상세 컨텐츠

본문 제목

[C#] 파일과 디렉토리

.NET/C#

by 클리엘 클리엘 2021. 5. 3. 11:41

본문

728x90

1. 파일

 

C#에서 파일을 다루는 데는 File과 FileInfo2개의 클래스를 사용합니다. 2개의 클래스는 거의 같은 기능을 제공하지만 File은 정적 메서드를 통해, FileInfo는 인스턴스를 통해 원하는 기능을 구현한다는 차이가 있습니다.

 

File클래스는 다음과 같은 방법으로 사용할 수 있습니다.

FileStream fs = File.Create("test.txt"); //파일 생성

File.Copy("test.txt", "test2.txt"); //test.txt파일을 text2.txt로 복사

File.Delete("test.txt") //파일 삭제

File.Move("C:\\test.txt", "D:\\test2.txt"); //파일 이동

File.Exists("C:\\test.txt") //파일 존재 유무

File.GetAttributes("test.txt"); //파일 속성

FileInfo클래스또한 다음의 방법으로 같은 기능을 수행할 수 있습니다.

FileInfo file = new FileInfo("test.txt");

FileStream fs = file.Create(); //파일 생성

FileInfo fi = file.CopyTo("test2.txt"); //파일 복사

file.Delete(); //파일 삭제

file.MoveTo("test2.txt"); //파일 이동

file.Exists //파일 존재 유무

file.Attribute //파일 속성

2. 디렉토리(폴더)

 

디렉터리 또한 파일과 마찬가지로 Directory클래스와 DirectoryInfo클래스를 제공하고 있으며 Directory는 정적 메서드를 DirectoryInfo는 인스턴스를 사용합니다.

 

Directory클래스는 다음과 같이 사용되며

DirectoryInfo di = Directory.CreateDirectory("tmp"); //디렉토리 생성

Directory.Delete("tmp"); //디렉토리 삭제

Directory.Move("tmp", "tmp2"); //디렉토리 이동

Directory.Exists("tmp"); //디렉토리 존재 여부

Directory.GetAttributes("tmp"); //디렉토리 속성

string[] dir = Directory.GetDirectories("tmp"); //하위디렉토리

string[] file = Directory.GetFiles("tmp"); //하위파일

DirectoryInfo 클래스로도 같은 기능을 수행할 수 있습니다.

DirectoryInfo di = new DirectoryInfo("tmp");

di.Create(); //디렉토리 생성

di.Delete(); //디렉토리 삭제

di.MoveTo("tmp"); //디렉토리 이동

di.Exists //디렉토리 존재 여부

di.GetDirectories(); //하위 디렉토리

di.GetFiles(); //하위 파일


3. 파일의 생성 및 읽기

 

파일 다루 기를 소개할 때 파일은 다음과 같은 방법으로 생성한다고 하였습니다.

FileStream fs = File.Create("test.txt"); //파일 생성

FileStream은 Stream클래스를 상속받은 클래스로서 디스크와 같은 저장장치에 파일 데이터를 기록하거나 읽을 수 있도록 하는 클래스입니다.

 

Stream이라는 것은 데이터의 흐름을 나타내는 것으로 파일 다룰 때를 예로 들면 파일과 메모리 사이에 Stream을 생성하고 이 Stream을 통해서 메모리에 있는 데이터를 파일 쪽으로 쓰거나 파일에 있는 데이터를 메모리로 읽어오게 됩니다. 참고로 데이터를 주고받은 순서에 따라서 데이터가 처음부터 끝까지 순서대로 처리되는 순차접근방식(Sequential Access)과 임의의 위치에 있는 데이터를 읽거나 쓸 수 있는 임의접근방식(Random Access) 2가지가 존재합니다.

 

Stream클래스는 바로 인스턴스를 생성해 사용할 수 없는 추상 클래스인데 때문에 이를 상속받은 파생 클래스를 통해서만 사용할 수 있습니다 다양한 환경에서의 데이터의 입출력을 Stream이라는 단일적인 방식으로 다룰 수 있도록 하기 위함인데 위에서 언급한 FileStream도 마찬가지로 Stream클래스를 상속받은 파생 클래스중 하나입니다. 다른 클래스로는 네트워크를 통해서 데이터를 주고받는 NetworkStream클래스, 파일을 압축하는 GZipStream 등이 있습니다.

 

위에서 파일을 생성할 때 파일명 이외에 다른 매개변수는 전달하지 않았는데 추가되는 매개변수에  따라 여러 가지 경우로 파일을 열거나 생성할 수 있습니다.

Stream stm = new FileStream("test.txt", FileMode.Create); //단순 파일 생성
Stream stm = new FileStream("test.txt", FileMode.Open); //파일 열기
Stream stm = new FileStream("test.txt", FileMode.OpenOrCreate); //파일이 없으면 생성
Stream stm = new FileStream("test.txt", FileMode.Truncate); //파일을 비우고 열기
Stream stm = new FileStream("test.txt", FileMode.Append); //파일내용 추가로 열기

파일을 열고 난 뒤에 파일을 쓰려면 FileStream클래스에 있는 Write() 또는 WriteByte() 메서드를 사용할 수 있습니다.

Stream stm = new FileStream("test.txt", FileMode.Create);

byte[] bValue = BitConverter.GetBytes(100);

stm.Write(bValue, 0, bValue.Length);

stm.Close();

Write() 메서드는 byte배열의 값을, WriteByte() 메서드는 byte값을 파일에 쓰게 됩니다. 예제에서는 Write() 메서드를 사용하기 위해 BitConverter클래스의 GetBytes() 메서드를 통해 100이라는 값을 byte로 변환하였습니다.

 

파일을 쓰기 위해 Write() 메서드를 호출했는데 만약 여러 건의 데이터를 작성해야 한다면 그만큼 Write() 메서드나 WriteByte() 메서드를 반복해서 호출하면 됩니다. 이때 Stream에서는 Position이라는 속성을 통해 현재 작성 중인 스트림의 위치 값을 확인할 수 있게 되는데 첫 바이트 값을 읽거나 쓰고 나면 Position값이 1씩 증가하게 됩니다. 이때 임의의 위치에 데이터를 쓰게 하려면 Stream의 Seek() 메서드를 통해 강제할 수도 있습니다.

 

예컨대 현재 위치에서 +3만큼 Position을 이동하려면 다음과 같이 메서드를 호출하면 됩니다.

stm.Seek(3, SeekOrigin.Current);

파일을 작성하고 난 이후에는 Close() 메서드로 스트림을 닫아줘야 합니다.

 

반대로 파일을 읽어오려면 파일의 내용을 byte로 다시 변환해 읽어와야 하는 과정을 거치야 합니다. 파일을 작성할 때와 정확히 반대의 개념이 적용됩니다.

Stream stm = new FileStream("test.txt", FileMode.Open);

byte[] readByte = new byte[4];

stm.Read(readByte, 0, readByte.Length);

int i = BitConverter.ToInt32(readByte, 0);

stm.Close();

보신 바와 같이 파일을 생성하고 읽기 위해 사용한 FileStream은 일일이 byte로 변환을 해야 합니다. 이러한 변환이 다소 불편하게 느껴질 수도 있는데 다행스럽게도 .NET에서는 쓰거나 읽기 위한 변환 없이 곧바로 메서드를 통해 원하는 데이터를 기록할 수 있는 클래스를 추가적으로 제공하고 있습니다. 그중 대표적인 것으로 BinaryWriter와 BinaryReader 클래스가 있습니다.

BinaryWriter bw = new BinaryWriter(new FileStream("test.txt", FileMode.Create));
bw.Write(100);
bw.Write("cliel");
bw.Close();

매우 간소화된 방법으로 파일을 작성하고 있습니다. 그저 Write() 메서드를 통해 원하는 데이터를 넣어 주기만 하면 됩니다. 물론 파일을 읽을 때도 마찬가지죠.

BinaryReader br = new BinaryReader(new FileStream("test.txt", FileMode.Open));
int i = br.ReadInt32();
string s = br.ReadString();
br.Close();

문자열로 이루어진 텍스트 파일도 StreamWriter / StreamReader 클래스를 통해서 거의 비슷한 방법으로 파일을 읽거나 쓸 수 있습니다.

StreamWriter sw = new StreamWriter(new FileStream("test.txt", FileMode.Create));
sw.Write(100);
sw.WriteLine("hello");
sw.Write("cliel");
sw.Close();

텍스트 전용 클래스답게 WriteLine() 메서드를 통해서 한 줄 단위 쓰기도 가능하군요.

StreamReader sr = new StreamReader(new FileStream("test.txt", FileMode.Open));
            
while(!sr.EndOfStream)
{
    string s = sr.ReadLine();
}

sr.Close();

파일을 읽을 때는 EndOfStream 속성을 통해 파일의 끝을 감지하면서 읽을 수 있습니다.


4. 복합 데이터 형식 다루기

 

문자열이나 숫자처럼 기본적인 데이터의 경우 Binary나 Stream관련 클래스를 사옹하면 충분히 데이터를 기록하고 읽어올 수 있습니다. 그러나 아래와 같은 경우

class Car
{
    public string Name { get; set; }
    public string Color { get; set; }
    public int Speed { get; set; }        
}

Car car = new Car()
{
    Name = "트럭",
    Color = "Red",
    Speed = 100
};

이러한 데이터 형식은 어떻게 저장할 수 있을까요? 이 질문에 대한 답은 객체의 직렬화를 통해서 얻을 수 있습니다. '직렬화'라는 말은 어떤 대상을 0과 1로 이루어진 데이터로 변환하여 그것을 저장하거나 읽어내는 것을 말합니다.

 

방법은 아주 간단합니다. 그저 애트리뷰트인 [Serializable]만 명시해 주면 됩니다.

[Serializable]
class Car
{
    public string Name { get; set; }
    public string Color { get; set; }
    public int Speed { get; set; }        
}

이걸로 준비는 끝났습니다. 이제 객체의 직렬화를 위한 BinaryFormatter클래스와 함께 위에서 파일을 저장했던 클래스를 그대로 사용해 객체를 저장해 주면 됩니다.

Car car = new Car()
{
    Name = "트럭",
    Color = "Red",
    Speed = 100
};

Stream s = new FileStream("test.txt", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();

bf.Serialize(s, car);
s.Close();

물론 직렬화를 통해 저장된 내용을 그대로 다시 가져올 수도 있습니다.

Stream s = new FileStream("test.txt", FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();

Car car = (Car)bf.Deserialize(s);
s.Close();

만약 직렬화를 원하는 않는 필드가 존재한다면 해당 필드는 [NonSerialized] 애트리뷰트를 부여해 주기만 하면 됩니다.

[Serializable]
internal class Car
{
    [NonSerialized]
    public string Name;

    public string Color { get; set; }
    public int Speed { get; set; }
}

 

728x90

'.NET > C#' 카테고리의 다른 글

[C#] 파일과 디렉토리  (0) 2021.05.03
[C#] dynamic  (0) 2021.04.30
[C#] LINQ  (0) 2021.04.22
[C#] 리플렉션  (0) 2021.04.19
[C#] 대리자(Delegate)  (0) 2021.04.15
[C#] 이벤트(Event)  (0) 2021.04.12

태그

관련글 더보기

댓글 영역