Contents
파일 정보와 디렉토리 정보 다루기
- 파일(File)이란 컴퓨터 저장 매체에 기록되는 데이터의 묶음.
- 디렉토리(Directory)는 파일이 위치하는 주소로서 파일을 폴더(Folder)라고도 한다.
- 아래는 System.IO 네임스페이스에 있는 파일과 디렉토리를 다룰 수 있는 클래스들.
클래스 | 설명 |
---|---|
File | 파일의 생성, 복사, 삭제, 이동, 조회를 처리하는 정적 메소드 |
FileInfo | File 클래스와 동일하지만 인스턴스 메소드를 제공한다는 차이가 있다. |
Directory | 디렉토리의 생성, 삭제, 이동, 조회를 처리하는 정적 메소드 |
DirectoryInfo | Directory 클래스와 동일하지만 인스턴스 메소드를 제공한다는 차이가 있다. |
- 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() |
파일을 읽고 쓰기 위해 알아야 할 것들
- 메모리에서 하드 디스크 –DVD, 플래시 메모리 등 모두 마찬가지– 로 데이터를 옮길 때 먼저 스트림(Stream)을 만들어 둘 사이를 연결한 뒤에 메모리에 있는 데이터를 바이트 단위로 하드 디스크로 옮겨 넣는다. 하드 디스크에서 메모리로 데이터를 옮길 때도 마찬가지다.
- 스트림은 데이터의 흐름이기 때문에 스트림을 이용하여 파일을 다룰 때는 처음부터 끝까지 순서대로 읽고 쓰는 것이 보통이다. 이것을 순차 접근(Sequential Access) 방식이라고 한다.
- 이러한 스트림의 구조는 네트워크나 데이터 백업 장치의 데이터 입/출력 구조와도 통하기 때문에 스트림을 이용하면 파일이 아닌 네트워크를 향해서 데이터를 흘려 보낼 수 있고 테이프 백업 장치를 통해 데이터를 기록하거나 읽을 수도 있다.
- 하드 디스크는 암과 헤드를 움직여 디스크의 어떤 위치에 기록된 데이터라도 즉시 찾아갈 수 있다. 가령 1MB 크기의 파일에서 768kbyte번째 위치한 데이터를 읽고 싶을 때 하드 디스크는 앞의 767kbyte를 읽지 않아도 곧장 원하는 위치로 이동 할 수 있다. 이런 방식을 임의 접근(Random Access) 방식이라고 한다.
- Stream 클래스는 그 자체로 입력 스트림, 출력 스트림의 역할을 모두 할 수 있으며 파일을 읽고 쓰는 방식 역시 순차 접근 방식과 임의 접근 방식을 모두 지원한다.
- 단, Stream 클래스는 추상 클래스이므로 이 클래스로부터 파생된 클래스를 이용해야 한다. 가령 저장 장치와 데이터를 주고 받으려면 FileStream 클래스를 이용하고 네트워크를 통해 데이터를 주고 받으려면 NetworkStream 클래스를 이용해야 한다.
/*
* FileStream 인스턴스 생성 방식
*/
// 새 파일 생성
Stream stream1 = new FileStream("a.dat", FileMode.Create);
// 파일 열기
파일을 열거나 파일이 없으면 생성
 // 파일을 비워서 열기
Stream stream4 = new FileStream("a.dat", FileMode.Truncate);
// 파일을 덧붙이기 모드로 열기
Stream stream5 = new FileStream("a.dat", FileMode.Append);
/*
* FileStream을 이용해서 데이터를 파일에 기록하는 방식
*/
long someValue = 0x1123456789ABCDEF0;
// 파일 스트림 생성
Stream outStream = new FileStream("a.dat", FileMode.Create);
// someValue를 byte 배열로 변환.
// FileStream 클래스의 Read/ Write는 오로지 byte[]만 매개 변수로 받으므로 BitConverter를 이용해서 데이터를 변환해 주어야 한다.
byte[] wBytes = BitConverter.GetBytes(someValue);
// 변환한 byte 배열을 파일 스트림을 통해 파일에 기록
outStream.Write(wBytes, 0, wBytes.Length);
// 파일 스트림 닫기
outStream.Close();
/*
* FileStream을 이용해서 파일에서 데이터를 읽어오는 방식
* 기본 내용은 동일하므로 상세한 내용은 생략한다.
*/
byte[] rBytes = new byte[8];
(rBytes, 0, rBytes.Length);
long readValue = BitConverter.ToInt64(rBytes, 0);
inStream.Close();
- Stream 클래스에는 Position이라는 프로퍼티가 있는데, 이 Position 프로퍼티는 현재 스트림의 읽는 위치 또는 쓰는 위치를 나타낸다. 만일 Position이 3이라면 파일의 3번째 바이트에서 읽거나 쓸 준비가 되어 있는 상태라는 뜻이다.
- 위 그림을 보면 FileStream 객체를 생성할 때 Position이 0이 되고, WriteByte() 메소드를 호출할 때마다 데이터를 기록한 후 자동으로 Position이 1씩 증가되는 것을 알 수 있다.
- 따라서 여러 개의 데이터를 기록하는 일은 그냥 Write() –또는 WriteByte()– 메소드를 차례차례 호출하는 것으로 충분하다. 이렇게 파일을 순차적으로 쓰거나 읽는 방식을 “순차 접근(Sequential Access)” 라고 한다.
- 한편 파일 내의 임이의 위치에 Position이 위치하도록 할 수도 있는데 –임의 접근(Random Access) 방식– Seek() 메소드를 호출하거나 Position 프로퍼티에 직접 원하는 값을 대입하면 지정한 위치로 점프하여 읽기/ 쓰기를 할 수 있다.
- (코드 생략)
이진 데이터 처리를 위한 BinaryWriter/ BinaryReader
// BinaryWriter 를 이용한 파일 쓰기 예시
BinaryWriter bw = new BinaryWriter( new FileStream("a.dat", FileMode.Create) );
bw.Write(32);
bw.Write("Good Morning");
bw.Write(3.14);
bw.Close();
// BinaryReader 를 이용한 파일 읽기 예시
;)a = br.ReadInt32();
string s br.ReadString();
double c = br.ReadDouble();
br.Close();
- FileStream 클래스는 데이터를 읽고 쓸 때 byte[] 형식으로 변환해 줘야 한다는 점에서 대단히 불편하다. 이런 불편함을 해결하기 위해 .NET 프레임워크는 BinaryWriter, BinaryReader 클래스를 만들었다. 클래스 사용 방식의 위의 예시 코드를 보면 된다.
- (BinaryWriter와 BinaryReader가 만든 바이너리 파일에 대한 내용 생략)
텍스트 파일 처리를 위한 StreamWriter/ StreamReader
// StreamWriter 를 이용한 파일 쓰기 예시
StreamWriter sw = new StreamWriter( new FileStream("a.txt", FileMode.Create) );
sw.Write(32);
sw.Write("Good Morning");
sw.Write(3.14);
sw.Close();
// StreamReader 를 이용한 파일 읽기 예시
;)// EndOfStream 프로퍼티는 스트림의 끝에 도달했는지를 알려준다.
while ( sr.EndOfStream == false )
{
Console.WriteLine(sr.ReadLine());
}
sr.Close();
- StreamWriter, StreamReader 클래스를 이용하면 텍스트 파일을 읽고 쓸 수 있다.
객체 직렬화 하기
- BinaryWriter/Reader, StreamWriter/Reader는 기본 데이터 형식을 스트림에 쓰고 읽을 수 있도록 메소드들을 제공하지만, 프로그래머가 직접 정의한 클래스나 구조체 같은 복합 데이터 형식은 지원하지 않는다.
- BinaryWriter/Reader, StreamWriter/Reader로 복합 데이터 형식을 기록하고 읽으려면 그 형식이 갖고 있는 필드의 값을 저장할 순서를 정한 후, 이 순서대로 저장하고 읽는 코드를 작성해야 한다.
- 이 문제를 위해 C#은 복합 데이터 형식에 대해 쉽게 스트림에 쓰기/ 읽기를 할 수 있게 하는 직렬화(Serialization)라는 메커니즘을 제공한다.
- 직렬화란 객체의 상태를 메모리나 영구 저장 장치에 저장이 가능한 0과 1의 순서로 바꾸는 것을 말한다.
- .NET 에서는 이진(Binary) 형식의 직렬화도 지원하지만, JSON이나 XML 같은 텍스트 형식으로의 직렬화도 지원한다. (C# 완전 짱짱맨)
- C#에서는 객체를 직렬화 할 수 있는 아주 간단한 방법을 제공하는데, 클래스 선언부 앞에 [Serializable] 애트리뷰트를 붙여주기만 하면 된다.
직렬화 예시
// 직렬화 하려면 클래스 위에 `[Serializable]` 애트리뷰트를 붙여주면 된다.
[Serializable]
class MyClass
{
public int myField1;
public int myField2;
[NonSerialized]
public int myField3; // myField3 을 제외한 나머지 필드만 직렬화 된다.
public int myField4;
}
class MainApp
{
void WriteFile ()
{
Stream ws = new FileStream("a.dat", FileMode.Create);
BinaryFormatter serializer = new BinaryFormatter();
MyClass obj = new MyClass();
// obj 필드에 값 저장...
serializer.Serialize(ws, obj); // 직렬화
ws.Close();
}
void ReadFile ()
{
new BinaryFormatter();
MyClass obj = (MyClass)deserializer.Deserialize(rs);
rs.Close();
}
}
- BinaryFormmater는 직렬화하거나 역질렬화 하는 역할을 한다.
- List를 비롯한 컬렉션들도 직렬화를 지원한다. 예컨대 List<MyClass> 형식의 객체도 직렬화를 통해 파일에 저장해 뒀다가 이를 역직렬화를 통해 –(List<MyClass>)deserializer.Deserialize(obj)– 컬렉션으로 불러들일 수 있다.