뭉
노재능 록리형 개발자
뭉
전체 방문자
오늘
어제
  • 분류 전체보기 (27)
    • Java (18)
      • Grammer (14)
      • Problem Solving (4)
    • JavaScript (0)
      • Grammer (0)
      • jQuery (0)
    • Spring (0)
    • DB (9)
      • SQL (6)
      • JPA (3)
    • Storage (0)
    • ETC (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
뭉

노재능 록리형 개발자

입출력 스트림 (2) - File I/O
Java/Grammer

입출력 스트림 (2) - File I/O

2022. 5. 24. 00:25

  2022.05.22 - [Java/Grammer] - 입출력 스트림 (1) - System.out.println와 I/O

이전 시간에는 System.out.println을 통해 자바 입출력 스트림에 대한 개념들에 대해 알아보았습니다.

이전에 배운 내용을 토대로 이미지나 텍스트 데이터를 다루어 파일에 입출력하는 File I/O에 대해 알아보겠습니다.

오늘 우리의 목표는 원하는 디렉토리 위에서 여러 종류의 데이터를 다루는 연습을 하는 겁니다.

 

파일 객체 다루기

파일을 만들기 전 우리는 디렉토리(폴더)를 만들어야 합니다.

자바에선 File Class를 통해 파일을 다룰 수 있으며 해당 객체는 해당 경로에 실제 파일이 없더라도 에러가 나지 않습니다. 그렇다면 파일 객체는 무엇을 할수 있을까요?

public static void main(String[] args) throws IOException {
    File file = new File("C:\\practice\\ex\\fileTest.txt");
    File folder = file.getParentFile();   //파일의 상위 폴더 객체

    if(folder.mkdirs()) {   //디렉토리 생성                 
        System.out.println("새 폴더 생성.");
    }else {
        System.out.println("정상적인 구조입니다.");
    }
    
    try {
        file.createNewFile();   //파일 생성
    } catch (IOException e) {
        e.printStackTrace();
    }
}

 

파일 객체는 파일을 만들거나 지우고 정보를 얻는 등의 일을 할수 있습니다.

 

우선 위에서 해당 경로의 파일 객체를 만듭니다. 실제 파일이 있는지 여부는 상관없습니다.

하지만 파일을 만들 때 해당 파일을 담고있는 디렉토리(폴더)가 존재하지 않는다면 IOException이 뜨기에 우선 mkdirs 메서드를 사용해 폴더를 만들어 주어야 합니다.

 

mkdirs 메서드는 파일이 이미 존재한다면 아무 처리도 하지 않고 false를 반환하고 없다면 만들어 true를 반환합니다.

하지만 false를 반환하는 경우의 수는 생각보다 다양합니다. 가령 같은 이름의 파일이 이미 존재하거나 쓰기 권한이 없는 등의 경우에도 모두 false를 반환합니다. 

 

현재는 학습용으로 간단한 예제만을 다루었지만 실무운영의 경우 위에 대한 경우의 수들을 한번은 생각해보셔야 합니다.

 

 

이미지 데이터 다루기

이미지 데이터는 InputStream/OutputStream을 구현한 FileInputStream/FileOutputStream을 통해 다룹니다.

File image = new File("C:\\practice\\io\\image.png");
File newImage = new File("C:\\practice\\io\\newImage.png");

image.getParentFile().mkdirs();

try(
    BufferedInputStream in = new BufferedInputStream(new FileInputStream(image));
    BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(newImage));
    ) {
    byte[] readData = new byte[1024];    //데이터 저장변수

    while((in.read(readData)) != -1) {   //파일의 끝까지 읽음
        out.write(readData);             //읽은 데이터 출력
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    System.out.println("작업이 모두 끝났습니다.");	
}

혹시 해당 내용이 이해가 가질 않는다면 2022.05.22 - [Java/Grammer] - 입출력 스트림 (1) - System.out.println와 I/O 를 참고하시길 바랍니다.

FileInputStream/FileoutputStream의 특이한 점은 파일이 없다면 새로 만들어주고 없다면 덮어쓴다는 점입니다. 이에 따라 우리가 파일 객체를 만들어 미리 생성해주는 번거로운 작업이 필요없습니다.

 

여기에 FileInputStream/FileoutputStream를 매개변수로 받아 BufferedInputStream/BufferedOutputStream 객체를 생성한걸 보실수 있을겁니다.

이들은 버퍼를 사용한 속도향상을 위한 보조스트림으로 데이터를 임시로 버퍼에 모아두었다 한번에 입출력(flush)하는 방식을 사용합니다. 

 

그리고 자세히 살펴보면 특이하게도 위 소스는 연결해제를 해주는 close가 보이지가 않고 일반적인 try-catch와 다른 특이한 생김새인걸 보실 수 있을 겁니다.

해당 구문은 try-with-resources로 I/O 작업이 끝나면 명시하지 않아도 자동으로 close 메서드를 실행해줍니다.

이때 close 메서드 실행 전 버퍼를 비우는 flush 메서드를 호출하는 점도 알아두시길 바랍니다.

 

 

문자열 데이터 다루기

이번에는 좀 다르게 콘솔에 입력한 내용을 파일에 쓰고, 그 내용을 다시 읽어 콘솔에 출력하는 프로그램을 만들어 봅시다.

File file = new File("c:\\practice\\io\\ex.txt");
		
file.getParentFile().mkdirs();

try (
    Scanner sc = new Scanner(System.in);
    PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file, StandardCharsets.UTF_8)));
    BufferedReader in = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8));
    ){
    String text = sc.nextLine();   
    String line = "";              //데이터 저장 변수
    StringBuilder result = new StringBuilder();   //모든 line 데이터를 저장할 곳

    out.println(text);             //파일에 출력

    while((line = in.readLine()) != null) {   //다음줄이 없을 때까지 읽음
        result.append(line);                  //읽은 데이터 저장
        System.out.println(result);           //콘손에 출력
    }
}catch (IOException e) {
    e.printStackTrace();
} finally {
    System.out.println("작업이 모두 끝났습니다.");
}

문자열은 최상위 메인스트림인 Writer/Reader를 구현한 FileWriter/FileReader 를 통해 다룹니다. 이들 또한 파일이 없다면 생성하고 있다면 내용을 덮어버립니다.

또한 위 메인스트림들은 바이트 단위가 아닌 문자 단위로 데이터를 읽습니다. 이때 생성자로 File과 CharSet 타입을 받아

UTF-8로 인코딩을 지정할 수 있습니다.

 

이를 도와줄 보조스트림은 편리한 출력 메서드를 제공하는 PrintWriter와 성능 향상 목적 BufferedWriter/BuffredReader를 사용했습니다.

여기서는 read가 아닌 readLine이란 메서드를 사용하는데 이는 줄단위로 데이터를 읽습니다.

 

아니...줄 단위로 읽는데 읽는 데이터 크기를 지정할 수 있는 read보다 느린거 아니야? 라고 생각하실수 있겠습니다.

이 부분에 대해 벤치마크 결과나 각종 구글링을 통해 알아본 결과 파일이 커질수록 readLine이 오히려 좀 더 빠르다는 결과를 얻었습니다.

버퍼 스트림인 만큼 줄바꿈에 대한 최적화가 되있는 것으로 추정되나 신기할 따름입니다.

 

참고로 PrintWriter의 생성자에는 autoflush를 지원하는 boolean 타입의 매개변수를 받는데 이를 사용하면 print, println, ...등등의 등록된 매서드 사용 시 버퍼를 비우게 됩니다.

대부분의 경우 성능상 좋지 않으니 사용하지 않는 걸 권합니다.

 

 

 

 

참고

https://stackoverflow.com/questions/32324492/is-flush-call-necessary-when-using-try-with-resources  

https://javacan.tistory.com/entry/43  

https://stackoverflow.com/questions/908168/when-to-flush-a-bufferedwriter  

https://stackoverflow.com/questions/32177690/is-printwriter-buffered

https://stackoverflow.com/questions/32324492/is-flush-call-necessary-when-using-try-with-resources  

https://stackoverflow.com/questions/7708390/performance-when-reading-a-file-line-by-line-vs-reading-the-whole-file   

https://stackoverflow.com/questions/7939390/java-reading-a-file-performance-difference-between-byte-and-character-streams

https://stackoverflow.com/questions/11372546/printstream-vs-printwriter

'Java > Grammer' 카테고리의 다른 글

Optional의 옳바른 사용법  (0) 2022.06.12
Stream API 정리  (0) 2022.06.04
입출력 스트림 (1) - System.out.println와 I/O  (0) 2022.05.22
ShallowCopy와 DeepCopy 완전 정복  (0) 2022.05.07
객체의 중복/정렬 그리고 Collection  (0) 2022.04.24
    'Java/Grammer' 카테고리의 다른 글
    • Optional의 옳바른 사용법
    • Stream API 정리
    • 입출력 스트림 (1) - System.out.println와 I/O
    • ShallowCopy와 DeepCopy 완전 정복
    뭉
    뭉
    노재능 록리형 개발자

    티스토리툴바