본문 바로가기

코딩 이야기/델파이 코딩

델파이 스트림(TStream) 종류별 설명 및 예시

스트림(TStream) 종류별 설명 및 예시

델파이에서 스트림(Stream)은 데이터의 연속적인 흐름을 추상화한 개념입니다. System.Classes 유닛에 정의된 TStream은 모든 스트림 클래스의 추상 기반 클래스로, 데이터가 메모리에 있든, 파일에 있든, 네트워크를 통해 전송되든 상관없이 일관된 방식으로 데이터를 읽고 쓸 수 있는 인터페이스를 제공합니다.

TStream 자체는 추상 클래스이므로 직접 인스턴스화할 수 없으며, 실제 작업에는 이를 상속받아 구현된 구체적인 스트림 클래스들을 사용합니다. 주요 메서드로는 데이터를 읽는 Read, 쓰는 Write, 스트림 내 위치를 이동하는 Seek가 있으며, Position (현재 위치), Size (전체 크기) 등의 속성을 공통으로 가집니다.

델파이 스트림 종류별 설명 및 예시

1. TMemoryStream

메모리 블록을 스트림으로 다룹니다. 동적으로 크기가 조절되는 메모리 버퍼에 데이터를 읽고 쓸 때 사용합니다. 비교적 작은 크기의 데이터를 임시로 저장하거나, 다른 스트림으로 데이터를 전송하기 전 중간 버퍼로 활용하기에 좋습니다.

  • 주요 용도: 임시 데이터 저장, 이미지/데이터 변환 버퍼, 다른 컴포넌트 간 데이터 전달.
  • 특징: LoadFromFile, SaveToFile 메서드를 통해 파일과 쉽게 데이터를 주고받을 수 있습니다.

TMemoryStream 예제

메모리 스트림에 정수와 문자열을 쓰고 다시 읽어오는 예제입니다.


uses System.Classes, System.SysUtils;

procedure MemoryStreamExample;
var
  MemStream: TMemoryStream;
  IntValue: Integer;
  StrValue: string;
  StrLen: Integer;
  Buffer: TBytes;
begin
  MemStream := TMemoryStream.Create;
  try
    // 정수 값 쓰기 (4바이트)
    IntValue := 12345;
    MemStream.Write(IntValue, SizeOf(Integer));

    // 문자열 쓰기 (길이 정보 + 문자열 데이터)
    StrValue := 'Hello Delphi Stream!';
    StrLen := Length(StrValue); // 문자열 길이
    MemStream.Write(StrLen, SizeOf(Integer)); // 길이 먼저 쓰기 (4바이트)
    // 문자열 데이터 쓰기 (UTF8 인코딩된 바이트 사용 권장)
    Buffer := TEncoding.UTF8.GetBytes(StrValue);
    MemStream.Write(Buffer, Length(Buffer));

    // 읽기 위해 위치를 처음으로 이동
    MemStream.Position := 0;

    // 정수 값 읽기
    MemStream.Read(IntValue, SizeOf(Integer));
    ShowMessage('읽은 정수: ' + IntToStr(IntValue)); // 결과: 12345

    // 문자열 읽기 (길이 먼저 읽고, 그 길이만큼 데이터 읽기)
    MemStream.Read(StrLen, SizeOf(Integer)); // 길이 읽기
    SetLength(Buffer, StrLen); // 버퍼 크기 조정 (UTF8 바이트 길이 기준)
    if StrLen > 0 then
      MemStream.Read(Buffer, StrLen); // 데이터 읽기
    StrValue := TEncoding.UTF8.GetString(Buffer); // UTF8 바이트를 문자열로 변환
    ShowMessage('읽은 문자열: ' + StrValue); // 결과: Hello Delphi Stream!

    // 파일로 저장 (선택적)
    // MemStream.SaveToFile('C:\Temp\MemoryData.dat');

  finally
    MemStream.Free; // 반드시 해제
  end;
end;
        

참고: 문자열을 쓸 때는 길이 정보를 함께 저장해야 나중에 정확히 읽어올 수 있습니다. 또한, 문자열 인코딩(예: UTF-8)을 명시적으로 처리하는 것이 안전합니다.

2. TFileStream

디스크 파일을 스트림으로 다룹니다. 파일의 내용을 읽거나 파일에 데이터를 쓸 때 사용합니다. 생성자에서 파일명과 파일 열기 모드(읽기 전용, 쓰기 전용, 생성 등)를 지정해야 합니다.

  • 주요 용도: 파일 읽기/쓰기, 파일 복사/이동, 대용량 데이터 처리.
  • 주의사항: 파일 작업 완료 후 반드시 Free를 호출하여 파일 핸들을 해제해야 합니다. try...finally 구문 사용이 필수적입니다.

TFileStream 예제

파일 스트림을 사용하여 텍스트 파일에 내용을 쓰고 읽는 예제입니다.


uses System.Classes, System.SysUtils, System.IOUtils;

procedure FileStreamExample;
var
  FileStream: TFileStream;
  FilePath: string;
  TextToWrite: string;
  ReadBuffer: TBytes;
  ReadString: string;
begin
  FilePath := TPath.Combine(TPath.GetTempPath, 'MyFileStreamTest.txt'); // 임시 폴더에 파일 생성

  // 파일에 쓰기 (파일이 없으면 생성, 있으면 덮어씀)
  FileStream := TFileStream.Create(FilePath, fmCreate);
  try
    TextToWrite := '델파이 파일 스트림 테스트입니다.' + sLineBreak + '두 번째 줄입니다.';
    ReadBuffer := TEncoding.UTF8.GetBytes(TextToWrite); // UTF-8 인코딩
    FileStream.Write(ReadBuffer, Length(ReadBuffer));
    ShowMessage(FilePath + ' 파일 쓰기 완료');
  finally
    FileStream.Free; // 쓰기 완료 후 해제
  end;

  // 파일에서 읽기 (읽기 모드로 열기)
  if TFile.Exists(FilePath) then
  begin
    FileStream := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyNone); // 읽기 + 공유 허용
    try
      SetLength(ReadBuffer, FileStream.Size); // 파일 크기만큼 버퍼 설정
      FileStream.Read(ReadBuffer, FileStream.Size); // 전체 내용 읽기
      ReadString := TEncoding.UTF8.GetString(ReadBuffer); // UTF-8 디코딩
      ShowMessage(FilePath + ' 파일 내용:' + sLineBreak + ReadString);
    finally
      FileStream.Free; // 읽기 완료 후 해제
    end;
    // TFile.Delete(FilePath); // 테스트 후 파일 삭제 (선택적)
  end
  else
  begin
    ShowMessage(FilePath + ' 파일을 찾을 수 없습니다.');
  end;
end;
        

3. TStringStream

문자열(String) 데이터를 스트림처럼 다룹니다. 내부적으로 문자열 데이터를 메모리에 유지하며, 이를 읽거나 쓸 수 있습니다. 특히 텍스트 인코딩(ANSI, UTF-8 등)을 처리할 때 유용합니다.

  • 주요 용도: 문자열 기반 데이터 처리, 텍스트 인코딩 변환, 메모리 상에서의 문자열 조작.
  • 주요 속성: DataString 속성을 통해 내부 문자열 데이터에 직접 접근할 수 있습니다. 생성자에서 초기 문자열과 인코딩을 지정할 수 있습니다.

TStringStream 예제

문자열 스트림을 사용하여 메모리 상에서 텍스트 데이터를 조작하는 예제입니다.


uses System.Classes, System.SysUtils;

procedure StringStreamExample;
var
  StringStream: TStringStream;
  InitialString: string;
  WriteString: string;
  ReadString: string;
  ReadBuffer: array[0..255] of Byte; // 작은 버퍼
  BytesRead: Integer;
begin
  InitialString := '초기 문자열 데이터. ';
  // UTF-8 인코딩을 명시하여 StringStream 생성
  StringStream := TStringStream.Create(InitialString, TEncoding.UTF8);
  try
    // 스트림 끝에 문자열 추가 (Write 사용 시 바이트 배열 필요)
    WriteString := '추가된 문자열입니다.';
    // Position을 끝으로 이동해야 추가됨
    StringStream.Position := StringStream.Size;
    BytesRead := StringStream.Write(TEncoding.UTF8.GetBytes(WriteString), Length(TEncoding.UTF8.GetBytes(WriteString)));
    ShowMessage(IntToStr(BytesRead) + ' 바이트 추가됨.');

    // DataString 속성으로 전체 내용 확인
    ShowMessage('전체 내용 (DataString): ' + StringStream.DataString);

    // 처음부터 일부 내용 읽기 (Read 사용)
    StringStream.Position := 0; // 위치를 처음으로
    BytesRead := StringStream.Read(ReadBuffer, 10); // 최대 10바이트 읽기
    ReadString := TEncoding.UTF8.GetString(ReadBuffer, 0, BytesRead); // 읽은 만큼만 변환
    ShowMessage('처음 10바이트 읽기 (Read): ' + ReadString);

  finally
    StringStream.Free;
  end;
end;
        

4. TResourceStream

애플리케이션의 리소스(Resource) 데이터를 스트림으로 다룹니다. 실행 파일(.exe)이나 DLL에 포함된 이미지, 아이콘, 문자열 테이블 등의 리소스 데이터를 읽어올 때 사용합니다.

  • 주요 용도: 프로그램에 내장된 리소스(이미지, 사운드, 텍스트 등) 접근.
  • 생성 방법: 리소스 핸들과 리소스 이름(또는 ID)을 이용하여 생성합니다.

TResourceStream 예제

애플리케이션 리소스에 포함된 'MY_TEXT'라는 이름의 RCDATA를 읽어오는 예제입니다.

주의: 이 예제를 실행하려면 프로젝트에 실제 'MY_TEXT'라는 이름의 RCDATA 리소스가 포함되어 있어야 합니다. (예: .rc 파일을 통해 추가하고, {$R *.res} 지시문 사용)


uses System.Classes, System.SysUtils;

// 프로젝트 파일(.dpr)이나 유닛에 리소스 파일 링크 필요
// {$R MyResources.res} // 가정: MyResources.res 파일에 MY_TEXT 리소스가 있음

procedure ResourceStreamExample;
var
  ResStream: TResourceStream;
  ResBytes: TBytes;
  ResString: string;
  ResourceName: string;
begin
  ResourceName := 'MY_TEXT'; // 리소스 이름 (대소문자 구분 주의)
  try
    // HInstance는 현재 모듈(EXE 또는 DLL) 핸들, 리소스 이름, 리소스 타입(RT_RCDATA) 지정
    ResStream := TResourceStream.Create(HInstance, ResourceName, RT_RCDATA);
  except
    on E: EResNotFound do
    begin
      ShowMessage('리소스 ''' + ResourceName + '''를 찾을 수 없습니다.');
      Exit; // 리소스 없으면 종료
    end;
    else
      raise; // 다른 예외는 다시 발생시킴
  end;

  try
    SetLength(ResBytes, ResStream.Size); // 리소스 크기만큼 버퍼 할당
    ResStream.Read(ResBytes, ResStream.Size); // 리소스 데이터 읽기
    // 리소스 데이터가 텍스트라고 가정하고 UTF-8로 디코딩
    ResString := TEncoding.UTF8.GetString(ResBytes);
    ShowMessage('리소스 ''' + ResourceName + ''' 내용:' + sLineBreak + ResString);
  finally
    ResStream.Free; // 반드시 해제
  end;
end;
        

5. TBlobStream

데이터베이스의 BLOB(Binary Large Object) 필드를 스트림으로 다룹니다. TDataSet 컴포넌트의 BLOB 필드(이미지, 동영상, 문서 파일 등 대용량 바이너리 데이터)를 읽거나 쓸 때 사용됩니다.

  • 주요 용도: 데이터베이스 BLOB 필드 데이터 처리.
  • 생성 방법: TDataSet.CreateBlobStream 메서드를 통해 특정 필드와 연결된 스트림 객체를 생성합니다.

TBlobStream 예제 (개념 설명)

TBlobStream은 데이터베이스 필드와 직접 연동되므로, 간단한 독립 실행 예제보다는 데이터셋 컴포넌트(예: TFDQuery, TADOQuery, TClientDataSet 등)와 함께 사용됩니다.

일반적인 사용 패턴은 다음과 같습니다:

  1. 데이터셋 컴포넌트에서 BLOB 필드를 가져옵니다 (예: MyQuery.FieldByName('ImageData') as TBlobField).
  2. 해당 필드에 대해 CreateBlobStream 메서드를 호출하여 TBlobStream 객체를 생성합니다. (읽기 또는 쓰기 모드 지정)
  3. 생성된 스트림 객체를 사용하여 데이터를 읽거나 씁니다 (예: TMemoryStream이나 TFileStream으로 로드/저장).
  4. 작업 완료 후 스트림 객체를 Free 합니다.

uses System.Classes, Data.DB; // DB 관련 유닛 필요

// 가정: MyQuery 라는 이름의 TDataSet 후손 컴포넌트가 있고,
//       'BlobData' 라는 이름의 BLOB 필드가 존재하며,
//       MyQuery가 활성 상태이고 레코드를 가리키고 있음.

procedure BlobStreamReadExample(Query: TDataSet; FieldName: string; TargetStream: TStream);
var
  BlobField: TBlobField;
  BlobStream: TStream; // TBlobStream은 TStream의 후손
begin
  BlobField := Query.FieldByName(FieldName) as TBlobField; // 필드 가져오기
  if BlobField = nil then Exit;

  // 읽기 모드로 BLOB 스트림 생성
  BlobStream := Query.CreateBlobStream(BlobField, bmRead);
  try
    // 대상 스트림(예: TMemoryStream)으로 BLOB 데이터 복사
    TargetStream.CopyFrom(BlobStream, BlobStream.Size);
    ShowMessage(FieldName + ' 필드 데이터를 읽었습니다. 크기: ' + IntToStr(TargetStream.Size));
  finally
    BlobStream.Free; // BLOB 스트림 해제
  end;
end;

procedure BlobStreamWriteExample(Query: TDataSet; FieldName: string; SourceStream: TStream);
var
  BlobField: TBlobField;
  BlobStream: TStream;
begin
  BlobField := Query.FieldByName(FieldName) as TBlobField;
  if BlobField = nil then Exit;

  // 쓰기 모드로 BLOB 스트림 생성 (먼저 Edit 상태여야 함)
  if not (Query.State in [dsEdit, dsInsert]) then
    Query.Edit; // 편집 모드로 전환

  BlobStream := Query.CreateBlobStream(BlobField, bmWrite);
  try
    // 원본 스트림(예: TMemoryStream) 내용을 BLOB 필드로 복사
    SourceStream.Position := 0; // 소스 스트림 위치 초기화
    BlobStream.CopyFrom(SourceStream, SourceStream.Size);
    ShowMessage(FieldName + ' 필드에 데이터를 썼습니다. 크기: ' + IntToStr(BlobStream.Size));
    // Query.Post; // 변경사항 저장 필요
  finally
    BlobStream.Free;
  end;
end;

// 사용 예시:
// var ms: TMemoryStream;
// begin
//   ms := TMemoryStream.Create;
//   try
//     // BLOB 필드 -> 메모리 스트림
//     BlobStreamReadExample(MyQuery, 'BlobData', ms);
//     // ... ms 내용을 사용 ...
//
//     // 메모리 스트림 -> BLOB 필드 (다른 레코드나 필드에 쓸 경우)
//     ms.Position := 0; // 쓸 때는 위치를 처음으로
//     BlobStreamWriteExample(MyOtherQuery, 'TargetBlobField', ms);
//   finally
//     ms.Free;
//   end;
// end;

        

참고: 실제 데이터베이스 연동 코드는 사용하는 데이터 접근 컴포넌트(FireDAC, ADO 등)와 데이터베이스 종류에 따라 세부적인 구현이 달라질 수 있습니다.

결론

델파이의 스트림 아키텍처는 다양한 데이터 소스를 일관된 방식으로 처리할 수 있게 해주는 강력한 기능입니다. TMemoryStream, TFileStream, TStringStream 등 목적에 맞는 스트림 클래스를 선택하여 사용하면 파일 I/O, 메모리 데이터 조작, 리소스 접근, 데이터베이스 BLOB 처리 등 복잡한 작업을 보다 쉽고 효율적으로 구현할 수 있습니다.