뇌를 자극하는 C# 4.0 프로그래밍/ 파일 다루기

파일 정보와 디렉토리 정보 다루기

  • 파일(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);

// 파일 열기
![](https://drive.google.com/uc?id=)파일을 열거나 파일이 없으면 생성
![](https://drive.google.com/uc?id=;)    // 파일을 비워서 열기
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];

 ![](https://drive.google.com/uc?id=)(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 를 이용한 파일 읽기 예시
![](https://drive.google.com/uc?id=);)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 를 이용한 파일 읽기 예시
![](https://drive.google.com/uc?id=);)// 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 ()
    {
        ![](https://drive.google.com/uc?id=)new BinaryFormatter();

        MyClass obj = (MyClass)deserializer.Deserialize(rs);
        rs.Close();
    }
}
  • BinaryFormmater는 직렬화하거나 역질렬화 하는 역할을 한다.
  • List를 비롯한 컬렉션들도 직렬화를 지원한다. 예컨대 List<MyClass> 형식의 객체도 직렬화를 통해 파일에 저장해 뒀다가 이를 역직렬화를 통해 –(List<MyClass>)deserializer.Deserialize(obj)– 컬렉션으로 불러들일 수 있다.
It's only fair to share...Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

The author

지성을 추구하는 디자이너/ suyeongpark@abyne.com

댓글 남기기