[그린컴퓨터] Server/JAVA(자바 JDK)

바이트 단위 스트림 { InputStream, OutputStream }

Ben의 프로그램 2023. 7. 5. 11:10
728x90

  • 입력 스트림의 계층은 위와 같다. 
  • 최상위 스트림은 InputStream 이다. 
  • FileInputStream 은 파일에서 바이트 단위로 자료를 읽으며 ByteArrayInputStream 은 배열 메모리(자료구조)에서 바이트 단위로 자료를 읽는다. 

  • 파일에서 데이터를 읽는 FileInputStream 을 확인해봅시다. 
package main;

import java.io.FileInputStream;
import java.io.IOException;

public class Ex4 {

	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("src/input.txt");
			
			while (true) {
				int i = fis.read(); // 1 바이트 읽기
				if(i == -1) {  // 파일의 끝에 도달하면 반복문 종료
					break;
				}
				System.out.println((char)i); // 문자 하나씩 출력
			}
		} catch (IOException e) { // Input Output 에서 발생하는 모든 예외를 한번에 처리하여 
//			try catch 문을 여러 번 작성하는 것을 방지했다.
			System.out.println(e);
		}
	}
}

  • while 문을 활용하여 문자 하나씩 출력하는 프로그램을 작성했습니다. 
  • try 첫 줄에서 우리는 입력 stream 을 만들었습니다. 입력 stream 을 만들었다는 것은 데이터를 읽고 쓰는 창구를 만들었다는 것을 의미합니다.

  • 위와 같이 input.txt 에 들어있는 값들이 문자 하나씩 출력되고 있는 것을 확인할 수 있습니다.  
  • 여기서 read( ) 메서드는 아스키 코드로 문자를 읽어오게 되는데, 이런 특성 때문에 읽어온 값을 int 변수에 담은 다음 문자로 출력할 때는 강제 형변환을 (char) 로 해주는 것을 확인할 수 있습니다. 

  • 그런데 만약 읽어오려는 파일이 영어가 아니라면 어떻게 될까요?

  • 이상한 문자가 출력된 것을 볼 수 있습니다. 이런 일이 벌어지는 이유는 한글은 한 글자가 2바이트를 차지하기 때문입니다. 그래서 byte 단위의 stream 을 사용하면 한글을 읽어올 수 없습니다. 이것이 바이트 단위 스트림의 단점입니다.
package main;

import java.io.FileInputStream;
import java.io.IOException;

public class Ex5 {

	public static void main(String[] args) {
		try {
			FileInputStream fis = new FileInputStream("src/input3.txt");
			byte[] arr = new byte[10];
			int i;
			
			while ((i = fis.read(arr)) != -1) {
				for (int k = 0; k < i; k++) {
					System.out.println((char) arr[k]);
				}
				System.out.println(": " + i + "바이트 읽음");
			}
		} catch (IOException e) {
			System.out.println(e);
		}
	}
}

  •  stream 은 단방향이라서 읽거나 쓰거나 한 가지만 할 수 있다. 위에서는 읽어오는 stream 으로 활용하고 있다. 
  • 코드를 보면 fis.read( ) 한 값을 arr 에 해당 배열의 크기만큼 넣어주고 배열에 넣은 값을 출력하는 코드이다. 

  • 위 코드에서 stream 이라고 불리는 이유를 확연히 느낄 수 있는 부분이 있다. 바로 while 구문에서인데, 처음 10개 byte 를 읽어와서 arr 배열에 대입을 해준 다음 for 구문이 실행되고 그 이후에 while 문의 조건문이 다시 실행되는데, 이때 직관적으로 이해하기로는 다시 실행된 것이므로 파일의 처음부터 다시 read() 를 실행할 것처럼 보이나, 그렇지 않고 마지막에 읽은 것 그 다음을 읽는 것을 볼 수 있는데, 이것은 stream 이 아직 close 되지 않았기 때문에 이어지고 있기 때문이다. 정말 말 그대로 stream 의 특성이라고 볼 수 있다. 

  • 위 fulsh 메서드는 출력할 때만 사용하는 메서드인데, 버퍼와 관련이 있다. 만약 데이터를 10번을 읽어야 한다면 각 10번을 읽으면 메모리가 비효율적이다. 그래서 버퍼에서 같은 파일을 여러번 읽는다면 버퍼에 저장을 하게 되는데, 버퍼도 용량의 한계가 있기 때문에 관리를 해주어야 한다. 버퍼가 꽉 차기 전에 데이터를 출력하고 싶으면 flush 를 사용하여 버퍼에 잔류된 데이터들을 우선적으로 보내주는 것을 수행하게 된다.

  • 데이터를 쓸 때는 숫자를 입력하고 숫자를 아스키코드의 문자로 변환하여 저장한다. 
package main;

import java.io.FileOutputStream;
import java.io.IOException;

public class Ex6 {

	public static void main(String[] args) {
		try {
			FileOutputStream fos = new FileOutputStream("src/output.txt");
			
			byte a = 65;
			byte b = 66;
			
			fos.write(a);
			fos.write(b);
			fos.write('c');
		} catch (IOException e) {
			System.out.println(e);
		}
	}
}

  • 쓰기(output) 코드를 작성해 보았다.
  • fileoutputStream 의 특징은 아스키 코드 값을 byte 변수에 담아서 주면 자동으로 문자로 치환해서 쓰기를 진행해준다. 
  • fileInputStream 과 마찬가지로 바이트 단위로 진행된다. 따라서 문자 3개를 쓰고 싶다면 write 메서드를 3번 사용해야한다.
package main;

import java.io.FileOutputStream;

public class Ex7 {

	public static void main(String[] args) {
		try {
			FileOutputStream fos = new FileOutputStream("src/output2.txt");
			byte[] arr = { 65, 66, 67 };
			
			fos.write(arr);  // 배열의 모든 데이터를 출력(쓰기)해준다. 
		} catch (Exception e) {
			System.out.println(e);
		}
	}
}

  • 이전에는 byte 하나 씩 파일에 썼다면 이번에는 byte 배열을 생성하여 write 의 인자로 넣어주었습니다. 
  • 이렇게 하면 배열의 배열 값들이 모두 파일에 쓰기(출력)가 진행되는 것을 볼 수 있습니다. 

연습문제 2

package quiz;

import java.io.FileOutputStream;
import java.io.IOException;


public class Quiz2 {

	public static void main(String[] args) {
		try {
			FileOutputStream fos = new FileOutputStream("src/quiz2.txt");
			byte[] arr = new byte[26];
			
			for (int i = 65, j = 0; i < 65 + 26; i++, j++) {
				 arr[j] = (byte) i;
			}
			fos.write(arr);
		} catch (IOException e) {
			System.out.println(e);
		}
	}
}

  • A~Z 까지 파일에 출력하는 코드인데, 더 간단하게 쓰는 방법이 있다. 
  • 우선 이런 코드를 처음부터 완벽하게 작성하는 것은 불가능하기 때문에, 하드코딩을 해보고 간단하게 만드는 방법으로 코드를 작성하면 훨씬 효율적으로 작성할 수 있다.
public class Quiz2 {

	public static void main(String[] args) {
		try {
			FileOutputStream fos = new FileOutputStream("src/quiz2.txt");
			byte[] arr = new byte[26];
			byte A = 65;
			for (int i = 0; i<26; i++) {
				 arr[i] = A;
				 A++;
			}
			fos.write(arr);
		} catch (IOException e) {
			System.out.println(e);
		}
	}
}
  • fos.write(65);
    fos.write(66); 
    ... 
    반복 
  • 위 처럼 하드코딩을 하면 어떻게 코드를 짤지 대략적인 틀이 보이게 되고  코드를 조금더 간단하고 보기 편하게 만들 수 있다.