소소한 일상과 잡다한 정보

IT/Dart

Dart_10일차: 파일 입출력 (File I/O) & 스트림 (Stream) 활용

pandada 2025. 3. 9. 16:49
반응형


 이번에는 저번에 맛보기로 진행했던 파일 입출력(File I/O)과 스트림(Stream)을 확인보자.

  • 파일을 읽고( readAsString() ), 쓰고( writeAsString() ) 수정하는 방법
  • 스트림( Stream )을 사용하여 대용량 파일을 효율적으로 처리하는 방법

1. 파일 쓰기 ( writeAsString() , writeAsBytes() )

 Dart에서는 dart:io 라이브러리를 사용하여 파일을 생성하고 데이터를 저장할 수 있다.

 ✔️ 문자열을 파일에 저장 ( writeAsString() )

import 'dart:io';

void main() async {
  File file = File('test.txt'); // 파일 객체 생성

  await file.writeAsString('Hello, Dart! 파일 입출력 테스트', mode: FileMode.write);

  print("✅ 파일 저장 완료: ${file.path}");
}
  • writeAsString() → 파일에 문자열을 저장
  • mode: FileMode.write → 기존 파일 내용을 덮어씀

2.  파일 읽기 ( readAsString() , readAsLines() )

 파일에 저장된 데이터를 읽을 수도 있다.

 ✔️ 파일 읽기 ( readAsString() )

import 'dart:io';

void main() async {
  File file = File('test.txt');

  if (await file.exists()) {
    String content = await file.readAsString();
    print("📄 파일 내용: $content");
  } else {
    print("⚠️ 파일이 존재하지 않습니다.");
  }
}
  • readAsString() → 파일의 모든 내용을 읽어 문자열로 반환
  • 파일이 존재하지 않을 경우 예외를 방지하기 위해 exists()로 확인

3. 파일에 여러 줄 저장 & 읽기 ( writeAsLines(), readAsLines() )

 여러 줄 데이터를 저장하고 읽을 수도 있다.

 ✔️ 여러 줄 저장 ( join('\n') 사용  )

import 'dart:io';

void main() async {
  File file = File('lines.txt');

  List<String> lines = [
    '첫 번째 줄',
    '두 번째 줄',
    '세 번째 줄'
  ];

  await file.writeAsString(lines.join('\n')); // 리스트를 문자열로 변환하여 저장

  print("✅ 여러 줄 파일 저장 완료");
}
  • join('\n') → 리스트를 문자열로 변환하여 파일에 저장

 

 ✔️ 여러 줄 읽기 (split('\n') 사용)

import 'dart:io';

void main() async {
  File file = File('lines.txt');

  if (await file.exists()) {
    String content = await file.readAsString();
    List<String> lines = content.split('\n'); // 줄 단위로 나누기

    print("📄 파일 내용:");
    for (var line in lines) {
      print(line);
    }
  } else {
    print("⚠️ 파일이 존재하지 않습니다.");
  }
}
  • split('\n') → 문자열을 줄 단위 리스트로 변환

 

 ✔️ 기존 파일에 내용 추가 (append 모드)

import 'dart:io';

void main() async {
  File file = File('lines.txt');

  List<String> newLines = [
    '네 번째 줄',
    '다섯 번째 줄'
  ];

  await file.writeAsString('\n' + newLines.join('\n'),
      mode: FileMode.append); // 기존 내용 뒤에 추가

  print("✅ 파일에 새로운 줄 추가 완료");
}
  • FileMode.append → 기존 내용 뒤에 새로운 데이터를 추가
  • '\n' + newLines.join('\n') → 기존 줄과 새 줄 사이에 줄바꿈 추가

4. 파일 존재 여부 확인 & 삭제 ( exists(), delete() )

파일이 존재하는지 확인하고 삭제할 수도 있다.

 ✔️ 파일 존재 여부 확인

import 'dart:io';

void main() async {
  File file = File('test.txt');

  if (await file.exists()) {
    print("✅ 파일이 존재합니다.");
  } else {
    print("⚠️ 파일이 존재하지 않습니다.");
  }
}

 ✔️ 파일 삭제 (delete())

import 'dart:io';

void main() async {
  File file = File('test.txt');

  if (await file.exists()) {
    await file.delete();
    print("🗑 파일 삭제 완료");
  } else {
    print("⚠️ 삭제할 파일이 없습니다.");
  }
}
  • delete() → 파일을 삭제
  • 파일이 존재하지 않으면 예외를 방지하기 위해 exists() 확인

5. 스트림(Stream) 활용 - 대용량 파일 처리

  • 파일을 한 번에 읽으면 메모리 사용량이 많아질 수 있다
  • 스트림( Stream )을 사용하면 데이터를 한 줄씩 읽으며 메모리를 효율적으로 관리 가능

 ✔️ 스트림을 이용한 파일 읽기 ( openRead() )

import 'dart:io';
import 'dart:convert';

void main() async {
  File file = File('large_file.txt');

  if (await file.exists()) {
    Stream<List<int>> inputStream = file.openRead(); // 스트림 열기

    inputStream
        .transform(utf8.decoder) // 바이트를 문자열로 변환
        .transform(LineSplitter()) // 줄 단위로 나누기
        .listen((line) {
      print("📄 읽은 줄: $line");
    }, onDone: () {
      print("✅ 파일 읽기 완료");
    }, onError: (e) {
      print("⚠️ 오류 발생: $e");
    });
  } else {
    print("⚠️ 파일이 존재하지 않습니다.");
  }
}
  • openRead() → 파일을 스트림으로 열기
  • transform(utf8.decoder) → 바이트 데이터를 문자열로 변환
  • transform(LineSplitter()) → 한 줄씩 처리

 

 ✔️트림(Stream)으로 파일 쓰기 ( openWrite() )

import 'dart:io';

void main() async {
  File file = File('stream_output.txt');

  // 스트림을 이용한 파일 쓰기 (기존 파일 내용 덮어쓰기)
  IOSink sink = file.openWrite(mode: FileMode.write);

  List<String> lines = [
    '첫 번째 줄 - 스트림 사용',
    '두 번째 줄 - 파일 쓰기',
    '세 번째 줄 - 효율적인 데이터 저장'
  ];

  for (var line in lines) {
    sink.writeln(line); // 한 줄씩 쓰기
  }

  await sink.flush(); // 버퍼 비우기
  await sink.close(); // 스트림 닫기

  print("✅ 스트림을 이용한 파일 쓰기 완료");
}
  • openWrite(mode: FileMode.write) → 파일을 쓰기 모드로 스트림 열기
  • sink.writeln(line) → 한 줄씩 데이터 추가 (writeln()은 자동 줄바꿈)
  • await sink.flush();버퍼를 비워 데이터 즉시 저장
  • await sink.close();파일을 닫아 메모리 해제
반응형

 

✔️ 테스트 예제 코드 1

 위에서 진행했던 내용을 토대로 충분한 실습을 진행한 후, 테스트 예제를 진행해보도록 하자.

 1. 사용자 입력을 받아 파일에 저장하는 프로그램

 2. 사용자가 입력한 내용을 파일(user_input.txt)에 저장하는 프로그램

 3. 여러 줄 입력 가능 (q 입력 시 종료)

 4. 기존 내용을 유지하면서 새로운 내용 추가 (FileMode.append)

 5. 입력값이 공백("")이면 다시 입력받도록 유효성 검사 포함

import 'dart:io';

void main() async {
  File file = File('user_input.txt'); // 파일 객체 생성
  IOSink sink = file.openWrite(mode: FileMode.append); // 기존 내용 유지하며 추가

  print("📝 텍스트를 입력하세요. (종료하려면 'q' 입력)");

  while (true) {
    stdout.write("> ");
    String? input = stdin.readLineSync()?.trim(); // 입력값 앞뒤 공백 제거

    // 공백 입력 방지
    if (input == null || input.isEmpty) {
      print("⚠️ 입력이 비어 있습니다. 다시 입력하세요.");
      continue;
    }

    // 'q' 입력 시 종료
    if (input.toLowerCase() == 'q') {
      print("🚪 입력 종료. 파일에 저장 완료!");
      break;
    }

    sink.writeln(input); // 한 줄씩 파일에 추가
  }

  await sink.flush(); // 버퍼 비우기
  await sink.close(); // 스트림 닫기
}
  • File('user_input.txt') → 파일 객체 생성
  • openWrite(mode: FileMode.append) → 기존 내용 유지하면서 데이터 추가
  • stdin.readLineSync() → 사용자 입력 받기
  • input.toLowerCase() == 'q' → q 입력 시 종료
  • sink.writeln(input); → 입력된 내용을 한 줄씩 파일에 추가
  • sink.flush(); & sink.close(); → 버퍼 비우고 파일 닫기

 

✔️ 테스트 예제 코드 2

 1. 파일에서 데이터를 읽어와서 출력하는 프로그램

 2. 사용자가 입력한 파일(user_input.txt)을 읽고 출력
 3. 파일이 존재하지 않으면 경고 메시지를 출력
 4. 한 줄씩 데이터를 읽어서 출력 (split('\n'))

 5. 파일이 클 경우, openRead()(스트림)를 사용하여 한 줄씩 읽도록 개선 가능
 6. 파일이 존재하지 않을 때 새 파일을 생성하도록 수정 가능

import 'dart:io';
import 'dart:convert';

void main() async {
  File file = File('user_input.txt'); // 읽을 파일 지정

  // 파일이 존재하지 않으면 자동 생성
  if (!await file.exists()) {
    await file.writeAsString("기본 데이터입니다.\nDart 파일 입출력 예제\n", mode: FileMode.write);
    print("🆕 파일이 없어서 새로 생성되었습니다!");
  }

  print("📄 파일 내용:");

  // 스트림을 활용하여 한 줄씩 파일 읽기
  Stream<List<int>> inputStream = file.openRead();
  inputStream
      .transform(utf8.decoder) // 바이트를 문자열로 변환
      .transform(LineSplitter()) // 한 줄씩 나누기
      .listen((line) {
        if (line.trim().isNotEmpty) { // 빈 줄 제외
          print(line);
        }
      }, onDone: () {
        print("✅ 파일 읽기 완료");
      }, onError: (e) {
        print("⚠️ 오류 발생: $e");
      });
}

 

✔️ 테스트 예제 코드 3

 1. 스트림(Stream) 활용 + 파일 자동 생성 + 특정 키워드 필터링 기능.

 2. 대용량 파일(large_file.txt)을 스트림으로 한 줄씩 읽기
 3. 파일이 없으면 기본 데이터를 포함한 새 파일을 자동 생성
 4. 사용자가 원하는 키워드가 포함된 줄만 출력 가능 (filterKeyword)
 5. 메모리를 절약하면서 효율적으로 파일 읽기

 6. openRead() + utf8.decoder + LineSplitter() 조합으로 효율적인 파일 처리

 7. 읽기 완료 시 메시지 출력 & 오류 발생 시 예외 처리

import 'dart:io';
import 'dart:convert';

void main() async {
  File file = File('large_file.txt'); // 파일 지정

  // 파일이 존재하지 않으면 기본 내용 포함하여 자동 생성
  if (!await file.exists()) {
    await file.writeAsString(
      "첫 번째 줄 - 스트림 사용\n"
      "두 번째 줄 - 대용량 데이터 처리\n"
      "세 번째 줄 - 메모리 절약\n"
      "네 번째 줄 - 효율적인 파일 읽기\n"
      "다섯 번째 줄 - 키워드 필터링 지원\n",
      mode: FileMode.write,
    );
    print("🆕 파일이 없어서 새로 생성되었습니다!");
  }

  print("📄 대용량 파일 읽기 시작...");

  String filterKeyword = "키워드"; // 특정 키워드가 포함된 줄만 출력
  Stream<List<int>> inputStream = file.openRead(); // 스트림 열기

  inputStream
      .transform(utf8.decoder) // 바이트 데이터를 문자열로 변환
      .transform(LineSplitter()) // 한 줄씩 나누기
      .listen((line) {
        if (line.trim().isNotEmpty) { // 빈 줄 제외
          if (line.contains(filterKeyword)) { // 특정 키워드 포함된 줄만 출력
            print("🔍 필터링된 줄: $line");
          } else {
            print("📄 읽은 줄: $line");
          }
        }
      }, onDone: () {
        print("✅ 대용량 파일 읽기 완료!");
      }, onError: (e) {
        print("⚠️ 오류 발생: $e");
      });
}
  • 파일이 없으면 기본 데이터를 포함한 새 파일을 자동 생성
  • 대용량 파일도 스트림(openRead())으로 한 줄씩 읽어 효율적으로 처리
  • trim().isNotEmpty를 사용하여 빈 줄 출력 방지
  • 특정 키워드(filterKeyword)가 포함된 줄만 따로 출력

 


 이렇게 파일 입출력 (File I/O) & 스트림 (Stream) 활용에 대해서 알아보았다. 추가 적인 내용이 필요한 경우에는 댓글을 요청드리고, 틀린 부분이 있다면 이것 또한 댓글로 알려주시면 수정하도록 하겠습니다!


 

반응형