Java 8일차

진창호·2023년 1월 29일

Java

목록 보기
8/9

Java는 노드 스트림을 지원한다.

I/O의 끝단을 노드, 두 노드를 연결하고 데이터를 전송하는 개념을 스트림이라 한다.
스트림은 단방향으로만 통신이 가능하다. 따라서 Input, Output 동시 통신은 할 수 없다.

스트림 분류는 아래와 같다.
스트림 분류

스트림 중 입력에 관한 스트림을 InputStream이라 한다. 주요 메서드는 아래와 같다.
스트림 메서드
첫번째 함수가 int를 반환하는 이유는
-1을 읽었을 때와 읽을 값이 없을 때를 구분하기 위해서이다.
읽었을 때 11111111 11111111 11111111 11111111이면 메서드가 리턴되고,
그게 아니면 뒤에 1byte만 읽는다.

아래 코드는 read()를 사용하는 예시를 보여준다.

public class SimpleInputTest {

    private String data = "hi java world";

    private void read1() {
        try (InputStream input = new ByteArrayInputStream(data.getBytes())) {
        	int read = -1;
        	
        	while ((read = input.read()) != -1) {
        		System.out.print(String.format("읽은 값 : %d, %c ", read, read) + " ");
        	}
        	System.out.println();
        	
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
    
    private void read2() {
    	byte[] buffer = new byte[10];
    	
        try (InputStream input = new ByteArrayInputStream(data.getBytes())) {
        	int read = -1;
        	
        	while ((read = input.read(buffer)) > 0) {
        		System.out.print(String.format("읽은 크기 : %d, %s  ", read, new String(buffer, 0, read)));
        	}
        	System.out.println();
        	
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        SimpleInputTest ns = new SimpleInputTest();
        ns.read1();
        ns.read2();
    }
}

출력 결과는 아래와 같다.

읽은 값 : 104, h 읽은 값 : 105, i 읽은 값 : 32, 읽은 값 : 106, j ...
읽은 크기 : 10, hi java wo 읽은 크기 : 3, rld

Byte 단위 입출력은 영어 String 또는 사진같은 멀티미디어를 입출력하는 데 주로 사용한다.
하지만 한글은 1Byte로 표현이 불가능하다. 따라서 Char 단위 입출력을 사용한다.
Char 단위 입력인 Reader의 주요 메서드는 아래와 같다.
리더 메서드
아래 코드는 한글 String을 read()를 사용하여 읽는 예시이다.

public class SimpleInputTest {
    private String data = "자바는 객체지향 언어입니다.";
    
    private void read3() {
    	char[] buffer = new char[10];
    	
        try (Reader input = new CharArrayReader(data.toCharArray())) {
        	int read = -1;
        	
        	while ((read = input.read(buffer)) > 0) {
        		System.out.print(String.format("읽은 크기 : %d, %s  ", read, new String(buffer, 0, read)));
        	}
        	System.out.println();
        	
        } catch (IOException e) {
        	e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        SimpleInputTest ns = new SimpleInputTest();
        ns.read3();
    }
}

출력 결과는 아래와 같다.

읽은 크기 : 10, 자바는 객체지향 언 읽은 크기 : 5, 어입니다.

※ Java가 알파벳과 한글을 읽는 기준
만약 A가를 읽는다고 가정하자.
그럼 UTF-8에선 0x80을 기준으로 byte값이 이보다 낮으면 1byte, 아니면 3byte로 바꾼다.
따라서 A가는 A / EA / B0 / 80 으로 바뀐다.

이 때 자바는 알파벳은 1byte를 2byte로 바꾸고, 한글은 3byte를 2byte로 바꾼다.

스트림 중 출력에 관한 스트림을 OutputStream이라 한다. 개념만 알아두자.


Java는 파일 입출력을 지원한다.

Java에선 내용을 저장하는 데 주로 파일을 사용한다.
Byte 파일을 읽고 쓰는데는 FileInputStream, FileOutputStream을 사용한다.

아래 코드는 byte 파일의 입출력을 보여준다.

public class Check {
	private int bufferSize = 100;

	public Check() {
		File src = new File(".\\origin.txt");
        File target = new File(".\\copy.txt");
        
        byte[] buffer = new byte[bufferSize];
        
        try (InputStream input = new FileInputStream(src); OutputStream output = new FileOutputStream(target)) {
        	int read = -1;
        	
        	while ((read = input.read(buffer)) > 0) {
        		output.write(buffer, 0, read);
        	}
        } catch (IOException e) {
        	e.printStackTrace();
        }
	}

	public static void main(String[] args) {
		new Check();
	}
}

위 코드를 실행시키면 origin.txt랑 내용이 똑같은 copy.txt가 생성된다.

한글 파일은 FileReader, FileWriter를 사용한다.


Java는 보조 스트림을 지원한다.

실제로 현실을 노트 스트림으로만 표현하는 데는 한계가 있다.
따라서 다른 스트림에 부가적인 기능을 제공하는 스트림을 보조 스트림이라 한다.

보조 스트림의 쓰임은 아래와 같다.

  1. InputStreamReader, OutputStreamWriter
    : byte 스트림을 char 스트림으로 변환
  2. BufferedInputStream, BufferedOutputStream,
    BufferedReader, BufferedWriter
    : 버퍼링을 통한 속도 햐상
  3. ObjectInputStream, ObjectOutputStream
    : 객체 전송

아래 코드는 BufferedReader를 사용해 입력 속도를 높인 예이다.

File src = new File(".\\target.txt");

try (BufferedReader br = new BufferedReader(new FileReader(src))) {
	String line = null;
    
    while ((line = br.readLine()) != null) {
    	System.out.println(line);
    }
} catch (IOException e) {
	e.printStackTrace();
}

※ Buffered 계열을 쓸 때는 close가 강제된다.


Java는 직렬화를 지원한다.

객체를 저장하기 위해 연속적인 데이터로 변환하는 것을 직렬화라고 한다.
반대로 저장된 연속적인 데이터를 다시 객체로 변환하는 것을 역직렬화라고 한다.

직렬화가 되기 위해선 Serializable 인터페이스를 implements 해야 한다.
아래 코드는 직렬화와 역직렬화의 예시이다.

class Info implements Serializable {
	private String id;
	private String pass;
	
	public Info(String id, String pass) {
		this.id = id;
		this.pass = pass;
	}

	@Override
	public String toString() {
		return "Info [id=" + id + ", pass=" + pass + "]";
	}
}
public class Person {
	public static void main(String[] args) {
		Info info = new Info("1234", "pass");
		
		File target = new File(".\\object.dat");
		try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(target))) {
			oos.writeObject(info);
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(target));) {
			Info copyInfo = (Info) ois.readObject();
			System.out.println(copyInfo.toString());
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

위의 상황에서 Info 클래스는 serialVersionUID를 설정할 것을 권장한다.
만약 이를 설정하지 않고 직렬화를 거치면 컴파일러가 serialVersionUID를 자동 설정한다.
그 후 Info 클래스에 멤버 변수를 추가하거나 삭제하면 역직렬화시 예외가 발생한다.

Info copyInfo = (Info) ois.readObject(); // 예외 발생

이는 직렬화와 역직렬화의 serialVersionUID가 달라서 발생한다.
따라서 직렬화 시 serialVersionUID를 설정하면 그 후에 멤버 변수를 추가하거나 삭제해도
적절히 맞춰서 역직렬화가 이뤄진다.

추가로, transient를 사용해서 직렬화에서 예외시킬 수도 있다.

class Info implements Serializable {
	private static final long serialVersionUID = -4915548407285446877L;
	private String id;
	private transient String pass;
	
	public Info(String id, String pass) {
		this.id = id;
		this.pass = pass;
	}

	@Override
	public String toString() {
		return "Info [id=" + id + ", pass=" + pass + "]";
	}
}

이러면 직렬화 시 pass 멤버 변수는 자동으로 제외된다.

profile
백엔드 개발자

0개의 댓글