ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java I/O <1 - OutputStream>
    언어/자바 2023. 12. 23. 03:46

     

     

    막연하게만 사용하던 I/O를 조금 더 이해해보고자 작성한 글입니다.
    기본적으로 I/O는 O/S 레벨에서 이루어지고 byte를 다룬다는 것을 생각하면 좋을 것 같습니다.

     

    글 작성에 참고한 영상입니다.

    Java IO - Output Streams by Kody Simpson

     

     


     

     

    OutputStream

     

    데이터를 Byte로 변경하여 목적지로 보내는 역할을 한다.
    크게 1개의 Byte를 사용하는 것과 Byte[ ]을 사용하는 경우로 나누어서 볼 수 있겠다.

    자바 코드에서 콘솔에 보이기 까지

     

    - 단일 Byte 사용

     

    1. byte타입

    class TestOuputStream {
        	public static void main(String[] args) {
            
            	byte b = 75;
            	
                OutputStream out = System.out;
                out.write(b);
                out.flush();  
                
                // 콘솔에 K 출력
            }
        }

     

    위 코드는 아래와 같이 실행된다.

    	class TestOuputStream {
        	public static void main(String[] args) {
            
            	// 1.byte는 signed 8bit의 데이터를 갖는다.
                // 따라서 b의 실제 데이터는 75를 2진수로 변환한 '01001011'이다.
            	byte b = 75; 
            	
                OutputStream out = System.out;
                
                // 2.write에 1byte인 '01001011' 전달한다.
                out.write(b);
                
                // 3.버퍼에 들어있는 내용을 강제로 목적지로 보낸다.
                out.flush();  
     
                
                // 4.터미널에서 전달받은 출력스트림을 인코딩한다.
                // 5.해당 결과를 기반으로 unicode에서 매치되는 글자를 찾는다.
                // 6.해당 글자를 console에 보여준다.
                
                
            
            }
        }

     

    [ UTF - 8 ]
    
    가변길이 인코딩 방식으로 문자를 1byte에서 4byte로 인코딩한다.
    '01001011'은 0으로 시작하기때문에 1Byte 데이터임을 알 수 있다.
    '01001011'를 Unicode 코드포인트로 변경하면 U+004B이다.
    ( 01001011 => 0100 1011 => 4B => U+004B )
    U+004B는 K를 의미

     

     

    2. int 타입

    class TestOuputStream {
        public static void main(String[] args) {
            
            int i = 75;
            	
            OutputStream out = System.out;
            out.write(b);
            out.flush();  
       
            // 콘솔에 K 출력
        }
    }

     

    위 코드는 아래와 같이 실행된다.

    class TestOuputStream {
          public static void main(String[] args) {
            	
                // 1. int는 signed 32bit의 데이터를 갖는다.
            	int i = 75;
            	
                OutputStream out = System.out;
                
                // 2. write(int a)의 경우는 32bit 중 하위 8bit만 사용한다.
                // 공식 문서 참고
                out.write(b);
                out.flush();  
                
                // 3. 이후 진행은 byte와 동일
          }
     }

     

     

    3. char 타입

    class TestOuputStream {
        	public static void main(String[] args) {
            
            	char c = 75;
                char c1 = 'K';
            	
                OutputStream out = System.out;
                out.write(c);
                out.write(c1);
                out.flush();  
                
                // 콘솔에 K 출력
            }
        }

     

    위 코드는 아래와 같이 실행된다.

    	class TestOuputStream {
        	public static void main(String[] args) {
            	
                // 1. char은 unsigned 16bit 데이터 타입
                // 기본적으로 unicode의 codepoint를 저장하기 위해 고안된 타입
                // 컴파일 타임에 숫자인지 문자 리터럴인지 확인
                // 숫자의 경우 해당 숫자를 16bit 데이터로 할당
                // 00000000 01001011 저장
                char c = 75;
                
                // 문자리터럴의 경우, 해당 문자열을 unicode에서 찾고
                // 해당 코드포인트를 16bit로 변경하여 할당
                // U+004B => 0100 1011 => 00000000 01001011 저장 
                char c1 = 'K';
            	
                OutputStream out = System.out;
                
                // write()는 int 혹은 byte[]을 입력으로 받는다.
                // char 타입을 write에 넣으면 자동으로 int로 변경된다.
                // 16bit char이 32bit int로 변경되고, 하위 8bit만 사용된다.
                out.write(c);
                out.write(c1);
                out.flush();  
            }
        }

     

     

    [ Type Promotion ]
    
    자바는 기본적으로 데이터의 손실이 발생하지 않는다면, 더 적은 크기의 데이터 타입은 더 큰 크기의 데이터타입으로 변경이 가능하다.
    
    1. char => int (o)
    char c = 'K' (00000000 01001011);
    int i = c (00000000 000000000 00000000 01001011);
    2. int => float (o)
    데이터의 손실이 없기때문에 변환된다.
    
    int i = 3; (00000000 000000000 00000000 01001011)
    float f = i; (0 10000000 10000000000000000000000)
    3. float => int (x)
    int로 변경할 경우, 소수점이 사라지는데 이는 데이터 손실을 의미한다.
    따라서 개발자가 데이터 손실을 인지한다는 것을 의미하는 casting을 해줘야 사용가능하다.
    
    float f = 3.3;
    int i = f; // x
    int i2 = (int) f; // o

     

     

     

    - Byte[] 사용

     

    1. byte[]

    class TestOutputStream {
        	public static void main(String[] args) {
           		
                byte[] b = new byte[4];
    
                for (int i = 0; i < b.length; i++) {
                    int character = 65 + i;
                    b[i] = (byte) character;
                }
    
                OutputStream out = System.out;
    
                out.write(b);
                out.flush();
            	
                // 콘솔에 ABCD 출력
            }
        }

     

    위 코드는 아래와 같이 실행된다.

    class TestOutputStream {
       public static void main(String[] args) {
           	// 1. 보통은 InputStream을 통해 전달받은 byte배열을 사용
            // 테스트를 위한 byte배열 생성
            byte[] b = new byte[4];
    			
            // 2. byte배열에 65 ~ 68 의 byte를 넣는다.
            for (int i = 0; i < b.length; i++) {
                int character = 65 + i;
                b[i] = (byte) character;
            }
             
            OutputStream out = System.out;
    
    	// 3. byte[]을 받는 경우, 해당 배열을 순회하면서 하나씩 write를 수행
            // 콘솔에 ABCD 출력
            out.write(b);
            out.flush();
            
      }
    }

     

     

    2. byte[]와 구간 설정

    class TestOutputStream {
        	public static void main(String[] args) {
           		
                byte[] b = new byte[4];
    
                for (int i = 0; i < b.length; i++) {
                    int character = 65 + i;
                    b[i] = (byte) character;
                }
    
                OutputStream out = System.out;
    
                out.write(b, 1, 2); 
                out.flush();
            	
                // 콘솔에 BC 출력
            }
        }

     

    위 코드는 아래와 같이 실행된다.

    	class TestOutputStream {
        	public static void main(String[] args) {
           		
                byte[] b = new byte[4];
    			
                for (int i = 0; i < b.length; i++) {
                    int character = 65 + i;
                    b[i] = (byte) character;
                }
    
                OutputStream out = System.out;
    
    			// 위 코드와 모두 동일하고,
                // 2번째 인자는 시작 index, 3번째 인자는 길이이다.
                // byte 배열의 1번 인덱스부터 2개를 보여준다는 의미
                out.write(b, 1, 2);
                out.flush();
            
            }
        }

     

     

     


     

    Sumamry

    • 개념적으로는 write() 메소드는 1byte를 입력받고 1byte를 출력버퍼에 쓴다.
    • 구현은 int를 입력 받고 있지만, byte보다 큰 타입은 int로 변경되고 하위 8bit만 사용한다.
    • write() 메소드는 내부적으로 system call을 호출해서 출력버퍼에 입력한다.
    • write 메소드는 출력버퍼가 가득 찰 때가지 1byte씩 입력한다.
    • 출력버퍼가 가득차기 전에 스트림을 흘려보내려면 flush() 메소드를 사용한다.

     

     


    [이미지 출처] 
    https://www.geeksforgeeks.org/java-io-tutorial/ (글 대표 이미지)

     

    [참고 자료]

    https://www.youtube.com/watch?v=eEjkxWo0NtE&list=PLfu_Bpi_zcDO4CdNYNS2Wten1vLuQfgp7&index=3



     

     

    '언어 > 자바' 카테고리의 다른 글

    Java I/O <3 - FileStream>  (0) 2023.12.25
    Java I/O <2 - InputStream>  (0) 2023.12.23
    너무 다양한 List를 만드는 방법들..  (1) 2023.12.20
    Queue의 add와 offer의 차이  (0) 2023.12.20
    instanceof는 어떻게 작동하나?  (0) 2023.12.20
Designed by Tistory.