using System;
using System.IO; // 파일 입출력 작업을 위한 네임스페이스
/*
파일의 입력과 출력(파일의 '내용'을 읽고 쓰는 방법)
스트림(Stream):
데이터가 흐르는 통로.
메모리에서 저장매체로 데이터를 옮길 때(파일을 쓸 때)는 먼저 스트림을 만들어 둘 사이를 연결한 후,
메모리에 있는 데이터를 바이트 단위로 저장매체에 옮겨 넣습니다.
저장매체에서 메모리로 데이터를 옮길 때(파일을 읽을 때)는 먼저 스트림을 만들어 둘 사이를 연결한 후,
저장매체(파일)에 있는 데이터를 바이트 단위로 메모리에 차례차례 옮겨 옵니다.
순차 접근(Sequential Access)방식:
스트림은 데이터의 '흐름'이기 때문에,
스트림을 이용하여 파일을 다룰 때는 처음부터 끝까지 순서대로 읽고 쓰는 것이 보통입니다.
이러한 스트림의 구조는 네트워크나 데이터 백업 장치의 데이터 입/출력 구조와도 통하기 때문에
스트림을 이용하면 파일이 아닌 네트워크를 향해 데이터를 흘려보낼 수 있고,
테이프 백업 장치를 통해 데이터를 기록하거나 읽을 수도 있습니다.
임의 접근(Random Access)방식:
임의의 주소에 있는 데이터에 바로 접근하는 것.
Stream 클래스(System.IO.Stream 클래스):
Stream 클래스 그 자체로 입력 스트림, 출력 스트림 역할을 모두 할 수 있으며,
파일을 읽고 쓰는 방식 역시 순차 접근 방식과 임의 접근 방식 모두를 지원합니다.
단, Stream 클래스는 '추상 클래스'이므로, 이 클래스의 인스턴스를 직접 만들어 사용할 수는 없고,
이 클래스로부터 파생된 클래스를 이용해야 합니다.
Stream 클래스가 '추상 클래스'로 만들어진 이유?
스트림이 다루는 다양한 매체나 장치들에 대한 파일 입출력을 스트림 모델 '하나'로 다룰 수 있도록 하기 위함입니다.
*/
// System.IO 네임스페이스의 FileStream 클래스와 BitConverter 클래스를 사용하여
// long 형식의 데이터를 파일에 쓰고 읽는 프로그램
namespace BasicIO
{
class MainApp
{
static void Main(string[] args)
{
// (1) FileStream 클래스를 통해 파일에 데이터를 쓰기
long someValue = 0x123456789ABCDEF0; // long 형식의 someValue 변수를 선언하고, 16진수 값으로 초기화
Console.WriteLine("{0,-1} : 0x{1:X16}", "Original Data", someValue); // someValue 값을 16진수 형식으로 출력
// 1) 파일 스트림 생성(FileStream 클래스의 인스턴스 생성)
Stream outStream = new FileStream("a.dat", FileMode.Create); // "a.dat" 파일을 쓰기 모드(FileMode.Create)로 열거나 생성
// 2) BitConverter 클래스를 사용하여, someValue(long 형식)를 byte 배열로 변환
byte[] wBytes = BitConverter.GetBytes(someValue); // someValue 값을 바이트 배열로 변환
Console.Write("{0,-13} : ", "Byte array"); // "Byte array" 출력
foreach (byte b in wBytes) // wBytes 배열의 각 바이트를 16진수 형식으로 출력
Console.Write("{0:X2} ", b);
Console.WriteLine();
// 3) 변환한 byte 배열을 파일 스트림을 통해 파일에 기록
outStream.Write(wBytes, 0, wBytes.Length); // wBytes 배열의 내용을 outStream에 씀
// 4) 파일 스트림 닫기
outStream.Close(); // outStream 닫기
// (2) FileStream 클래스를 통해 파일에서 데이터를 읽어오기
byte[] rbytes = new byte[8]; // 읽어온 데이터를 저장할 바이트 배열 rbytes를 선언
// 1) 파일 스트림 생성
Stream inStream = new FileStream("a.dat", FileMode.Open); // "a.dat" 파일을 읽기 모드로 염
int i = 0;
while (inStream.Position < inStream.Length) // inStream의 현재 위치가 파일의 끝에 도달할 때까지 반복
rbytes[i++] = (byte)inStream.ReadByte(); // inStream에서 한 바이트씩 읽어와 rbytes 배열에 저장
// 2) BitConverter 클래스를 사용하여, rbytes 배열을 long 형식으로 변환
long readValue = BitConverter.ToInt64(rbytes, 0); // rbytes 배열을 long 형식으로 변환
Console.WriteLine("{0,-13} : 0x{1:X16} ", "Read Data", readValue); // readValue 값을 16진수 형식으로 출력
inStream.Close(); // inStream 닫기
}
}
}
/*
출력 결과
Original Data : 0x123456789ABCDEF0
Byte array : F0 DE BC 9A 78 56 34 12
Read Data : 0x123456789ABCDEF0
*/
코드 설명
using System;
using System.IO; // 파일 입출력을 위한 네임스페이스
namespace BasicIO
{
class MainApp
{
static void Main(string[] args)
{
long someValue = 0x123456789ABCDEF0; // long 형식의 someValue 변수 선언 및 16진수 값으로 초기화
Console.WriteLine("{0,-1} : 0x{1:X16}", "Original Data", someValue); // someValue 값을 16진수 형식으로 출력
Stream outStream = new FileStream("a.dat", FileMode.Create); // "a.dat" 파일을 쓰기 모드로 열거나 생성
byte[] wBytes = BitConverter.GetBytes(someValue); // someValue 값을 바이트 배열로 변환
Console.Write("{0,-13} : ", "Byte array"); // "Byte array" 출력
foreach (byte b in wBytes) // wBytes 배열의 각 바이트를 16진수 형식으로 출력
Console.Write("{0:X2} ", b);
Console.WriteLine();
outStream.Write(wBytes, 0, wBytes.Length); // wBytes 배열의 내용을 outStream에 씀
outStream.Close(); // outStream 닫기
Stream inStream = new FileStream("a.dat", FileMode.Open); // "a.dat" 파일을 읽기 모드로 염
byte[] rbytes = new byte[8]; // 읽어온 데이터를 저장할 바이트 배열 rbytes 선언
int i = 0;
while (inStream.Position < inStream.Length) // inStream의 현재 위치가 파일의 끝에 도달할 때까지 반복
rbytes[i++] = (byte)inStream.ReadByte(); // inStream에서 한 바이트씩 읽어와 rbytes 배열에 저장
long readValue = BitConverter.ToInt64(rbytes, 0); // rbytes 배열을 long 형식으로 변환
Console.WriteLine("{0,-13} : 0x{1:X16} ", "Read Data", readValue); // readValue 값을 16진수 형식으로 출력
inStream.Close(); // inStream 닫기
}
}
}
코드 설명
이 C# 코드는 System.IO 네임스페이스의 FileStream 클래스와 BitConverter 클래스를 사용하여 long 형식의 데이터를 파일에 쓰고 읽는 예제입니다.
long someValue = 0x123456789ABCDEF0;: long 형식의 someValue 변수를 선언하고 16진수 값으로 초기화합니다.Console.WriteLine("{0,-1} : 0x{1:X16}", "Original Data", someValue);: someValue 값을 16진수 형식으로 출력합니다.Stream outStream = new FileStream("a.dat", FileMode.Create);: "a.dat" 파일을 쓰기 모드(FileMode.Create)로 열거나 생성합니다. FileStream 클래스는 파일을 읽고 쓰는 데 사용됩니다.byte[] wBytes = BitConverter.GetBytes(someValue);: someValue 값을 바이트 배열로 변환합니다. BitConverter 클래스는 기본 데이터 타입과 바이트 배열 간의 변환을 제공합니다.Console.Write("{0,-13} : ", "Byte array");: "Byte array"를 출력합니다.foreach (byte b in wBytes) Console.Write("{0:X2} ", b);: wBytes 배열의 각 바이트를 16진수 형식으로 출력합니다.outStream.Write(wBytes, 0, wBytes.Length);: wBytes 배열의 내용을 outStream에 씁니다.outStream.Close();: outStream을 닫습니다.Stream inStream = new FileStream("a.dat", FileMode.Open);: "a.dat" 파일을 읽기 모드(FileMode.Open)로 엽니다.byte[] rbytes = new byte[8];: 읽어온 데이터를 저장할 바이트 배열 rbytes를 선언합니다.while (inStream.Position < inStream.Length): inStream의 현재 위치가 파일의 끝에 도달할 때까지 반복합니다.rbytes[i++] = (byte)inStream.ReadByte();: inStream에서 한 바이트씩 읽어와 rbytes 배열에 저장합니다.long readValue = BitConverter.ToInt64(rbytes, 0);: rbytes 배열을 long 형식으로 변환합니다.Console.WriteLine("{0,-13} : 0x{1:X16} ", "Read Data", readValue);: readValue 값을 16진수 형식으로 출력합니다.inStream.Close();: inStream을 닫습니다.출력 결과
Original Data : 0x123456789ABCDEF0
Byte array : F0 DE BC 9A 78 56 34 12
Read Data : 0x123456789ABCDEF0
스트림
저장 매체(예: 하드 디스크)에서 메모리로 데이터를 옮길 때 (파일을 읽을 때)는 먼저 스트림을 생성하여 둘 사이를 연결해야 합니다. 스트림은 데이터의 흐름을 나타내며, 저장 매체와 메모리 간의 데이터 이동 통로 역할을 합니다.
스트림을 생성한 후에는 저장 매체에 있는 데이터를 바이트 단위로 읽어서 메모리에 저장합니다. 이때 FileStream 클래스를 사용하여 파일을 열고, ReadByte() 메서드를 사용하여 바이트 단위로 데이터를 읽을 수 있습니다.
using System;
using System.IO;
class FileReadExample
{
static void Main(string[] args)
{
// 파일을 읽기 모드로 엽니다.
using (FileStream fs = new FileStream("data.txt", FileMode.Open))
{
// 파일의 끝에 도달할 때까지 반복합니다.
while (fs.Position < fs.Length)
{
// 파일에서 한 바이트를 읽습니다.
int byteData = fs.ReadByte();
// 읽은 바이트를 출력합니다.
Console.Write("{0} ", (char)byteData);
}
}
}
}
이 코드는 FileStream을 사용하여 data.txt 파일을 열고, ReadByte() 메서드를 사용하여 파일의 내용을 바이트 단위로 읽어서 콘솔에 출력합니다.
스트림을 사용하면 파일뿐만 아니라 네트워크, 메모리 등 다양한 곳에서 데이터를 읽고 쓸 수 있습니다.
이는 컴퓨터가 데이터를 저장하는 방식인 엔디안(Endianness) 때문입니다. 엔디안은 컴퓨터 메모리에 여러 바이트로 구성된 데이터를 저장할 때, 바이트 순서를 어떻게 배열하는지를 나타내는 방식입니다.
크게 빅 엔디안(Big Endian)과 리틀 엔디안(Little Endian)으로 나뉘는데,
대부분의 Intel x86 프로세서는 리틀 엔디안 방식을 사용합니다. 따라서 16진수 123456789ABCDEF0 을 바이트 배열로 변환하면, 메모리에는 다음과 같이 저장됩니다.
주소 | 값
-----|-----
0x00 | F0
0x01 | DE
0x02 | BC
0x03 | 9A
0x04 | 78
0x05 | 56
0x06 | 34
0x07 | 12
BitConverter.GetBytes(someValue) 메서드는 이렇게 메모리에 저장된 순서대로 바이트 배열을 생성합니다. 따라서 wBytes 배열의 요소를 출력하면 F0 DE BC 9A 78 56 34 12 순서로 출력되는 것입니다.
따라서 C# 프로그램에서 만든 파일을 다른 시스템에서 읽도록 하려면 이러한 바이트 오더의 차이를 반드시 고려하여야 합니다.
엔디안 문제 해결
만약 빅 엔디안 방식으로 데이터를 저장하거나 읽어야 할 경우, Array.Reverse() 메서드를 사용하여 바이트 배열의 순서를 뒤집을 수 있습니다.
Array.Reverse(wBytes); // wBytes 배열의 순서를 뒤집습니다.
이렇게 하면 wBytes 배열은 12 34 56 78 9A BC DE F0 순서로 변경됩니다.