Java

[Java] 입출력(I/O), 스트림, 버퍼

공대생안씨 2024. 7. 28. 22:43

1. Java 입출력

  • 자바 ⇒ 모든 I/O가 스트림(Stream)을 통해 이루어짐
  • 스트림 : “데이터의 흐름” (데이터 입출력 시 데이터가 이동하는 통로)
📌  java.io 패키지 ⇒ 다양한 입출력 스트림 클래스 제공
                                 크게 바이트 기반 스트림과 문자 기반 스트림으로 나눌 수 있음

바이트 기반 입출력 스트림 : 그림, 멀티미디어, 문자 등 모든 종류의 데이터들을 주고받을 수 있음
문자 기반 입출력 스트림 : 오로지 문자만 주고받을 수 있게 특화

 

1-1. 바이트 기반 스트림

  • 최상위 클래스
    • InputStream, OutputStream 존재
    • 둘 다 추상 클래스 ⇒ 상속받는 하위 클래스 통해서 구현
    • 하위 클래스 : XXXInputStream, XXXOutputStream
      • 입력 ⇒ ex) FileInputStream, BufferedInputStream, DataInputStream
      • 출력 ⇒ ex) FileOutputStream, BufferedOutputStream, DataOutputStream, PrintStream

 

1-1-1. 바이트 입력 스트림 : InputStream

  • read()
    • 입력 스트림으로부터 바이트 단위로 값을 읽음
    • ASCII 코드 반환 (int형)
    • 바이트가 없으면 -1 반환
public static void main(String[] args) throws FileNotFoundException {
    String filePath = System.getProperty("user.dir") + "/src/week4/ch4_1/input.txt";
    // try-with-resources : inputStream.close() 생략가능!
    try (InputStream inputStream = new FileInputStream(filePath)) {
        while(true) {
            int readData = inputStream.read();
            if(readData == -1) {
                System.out.println("끝");
                break;
            }
            System.out.println("readData = " + readData);
            System.out.println("(char)readData = " + (char) readData);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}


// input.txt 에는 "hello~" 가 저장되어 있음
/* 실행결과
readData = 104
(char)readData = h
readData = 101
(char)readData = e
readData = 108
(char)readData = l
readData = 108
(char)readData = l
readData = 111
(char)readData = o
readData = 126
(char)readData = ~
끝
*/
  • read(byte[] b)
    • 입력 스트림으로부터 읽어온 데이터를 (파라미터로 넘긴) 바이트 배열에 저장함
    • 읽은 바이트 수를 반환함
public static void main(String[] args) throws FileNotFoundException {
    String filePath = System.getProperty("user.dir") + "/src/week4/ch4_1/input.txt";
    
    try(InputStream inputStream = new FileInputStream(filePath)) {
        byte[] byteArr = new byte[20];
        int read = inputStream.read(byteArr); // 반환값 : 읽은 바이트 수
        System.out.println("read = " + read);

				// 인덱스 0~read 까지 byteArr 배열의 값을 String 객체로 생성
        System.out.println("String(byteArr) = " + new String(byteArr,0,read));

    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

// input.txt 에는 "hello~" 가 저장되어 있음
/* 실행결과
read = 6
String(byteArr) = hello~
*/
  • read(byte[] b, int off, int len)
    • 입력 스트림으로부터 읽어 온 데이터를 바이트 배열에 저장
      • 이때, 배열의 offset 인덱스부터 저장
      • len 만큼만 읽어옴
    • read(byte[] b) 와 동일하게 읽어온 바이트 수 반환
public static void main(String[] args) throws FileNotFoundException {
    String filePath = System.getProperty("user.dir") + "/src/week4/ch4_1/input.txt";

    try(InputStream inputStream = new FileInputStream(filePath)) {
        byte[] byteArr = new byte[10];
        int read = inputStream.read(byteArr, 2, 4);
        System.out.println("read = " + read);

        System.out.println("String(byteArr) = " + new String(byteArr));
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
// input.txt 에는 "hello~" 가 저장되어 있음
/* 실행결과
read = 4
String(byteArr) = xxhellxxxx ( <= 여기서 x는 없음을 의미)
*/

 

1-1-2. 바이트 출력 스트림 : OutputStream

  • write(int b)
    • 출력 스트림으로 단일 바이트 출력
    • 파라미터가 int ⇒ int 형 데이터를 넘기면 마지막 1바이트만 전송됨
public static void main(String[] args) {
    String filePath = System.getProperty("user.dir") + "/src/week4/ch4_1/output.txt";

    try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
        String outputMessage = "OutputStream Example~";
        byte[] bytes = outputMessage.getBytes();
        for (byte b : bytes) {
            outputStream.write(b);
        }
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
/* 실행결과
(output.txt 가 없다면 해당 경로에 output.txt가 생성되고)
OutputStream Example~ 이 출력됨
*/
  • write(byte[] b)
    • 바이트 배열 b를 출력 스트림 통해서 출력함
public static void main(String[] args) {
    String filePath = System.getProperty("user.dir") + "/src/week4/ch4_1/output.txt";
    
    try(FileOutputStream outputStream = new FileOutputStream(filePath)) {
        byte[] bytes = "OutputStream Example 2 !!".getBytes();
        outputStream.write(bytes);
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
/* 실행결과
(output.txt 가 없다면 해당 경로에 output.txt가 생성되고)
OutputStream Example 2 !! 이 출력됨
*/
  • write(byte[] b, int off, int len)
    • 바이트 배열 b에서 off 인덱스부터 len 개수만큼의 바이트를 출력 스트림 통해서 출력함
public static void main(String[] args) {
    String filePath = System.getProperty("user.dir") + "/src/week4/ch4_1/output.txt";

    try(FileOutputStream outputStream = new FileOutputStream(filePath)) {
        byte[] bytes = "OutputStream Example 3 !!".getBytes();
        outputStream.write(bytes, 13, 9); // "Example 3"
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
/* 실행결과
(output.txt 가 없다면 해당 경로에 output.txt가 생성되고)
Example 3 이 출력됨
*/

 

1-2. 문자 기반 스트림

  • 최상위 클래스
    • Reader, Writer 존재
    • 둘 다 추상 클래스 ⇒ 상속받는 하위 클래스 통해서 구현
    • 하위 클래스 : XXXReader, XXXWriter
      • 입력 ⇒ ex) FileReader, BufferedReader, InputStreamReader
      • 출력 ⇒ ex) FileWriter, BufferedWriter, OutputStreamWriter, PrintWriter

 

1-2-1. 문자 입력 스트림 : Reader

  • read()
    • 입력 스트림으로부터 단일 문자 단위로 값을 읽음
    • 단일 문자 ⇒ 2바이트 의미
    • 값이 존재하지 않으면 -1 반환
📌 InputStream의 read() vs Reader의 read()

- InputStream의 read() : 바이트 단위로 값을 읽어옴
- Reader의 read() : 단일 문자(2바이트)로 값을 읽어옴

⇒ InputStream의 read()는 한글이 깨지는 현상 발생
    Reader의 read()는 2바이트로 읽어오므로 한글이 깨지지 않음!
public static void main(String[] args) {
    String filePath = System.getProperty("user.dir") + "/src/week4/ch4_1/input.txt";
    try (FileReader reader = new FileReader(filePath)){
        while(true) {
            int read = reader.read();
            if(read == -1) break;
            System.out.println("read = " + read);
            System.out.println("(char)read = " + (char) read);
        }



    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
// input.txt => "hello~" 인 경우
/* 실행결과 (InputStream의 read() 와 동일함)
read = 104
(char)read = h
read = 101
(char)read = e
read = 108
(char)read = l
read = 108
(char)read = l
read = 111
(char)read = o
read = 126
(char)read = ~
*/

// input.txt => "안녕하세요!" 인 경우
/* 실행결과 (InputStream의 read()와는 다르게 한글이 깨지지 않음!)
read = 50504
(char)read = 안
read = 45397
(char)read = 녕
read = 54616
(char)read = 하
read = 49464
(char)read = 세
read = 50836
(char)read = 요
read = 33
(char)read = !
*/
  • read(char[] cbuf)
    • 읽어온 데이터를 파라미터로 넘긴 cbuf (char 배열)에 저장함
    • 읽어온 단일문자의 수 반환
public static void main(String[] args) {
    String filePath = System.getProperty("user.dir") + "/src/week4/ch4_1/input.txt";
    try (FileReader reader = new FileReader(filePath)){

        char[] cbuf = new char[20];
        System.out.println("reader.read(cbuf) = " + reader.read(cbuf));
        System.out.println("Arrays.toString(cbuf) = " + Arrays.toString(cbuf));


    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
// input.txt => "안녕하세요!" 인 경우
/* 실행결과
reader.read(cbuf) = 6
Arrays.toString(cbuf) = [안, 녕, 하, 세, 요, !, , , , ]
*/

 

1-2-2. 문자 출력 스트림 : Writer

  • write(int c), write(char[] cbuf), write(char[] c, int off, int len)
    위의 메서드 들과 동일
  • write(String str)
    • 편의상 char 배열이 아닌 String 타입도 가능!
public static void main(String[] args) {
    String filePath = System.getProperty("user.dir") +  "/src/week4/ch4_1/output.txt";
    try (FileWriter writer = new FileWriter(filePath)){
        String content = "hello!\n안녕하세요!";

        writer.write(content);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
/* 실행결과
(output.txt 가 없다면 해당 경로에 output.txt가 생성되고)
hello!
안녕하세요!
*/
  • write(String str, int off, int len)
    • String 타입 (문자열) 에서 offset 인덱스부터 length 만큼의 문자열을 출력 스트림 통해서 출력함
public static void main(String[] args) {
    String filePath = System.getProperty("user.dir") +  "/src/week4/ch4_1/output.txt";
    try (FileWriter writer = new FileWriter(filePath)){
        String content = "hello!\n안녕하세요!";

        writer.write(content,0,6);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
/* 실행결과
(output.txt 가 없다면 해당 경로에 output.txt가 생성되고)
hello!
*/

 

2. 버퍼 (Buffer)

  • 버퍼 : 데이터를 임시로 저장하는 메모리 공간
📌 버퍼는 입력받은 값을 임시로 저장하고
      버퍼에 값이 가득차거나 개행문자가 나타나면 버퍼의 내용을 한 번에 전송함!!

바로바로 입력값을 전달하는 것이 아니라 모아두었다가 한 번에 전송하기 때문에 속도가 빠름!

 

2-1. 버퍼 주의사항

  1. Scanner ⇒ 띄어쓰기, 개행문자를 기준으로 입력 값 구분
    버퍼 ⇒ 오직 개행문자만을 기준으로 입력 값 구분
    따라서 버퍼 사용시에는 띄어쓰기는 데이터 가공으로 직접 구분해야함!
  2. 버퍼로 입력받은 값 ⇒ 기본타입: String
    따라서 원하는 타입 (ex. int, double) 등으로 형변환 해야함!
  3. IOException 에 대한 처리 필수

 

2-2. 버퍼 스트림 사용법

public static void main(String[] args) {

  String fileInputPath = System.getProperty("user.dir") +  "/src/week4/ch4_1/input.txt";
  String fileOutputPath = System.getProperty("user.dir") +  "/src/week4/ch4_1/output.txt";
  try (BufferedReader bufferedReader = new BufferedReader(new FileReader(fileInputPath));
       BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(fileOutputPath))) {

      String inputString = bufferedReader.readLine();
      // 띄어쓰기로 구분
      String[] stringArr = inputString.split(" ");
      for (String s : stringArr) {
          bufferedWriter.write(s + "\n");
      }

  } catch (FileNotFoundException e) {
      throw new RuntimeException(e);
  } catch (IOException e) {
      throw new RuntimeException(e);
  }
}
// input.txt : "버퍼스트림 예제 input" 저장되어 있을 때
/* 실행결과 (output.txt 에 아래와 같이 출력됨)
버퍼스트림
예제
input
*/