C#에서 IO와 관련된 기능을 알아본다.
파일 정보와 디렉토리 정보
System.IO 네임스페이스 안에 포함되어 있다.
파일의 생성, 복사, 삭제, 이동 등의 기능을 지원하는 클래스
- File(정적 메서드), FileInfo(인스턴스 메서드)
디렉토리의 생성, 삭제, 이동, 조회를 처리하는 클래스
- Directory(정적 메서드), DirectoryInfo(인스턴스 메서드)
파일과 디렉터리관련 함수
기능 | File | FileInfo | Directory | DirectoryInfo |
생성 | Create() | Create() | CreateDirectory() | Create() |
복사 | Copy() | CopyTo() | - | - |
삭제 | Delete() | Delete() | Delete() | Delete() |
이동 | Move() | MoveTo() | Move() | MoveTo() |
존재 여부 확인 | Exists() | Exists | Exists() | Exists |
속성 조회 | GetAttributes() | Attributes | GetAttributes() | Attributes |
하위 디렉토리 조회 | GetDirectories() | GetDirectories() | ||
하위 파일 조회 | GetFiles() | GetFiles() |
파일의 생성
FileStream fs = File.Create("sample.dat");
FileInfo file = new FileInfo("my.dat");
FileStream fs = file.Create();
File 클래스의 정적 메서드를 사용하여 생성된 파일을 다룰 수 있는 핸들 클래스(FileStream)를 반환한다. FileInfo를 사용하여 인스턴스 메서드를 사용해도 파일을 생성할 수 있다.
폴더의 생성
DirectoryInfo dir = Directory.CreateDirectory("NewFolder");
DirectoryInfo dir = new DirectoryInfo("NewFolder");
dir.Create();
Directroy의 정적 메서드 CreateDirectory()와 DirectoryInfo 개체의 Create 메서드를 사용하여 폴더를 생성한다. 폴더의 경우 IO와는 직접적인 관련이 없기 때문에 FileStream과 같은 핸들이 존재하지 않는다.
- 현재 디렉토리 경로에 있는 모든 파일 정보를 리스트로 받아오는 방법
string dirPath = ".";
var files = (from file in Directory.GetFiles(dirPath)
let info = new FileInfo(file)
select new
{
Name = info.Name,
FullName = info.FullName,
FileSize = info.Length,
Attributes = info.Attributes
}).ToList();
I/O를 위한 Stream 스트림
파일, 콘솔, 네트워크 등 I/O를 위해 사용하는 버퍼로 데이터를 일렬로 흐르듯이 읽거나 쓰는 형식으로 사용하기 때문에 스트림이라고 한다. System.IO.Stream 네임스페이스에 포함되어 있으며 여러 I/O 기능을 제공하는 스트림 클래스를 제공한다.
- 스트림 클래스
- FileStream : 파일을 읽고 쓰는 것을 지원한다.
- IsolatedSotrageFileStream : 고립된 저장장치에 파일을 읽고 쓰는 것을 지원한다.
- MemoryStream : 요구 페이징과 관련된 저장장치의 메모리 I/O를 지원한다.
- BufferedStream : 읽고 쓰는 성능을 향상시키기 위해 지원한다.
- NetworkStream : 네트워크 소켓관련 I/O 기능을 지원한다.
- PipeStream : 파이프 통신 기능을 지원한다.
- CryptoStream : 데이터 스트림을 암호화?하는 기능을 제공한다.
파일 스트림 생성하기
FileStream fs = new FileStream("a.dat", FileMode.Create);
fs.Close();
파일 스트림 개체에 직접 파일 이름을 전달하여 파일 스트림을 생성한다. 사용한 뒤에는 반드시 Close() 혹은 Dispose()를 호출하여 사용한 리소스를 반납한다. 두번째 인자에 FileMode를 지정하여 생성/쓰기/읽기/Truncate/Append 행동을 지정할 수 있다.
Stream 스트림의 Read/Write
public abstract void Write(byte[] buffer, int offset, int count)
public abstract int Read(byte[] buffer, int offset, int count)
스트림 개체를 통해 데이터를 읽거나 쓰기 위해서는 위 함수를 통해 가능하다. 높은 추상화로 모든 Stream 클래스의 I/O기능을 지원하기 위해 byte 형식의 버퍼 데이터만 읽고 쓸 수 있다.
C#에서는 BitConverter라는 클래스로 GetBytes() 함수를 통해 기본 자료형 변수에 대해 byte[]로 변환할 수 있다.
변환되어 얻어진 byte[] 데이터는 실제 I/O 스트림 버퍼에서 사용하는 값으로 인텔의 경우 리틀 엔디안 방식으로 데이터가 쓰여진다.
0x12345678 -> 0x78563412 (낮은 주소부터 하위 바이트 데이터가 먼저 들어간다.)
long someValue = 0x123456789ABCDEF0;
Console.WriteLine($"{"Original Data", -1}, 0x{someValue, 1:X16}");
// 1) 파일 스트림 생성
Stream outStream = new FileStream("a.dat", FileMode.Create);
// 2) someValue (long 형식)을 byte 배열로 변환
byte[] wBytes = BitConverter.GetBytes(someValue);
Console.Write("{0, -13} : ", "Byte array");
// CLR의 경우 데이터를 기록할 때 리틀엔디안 방식을 사용
foreach (byte b in wBytes)
Console.Write("{0:X2}", b);
Console.WriteLine();
// 3) 변환한 byte 배열을 파일 스트림을 통해 파일에 기록
outStream.Write(wBytes, 0, wBytes.Length);
// 4) 파일 스트림 닫기
outStream.Close();
Stream inStream = new FileStream("a.dat", FileMode.Open);
byte[] rBytes = new byte[8];
int i = 0;
while (inStream.Position < inStream.Length)
rBytes[i++] = (byte)inStream.ReadByte();
long readValue = BitConverter.ToInt64(rBytes, 0);
Console.WriteLine($"{"Read Data",-13} : 0x{readValue:X16}");
inStream.Close();
BinaryWriter / BinaryReader
스트림 객체를 생성자에서 받아 소유하고 있는 스트림 버퍼에 이진 데이터를 기록할 수 있도록 도와준다.
public BinaryWriter(Stream output)
public BinaryWriter(Stream output, Encoding encoding)
public BinaryWriter(Stream output, Encoding encoding, bool leaveOpen)
public BinaryReader(Stream input)
public BinaryReader(Stream input, Encoding encoding)
public BinaryReader(Stream input, Encoding encoding, bool leaveOpen)
Stream 버퍼를 상속하지는 않았지만 스트림 버퍼를 소유하기 때문에 직접 Close() 혹은 Dispose()를 해줘야한다.
using을 이용하여 스트림 개체나 Reader/Writer 개체와 같은 IDisposable<T>를 상속하는 클래스를 이용할 때 자동으로 Dispose()를 호출하여 자원 해제 실수를 줄여준다.
using (BinaryWriter bw = new BinaryWriter(new FileStream("a.dat", FileMode.Create)))
{
bw.Write("테스트 중 입니다");
bw.Write("안녕하세요, 좋은 아침입니다.");
}
using BinaryReader br = new BinaryReader(new FileStream("a.dat", FileMode.Open));
Console.WriteLine($"File Size : {br.BaseStream.Length} bytes");
Console.WriteLine($"{br.ReadString()}");
Console.WriteLine($"{br.ReadString()}");
StreamWriter / StreamReader
StreamWriter/StreamReader를 통해 사람이 읽기 쉬운 텍스트 형식의 파일을 만들고 읽거나 쓸 수 있다.
using (StreamWriter sw = new StreamWriter(new FileStream("ReadMe.txt", FileMode.Create)))
개체 직렬화하기
Writer/Reader 클래스를 사용하여 데이터를 편하게 읽고 쓸 수 있게 되었지만 기본 데이터 형식이 아닌 프로그래머가 직접 정의한 클래스나 구조체 같은 복합 데이터 형식을 바로 사용할 수는 없다.
Serializable
[Serializable]
class MyClass
{
}
Serializable은 해당 개체가 직렬화가 가능하다(읽기/쓰기가 가능한)는 것을 애트리뷰트로 나타낸다. Serializable를 애트리뷰트로 표기한 클래스는 모든 필드를 데이터화 하여 저장 장치에 저장할 수 있다.
클래스 내 다른 오브젝트가 필드에 선언되어있다면 그 오브젝트 역시 Serializable 해야하며 그렇지 않을 경우 오류가 발생한다. 해당 오브젝트를 직렬화하고 싶지 않다면 NonSerialized를 애트리뷰트로 선언한다.
System.Runtime.Serialization.Formatters.Binary 네임스페이스에 있는 BinaryFormatter 클래스를 사용하면 직접 파일 스트림에 Serialize와 Deserialize 메서드를 사용해서 Object 객체를 읽고 쓸 수 있다.
using FileStream wfs = new FileStream("out.data", FileMode.Create);
MyVector vec = new MyVector();
vec.Name = "벡터";
vec.Resize(20, 5);
BinaryFormatter serializer = new BinaryFormatter();
serializer.Serialize(wfs, vec);
using FileStream rfs = new FileStream("out.data", FileMode.Open);
MyVector readVec = (MyVector)serializer.Deserialize(rfs);
하지만 .Net 5.0 부터 보안 문제로 BinaryFormatter의 이용을 권장하지 않는다. BinaryFormatter를 사용하면 경고 메세지를 띄우며 Json이나 Xml과 같은 다른 직렬화 방식을 추천한다.
'C# > C# 기본' 카테고리의 다른 글
[C#] default 연산자 (0) | 2021.11.08 |
---|---|
[C#] 스레드 (0) | 2021.10.21 |
[C#] dynamic 타입 (0) | 2021.10.19 |
[C#] 리플렉션과 애트리뷰트 (0) | 2021.10.19 |
[C#] LINQ (0) | 2021.10.18 |