그러냐

C#에서의 IO [2/3] 본문

c#

C#에서의 IO [2/3]

관절분리 2016. 1. 27. 18:35
반응형
출처 Santi::平和 | 카르마
원문 http://blog.naver.com/hahnes2/40023216693

닷넷에서는 스트림처리를 위해 System.IO.Stream클래스를 제공합니다.

 

<System.IO.Stream 클래스 보기>

http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/cpref/html/frlrfsystemiostreamclasstopic.asp

 

역시 자바처럼 Stream클래스는 가장 기본적인 기능들만 제시해 주고 있습니다.

간단히 말해 스트림은 다음 중 하나 이상의 작업을 지원하는 개체입니다.

바이트 읽기

바이트 쓰기

특정 위치 검색

, 스트림에서 바이트를 읽고 이를 스트림에 작성하고 스트림의 특정 위치로 이동할 수 있습니다. 이런 역할을 수행하기 위해 Stream클래스에서는 다음과 같은 멤버를 제공하고 있습니다.

 

<Stream클래스멤버 보기>

http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/cpref/html/frlrfsystemio.asp

 

멤버들을 보시면 알겠지만 abstract입니다. Stream클래스를 상속한 클래스들이 나름대로 정의해 사용해야하죠. 그밖에 public 메서드를 보시면 virtual로 선언되어 있습니다.

저번 스터디 내용이었던 다형성(polymorphism)에서 배웠습니다.

virtual로 선언해 둠으로서 Stream을 상속하는 클래스에서 재정의가능하게 해 두었습니다.

알아봤다시피 Stream클래스 자체는 스트림이 작동하는 방식을 정의하는 추상적인 클래스이지 특정 유형의 스트림을 구현한 클래스는 아닙니다.

, 우리가 실제 코드를 작성할 경우에는 상황에 맞는, 스트림에서 파생된 클래스를 이용하여 처리해야겠죠. 일반적인 스트림은 다음과 같습니다.

Stream클래스로부터 파생된 클래스는 다음과 같습니다.

 

System.Object
   System.MarshalByRefObject
      System.IO.Stream
         
System.Data.OracleClient.OracleBFile
         System.Data.OracleClient.OracleLob
         System.IO.BufferedStream
         System.IO.FileStream
         System.IO.MemoryStream
         System.Net.Sockets.NetworkStream
         System.Security.Cryptography.CryptoStream

 

System.Data.OracleClient.OracleBFile클래스와

System.Data.OracleClient.OracleLob클래스는

오라클에서 제공하는 BFile이나 Lob데이터형식을 지원하기 위한 클래스이므로

여기서는 대상외로 하겠습니다.

System.Net.Sockets.NetworkStream는 네트워크를 통한 입출력에 관한 클래스입니다.

이 클래스에 대한 내용은 네트워크에 대한 내용에서 다뤄질 것입니다.

System.Security.Cryptography.CryptoStream는 입출력시 파일 암호화를 지원하는 클

래스입니다. 역시 암호화에 대한 내용에서 다뤄질 것 입니다.

처음에 Stream클래스에 대한 기능설명에서도 말씀 드렸다시피 스트림클래스는

바이트로 읽기 쓰기를 합니다. 그러므로 한글이나 일본어같은 유니코드문자를

제대로 읽고 쓰기위해서는 Stream클래스로 파생된 클래스를 사용하는 것이 아닌

다음의 클래스들중 하나를 이용하여 코드를 구현해야 합니다.

 

System.IO.StreamReader

System.IO.StreamWriter

System.IO.StringReader

System.IO.StringWriter

System.IO.TextReader

System.IO.TextWriter

 

StreamReader, StringReader클래스는 TextReader클래스로부터

StreamWriter, StringWriter클래스는 TextWriter클래스로부터 파생된 클래스입니다.

 

또한 C#에서는 바이너리데이터를 읽고 쓰기 위해 다음의 클래스를 이용합니다.

 

System.IO.BinaryReader

System.IO.BinaryWriter

 

파일에 대해 스트림을 제공하는 FileStream

 

먼저 FileStream클래스부터 차근차근 보도록 하겠습니다.

 

<FileStream 멤버 보기>

http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/cpref/html/frlrfsystemiofilestreammemberstopic.asp

 

파일스트림의 생성자를 보시면 파일의 오픈모드(FileMode열거형)도 지정할 수 있고, 억세스모드(FileAccess열거형)나 다른 프로세스와의 파일 공유여부(FileShare열거형)까지도 함께 지정할 수 있습니다. 호오~~

FileStream s2 = new FileStream(name, FileMode.Open, FileAccess.Read, FileShare.None);

다음과 같은 코드의 경우 name에 해당하는 파일은 현프로세스가 살아있는 동안에는 다른 프로세스에서 접근이 불가능합니다.

C에서의 fopen()을 떠올리게 하는군요. JAVA보다 더 쉽게 파일스트림에 대한 기능설정이 가능하도록 한 것 같습니다. 상당히 다양한 생성자가 존재합니다.

다음 예제는 Test.data라는 파일이 존재하는지 확인하고 존재하지 않는다면FileMode.CreateNew를 통해 Test.data라는 파일명으로 파일스트림을 생성합니다.

FileStream fs = new FileStream(FILE_NAME, FileMode.CreateNew);

그리고 아직 설명은 안했지만 BinaryWriter로 파일스트림을 감싸 바이너리값으로 1부터 10까지 데이터파일에 int형으로 저장합니다. 그 후에 읽기전용으로 파일스트림을 열어

fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read);

데이터파일에 있는 바이너리값들을 int형으로 읽어들여 화면에 출력합니다.

여하튼 데이터파일에는 바이너리형태로 숫자들이 기록되어 있기 때문에 파일을 열어도 숫자가 보이지는 않습니다.

 

 

using System;

using System.IO;

 

class MyStream

{

    private const string FILE_NAME = "Test.data";

    public static void Main(String[] args)

    {

        // Create the new, empty data file.

        if (File.Exists(FILE_NAME))

        {

            Console.WriteLine("{0} already exists!", FILE_NAME);

            return;

        }

        FileStream fs = new FileStream(FILE_NAME, FileMode.CreateNew);

        // Create the writer for data.

        BinaryWriter w = new BinaryWriter(fs);

        // Write data to Test.data.

        for (int i = 0; i < 11; i++)

        {

            w.Write((int)i);

        }

        w.Close();

        fs.Close();

        // Create the reader for data.

        fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read);

        BinaryReader r = new BinaryReader(fs);

        // Read data from Test.data.

        for (int i = 0; i < 11; i++)

        {

            Console.WriteLine(r.ReadInt32());

        }

        r.Close();

        fs.Close();

    }

}

 

Stream클래스에서 파생된 FileStream, BufferedStream, MemoryStream클래스는 파일포인터를 이용한 파일에의 임의 접근이 가능합니다(Seek). 하지만 뒤에 알아볼 문자 데이터를 다루는 스트림 클래스들은 이런 임의 접근을 지원하지 않습니다.

다음 코드 예제에서는 데이터를 바이트 단위로 파일에 쓴 다음 데이터가 올바르게 쓰여졌는지 확인하는 방법을 보여 줍니다. System.Random클래스의 NextBytes메서드를 사용해 지정된 바이트배열을 난수로 채웁니다. 그리고 FileStream을 이용해 파일을 생성하고, 배열의 내용을 바이트형으로 파일에 write합니다. Seek메서드를 이용해 파일 끝을 가리키고 있는 파일포인터를 파일 처음으로 이동시킵니다. 그리고 FileStream ReadByte메서드를 이용해 파일에 있는 내용을 바이트단위로 읽어 들여 실제 배열에 있는 내용과 비교해 정합성을 판정하는 코드입니다.

 

using System;

using System.IO;

 

class FStream

{

    static void Main()

    {

        const string fileName = "Test#@@#.dat";

 

        // Create random data to write to the file.

        byte[] dataArray = new byte[100000];

        new Random().NextBytes(dataArray);

 

        using (FileStream

            fileStream = new FileStream(fileName, FileMode.Create))

        {

            // Write the data to the file, byte by byte.

            for (int i = 0; i < dataArray.Length; i++)

            {

                fileStream.WriteByte(dataArray[i]);

            }

 

            // Set the stream position to the beginning of the file.

            fileStream.Seek(0, SeekOrigin.Begin);

 

            // Read and verify the data.

            for (int i = 0; i < fileStream.Length; i++)

            {

                if (dataArray[i] != fileStream.ReadByte())

                {

                    Console.WriteLine("Error writing data.");

                    return;

                }

            }

            Console.WriteLine("The data was written to {0} " +

                "and verified.", fileStream.Name);

        }

    }

}

 

 

버퍼를 이용하여 오버헤드를 줄여라~ BufferedStream

 

다음으로 BufferedStream클래스를 살펴 보겠습니다.

이름만 봐도 아시다시피 입출력시 버퍼를 두어 입출력횟수를 줄여 성능을 향상시킵니다.

자바에도 똑같은 기능을 하는 BufferedReader/Writer클래스가 있습니다만 다시 한번 설명하자면  FileStream같이 바이트단위로 데이터를 읽고 쓰는 경우에는 바이트당 read/write가 발생하기 때문에 오버헤드가 발생합니다. 물론 쓰고 읽는 데이터가 작은 크기라면 별 상관이 없겠지만서도.. 아무튼 이러한 바이트스트림에 BufferedStream을 이용해 버퍼크기만큼 데이터를 저장했다가 한번에 읽고 쓰기를 한다면 물리적인 디스크와 접촉하는 횟수가 줄기 때문에 성능향상을 기대할 수가 있습니다.

 

<BufferedStream 멤버보기>

http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/cpref/html/frlrfsystemiobufferedstreammemberstopic.asp

 

BufferedStream의 생성자를 보면 자바의 BufferedReader/Writer와 같이 Stream형태의 개체를 매개변수로 받아 개체를 생성합니다. 또한 생성시 버퍼사이즈를 설정할 수도 있습니다. 기본 버퍼 사이즈는 (1024*4)바이트입니다. 버퍼 크기가 128KB일 경우 버퍼링된 작업은 버퍼링되지 않은 작업에 비해 10-20% 정도 속도가 빠르다고 합니다(MSDN).

public BufferedStream(Stream stream);

public BufferedStream(Stream stream, int buffersize);

자바에서와 같이BufferedStream은 자체적으로 스트림을 생성하지 못하기 때문에 우선 사용하기 위해서는 스트림을 생성해서 건네주어야겠죠.

FileStream fs = new FileStream(“test.txt”, FileMode.Open, FileAccess.Write);

BufferedStream bs = new BufferedStream(fs);

bs.Write(……);

 

 

다음 코드는 아스키 값을 파일에 쓰고 읽는 간단한 코드입니다. 버퍼를 이용해 파일에 쓰는데 한번, 읽는데 한번의 파일 접근(Access)이 이루어집니다.

 

using System;

using System.IO;

 

class BufferedStreamTest

{

    public static void Main(String[] args)

    {

        byte[] data = new byte[127];

 

        FileStream fs = new FileStream("test.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);

        BufferedStream bs = new BufferedStream(fs);

        for (int i = 0; i < 127; i++) {

            data[i] = (byte)(i + 1);

        }

        bs.Write(data, 0, data.Length);

        bs.Close();

 

        fs = new FileStream("test.txt", FileMode.Open, FileAccess.Read);

        bs = new BufferedStream(fs);

        data = new byte[127];

        bs.Read(data, 0, 127);

        Console.WriteLine(System.Text.Encoding.Default.GetString(data));

        bs.Close();

 

    }

}

 

위 코드에서는BufferedStream을 사용하여 파일에 쓰는데 한번, 읽는데 한번의 파일 입출력이 이루어 지지만, 그냥 FileStream을 이용했다면 127회 파일에 대한 입출력이 이루어졌을 겁니다.

for(int i=1; i<=127; i++){

    file.WriteByte(Convert.ToByte(i));             

}

 

 

백업 저장소가 메모리인 MemoryStream

 

다음으로 알아볼 스트림은 MemoryStream입니다.

MSDN에 있는MemoryStream에 대한 설명을 인용해 보기로 하겠습니다.

MemoryStream 클래스에서는 디스크나 네트워크 연결 대신 메모리를 백업 저장소로 사용하는 스트림을 만듭니다. MemoryStream 에서는 MemoryStream 개체 작성 시 초기화되거나 비어 있게 만들어진 부호 없는 바이트 배열로 저장된 데이터를 캡슐화합니다. 캡슐화된 데이터는 메모리에서 직접 액세스할 수 있습니다. 메모리 스트림을 사용하면 응용 프로그램의 임시 버퍼 및 파일의 필요성을 줄일 수 있습니다.

 

<MemoryStream 멤버 보기>

http://msdn.microsoft.com/library/kor/default.asp?url=/library/KOR/cpref/html/frlrfsystemiobufferedstreamclasstopic.asp

 

생성자를 보면 초기 메모리바이트도 함께 설정할 수 있는데 int형을 이용하는 생성자는 나중에 크기조정 가능한 개체를 생성하지만, byte배열을 이용하는 생성자는 크기 조정이 불가능한 개체로 초기화 합니다.

중요 멤버를 한번 보자면...

멤버                      설명

Capacity               이 스트림에 할당된 바이트 수를 가져오거나 설정합니다.

GetBuffer              스트림에 할당된 내용을 바이트 배열로 반환합니다.

ToArray                전체 문자열의 내용을 바이트 배열로 반환합니다.

WriteTo                MemoryStream의 모든 내용을 다른 문자열 파생 타입에 출력합니다.

 

다음 예제는 MemoryStream의 사용법에 대한 MSDN에 나와 있는 예제입니다.

우선 UnicodeEncoding개체를 생성합니다. 첫번째 문자열로는"Invalid file path characters are: "를 작성하고 두번째 문자열에는 System.IO.Path클래스의InvalidPathChars메서드를 이용하여 부적절한 패스문자들을 입력받습니다. 두 문자열을 MemoryStream개체를 이용해 메모리에 쓰고, 메모리로부터 읽어 내어 출력하는 예제입니다.

 

using System;

using System.IO;

using System.Text;

 

class MemStream

{

    static void Main()

    {

        int count;

        byte[] byteArray;

        char[] charArray;

        UnicodeEncoding uniEncoding = new UnicodeEncoding();

 

        // Create the data to write to the stream.

        byte[] firstString = uniEncoding.GetBytes(

            "Invalid file path characters are: ");

        byte[] secondString = uniEncoding.GetBytes(

            Path.InvalidPathChars);

 

        using (MemoryStream memStream = new MemoryStream(100))

        {

            // Write the first string to the stream.

            memStream.Write(firstString, 0, firstString.Length);

 

            // Write the second string to the stream, byte by byte.

            count = 0;

            while (count < secondString.Length)

            {

                memStream.WriteByte(secondString[count++]);

            }

 

            // Write the stream properties to the console.

            Console.WriteLine(

                "Capacity = {0}, Length = {1}, Position = {2}\n",

                memStream.Capacity.ToString(),

                memStream.Length.ToString(),

                memStream.Position.ToString());

 

            // Set the position to the beginning of the stream.

            memStream.Seek(0, SeekOrigin.Begin);

 

            // Read the first 20 bytes from the stream.

            byteArray = new byte[memStream.Length];

            count = memStream.Read(byteArray, 0, 20);

 

            // Read the remaining bytes, byte by byte.

            while (count < memStream.Length)

            {

                byteArray[count++] =

                    Convert.ToByte(memStream.ReadByte());

            }

 

            // Decode the byte array into a char array

            // and write it to the console.

            charArray = new char[uniEncoding.GetCharCount(

                byteArray, 0, count)];

            uniEncoding.GetDecoder().GetChars(

                byteArray, 0, count, charArray, 0);

            Console.WriteLine(charArray);

        }

    }

}

 

 

이상으로 System.IO.Stream클래스로부터 파생된 스트림에 대해 알아 보았습니다. 

다음에는 문자 스트림에 대해 알아보겠습니다.

 

 

반응형

'c#' 카테고리의 다른 글

동적 배열  (0) 2016.01.27
[WCF]데이터 보내기1  (0) 2016.01.27
합계구한 컬럼을 DataTable에 추가하기  (0) 2016.01.27
C#에서의 IO [3/3]  (0) 2016.01.27
ArrayList 파일 IO  (0) 2016.01.27