본문 바로가기

Java/Study

[JAVA]백기선 라이브 스터디 13주차 JAVA I/O

목표

자바의 Input과 Ontput에 대해 학습하세요.

학습할 것 (필수)

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기(FileInputStream, FileOutputStream)

출처 : https://story.stevenlab.io/98?category=474123

스트림(Stream) 

컴퓨터의 내부 또는 외부의 장치와 프로그램 간의 데이터를 주고 받는 것을 의미한다.

뜻 그대로 데이터의 흐름이라고 생각하면 된다.

 

→ 단방향 통신 ( 입력 , 출력시 두 개의 Stream이 필요)

→ 먼저 보낸 데이터를 먼저 받는 FIFO(First In First Out)구조 

 

단점 : 커널 영역에서 프로세스 영역 안의 버퍼로 데이터를 복사할 때 CPU 사용(다른 resource도 활용)

          디스크 컨트롤러에서 커널 영역으로 데이터를 복사 할 때 프로세스 영역 블로킹 현상 발생

          복사에 활용한 버퍼도 GC의 대상이 됨

NIO는 이런 IO 의 단점을 보완하기 위해 만들어졌다.

버퍼(Buffer)

(백기선님 피드백)

단순하게 버퍼를 사용해서 성능상 이점이 생기는 것은 아님!

왜 더 빨라질까 os 레벨에 있는 시스템 콜 횟수 자체를 줄기 때문에 성능이 빨라진다. ex) 한 바이트 씩 네번 vs 한 번에 모아서 가져옴

 

1.4 이전 버전에서 모든 메모리는 힙 영역에서 담당했는데 시스템 메모리를 직접 사용할 수 있는 Buffer 클래스가 도입되었다.

버퍼의 활용 : 다양한 예제가 있는데 (allocate(), allocatedirect(),wrapping(), ..)

'랩핑' 

  이미 출력하고자 하는 데이터의 배열을 가지고 있다면 버퍼를 새로 만들고 채우는 것 보다 배열을 버퍼로 감싸면 된다.

byte[] date = sojungdata.get("UTF_8");
ByteBuffer buffer = ByteBuffer.wrap(data);

 

버퍼 데이터를 읽고 쓰는 방법  ; put(), get()

 

채널 (Channel)

  • 입력과 출력을 동시에 수행한다.
  • Selector와 연결되어 있고, 하나에 Selector에는 다수의 채널이 존재할 수 있다.
  • Blocking 된 스레드를 깨우거나, 다시 Blocking할 수 있다. 

더 깊은 내용들이 많은데 네트워크 프로그래밍적 요소가 많아서 여기까지만 정리한다.


InputStream 과 OutputStream

대표적인 Byte Stream 의 종류

원래 모든 데이터는 Byte 단위 (8bit의 이진 비트를 묶은 것) 로 구성되어 있다.(그림, 텍스트, zip, jar모두 바이트 단위)

이를 적절히 변환하면 의미있는 데이터가 되는 것이다.

즉, 원시 byte를 그대로 주고 받겠다는 의미이다.

 

대상의 유형에 따라 여러 종류의 입출력 타입이 있다.

 

InputStream OutputStream type
FileInputStream FileOutputStream 파일
ByteArrayInputStream ByteArrayOuputStream 메모리 (byte 배열)
PipedInputStream PipedOutputStream 프로세스
AudioInputStream AudioOutputStream 오디오

 

이들은 모두 InputStream, OutputStream 의 자손들이며, 각각을 읽고 쓰는데 필요한 추상 메서드를 자신에 맞게 구현 한다.

 

InputStream OutputStream
abstract int read() (** return 0~255 -1 이기 때문) abstract void write(int b)
void read(byte[] b) void write(byte[] b)
void read(byte[] b, int off, int len) void write(byte[] b, int off, int len)

 


System class (표준 입출력 클래스)

- JVM을 구성하고 있는 표준 장치 -독립적으로 동작할 수 있는 구조

- in 표준 입력, out 표준 출력, err 표준 에러 출력

- System.in 콘솔로부터 데이터를 입력 받을 때 사용

InputStream의 형태로 데이터를 입력 받음

 

public class DemoSystemClass {

    public static void main(String[] args) throws IOException {
        System.out.println("1. 여름");
        System.out.println("2. 겨울");
        System.out.println("3.. 가을");
        System.out.println("좋아하는 계절은 ?");
        InputStream comment = System.in;
        char inputchar = (char)comment.read();// 한글은 인식을 못함
        System.out.println(inputchar);
    }
}

- System.out과  System.err 차이 : 서로 다른 flush() 타이밍

System.out 자신만의 버퍼를 갖는데 print requests를 모으고 있다가 한번에 flush() - 성능 향상의 효과

System.err 크리티컬한 오류를 추적하기 위해 존재하므로 바로바로 flush() 함 - 성능이 느려짐

System.out vs System.err 

동시에 있으면 Sysrem.err 가 먼저 보여지게 된다고 한다.

 

로그의 레벨을 나누는 라이브러리를 사용하거나 Out 만 쓴다고 한다,,

?

로그의 레벨? -> 로그를 단계를 나눠서 남긴다는 의미 같은데 사용해 보진 않아서 잘 모르겠다.

대표적인

log4j

# TRACE : 추적 레벨은 Debug보다 좀더 상세한 정보를 나타냄
# DEBUG : 프로그램을 디버깅하기 위한 정보 지정
# INFO :  상태변경과 같은 정보성 메시지를 나타냄 
# WARN :  처리 가능한 문제, 향후 시스템 에러의 원인이 될 수 있는 경고성 메시지를 나타냄 
# ERROR :  요청을 처리하는 중 문제가 발생한 경우
# FATAL :  아주 심각한 에러가 발생한 상태, 시스템적으로 심각한 문제가 발생해서 어플리케이션 작동이 불가능할 경우

[출처] logger.info()와 .debug() 의 차이|작성자



출처: https://mdj1234.tistory.com/63 [짬타이거 화이팅!]

 


Byte와 Character Stream

byte단위라 함은 입출력의 단위가 1 byte라는 것이고,

문자가 C언어는 1byte, Java에서는 2byte라서 스트림으로 처리하기 어렵다.

이를 보완하기 위한 Stream이 Character Stream 이다.

InputStream -> Reader
OutputStream ->Wrtiter

둘다 처음에 Byte단위로 문자를 받는다. 나머지는 Stream 내부적으로 알아서 처리해 준다. (8bit 단위로 읽을지 16 bit 단위로 읽을지 등)

결론 적으로 Stream은 입력과 출력을 도와주는 중간 매개체로서의 역할을 한다.

코드는 byteStream을 살펴 보자

 

 public static void main(String[] args) 
 {
        byte[] inSrc = {1,2,3,4,5,6};
        byte[] outSrc = null;
        ByteArrayInputStream input = new ByteArrayInputStream(inSrc);// CharArrayReader
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        
         int data = 0;
        while((data = input.read()) != -1)
            outputOrigin.write(data);
        outSrc = outputOrigin.toByteArray();


        System.out.println("Input Source:" + Arrays.toString(inSrc));//{1,2,3,4,5,6}
        System.out.println("OutputOrigin Source:" + Arrays.toString(outSrc));//;{1,2,3,4,5,6}
        }

 

 


FileInputStream & FileOutputStream

InputStream 과 OutputStream의 자손으로 코드를 확인해보자.

 

public class DemoFileInOutStream {
    public static void main(String[] args) throws IOException {
        String path = "/Users/sjpark/IdeaProjects/JavaStudy/test.txt";
        FileInputStream file = null;
        //일반 try-finally 문
        try {
            file = new FileInputStream(path);
            int data;
            while ((data = file.read()) != -1) {
                char c = (char) data;
                System.out.print(c);
            }
        } finally {
            if (file != null) {
                file.close(); 
            }
        }
        }

 

마지막에 읽은 파일을 닫아서 자원을 반납 해줘야한다.

이 과정을 좀더 간편하게 할 수 있는 방법은 try with resource을 활용하는 것이다.

 

//try-with-resource 문
        try (
                FileInputStream file2 = new FileInputStream(path)) {
            int data;
            while ((data = file2.read()) != -1) {
                char c = (char) data;
                System.out.print(c);

            }
        }

 

스트림의 기능을 보완하기 위한 보조스트림도 존재한다.

보조 스트림은 실제 데이터를 입출력 할 수는 없다.

모든 보조스트림은 FilterInputStream 과 FileterOutputStream의 자손들이다.

 

public class DemoBufferInPut {
    public static void main(String[] args) throws FileNotFoundException {
        String path = "/Users/sjpark/IdeaProjects/JavaStudy/123.txt";
        try{
            FileOutputStream file = new FileOutputStream(path);// 출력은 FileOutputStream이 구현
            BufferedOutputStream bos = new BufferedOutputStream(file,2);
            for (int i = '1'; i <= '9'; i++) {
                bos.write(i);
            }
            file.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }