-
[자바의 정석] Ch.15 입출력(I/O)스터디플래너/공부하기 2022. 10. 3. 09:49
3. 바이트기반의 보조스트림
3.1 FilterInputStream과 FilterOutputStream
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/FilterInputStream.html
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/FilterOutputStream.html
- FilterInputStream과 FilterOutputStream은 각각 InputStream과 OutputStream의 자식 클래스이면서 보조스트림 클래스의 조상 클래스
- FilterInputStream/FilterOutputStream은 보조스트림으로 자체적으로 입출력을 수행할 수 없고 InputStream과 OutputStream이 필요함
- FilterInputStream/FilterOutputStream은 각각 InputStream과 OutputStream의 모든 메소드를 상속받을 수 있고 상속을 통해 원하는 작업을 수행하도록 오버라이딩해야 함
- FilterInputStream은 접근제한자가 protected로 인스턴스를 생성할 수 없고 상속을 통해 오버라이딩 해야함
3.2 BufferedInputStream과 BufferedOutputStream
http://www.tcpschool.com/c/c_io_console
- 버퍼란 임시메모리 공간으로 위 그림에서 키보드로 'LOVE'를 쓸 때 'L'을 누를 때 'L', 'O'를 누를 때 'O' 를 바로 프로그램에 보여주는 것이 아니라 버퍼에 'L', 'O', 'V', 'E'를 저장해두었다가 버퍼가 가득 차거나 개행문자가 나타나면 버퍼의 내용을 프로그램에 전송하는 것
- BufferedInputStream/BufferedOutputStream은 버퍼를 이용하여 스트림의 입출력 효율을 높히는 보조스트림
- 외부의 입력소스로부터 읽는 것보다 내부의 버퍼로부터 읽는 것이 빨라 효율이 올라감
- InputStream의 인스턴스를 입력소스로 함.
- int size를 통해 버퍼의 사이즈를 지정할 수 있고 크기를 지정하지 않으면 8192byte(8K)크기의 버퍼가 생김
- BufferedInputStream은 버퍼의 크기만큼 입력 소스에서 데이터를 읽어와 버퍼에 저장하는 작업을 반복함
- OutputStream을 출력 소스로 함.
- InputStream과 마찬가지로 int size로 버퍼의 사이즈를 정할 수 있고 지정하지 않으면 8192 byte크기의 버퍼가 생김
- BufferedOutputStream은 write()를 하면 출력이 버퍼에 저장하고 버퍼가 가득차면 버퍼의 모든 내용을 출력.
- 버퍼가 가득 찼을 때만 출력하기 때문에 버퍼에 데이터가 남은 채로 종료될 수 있음
- close()나 flush()를 호출하여 버퍼스트림에 저장된 모든 내용을 출력하게 해야 함
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class BufferedOutputStreamEx { public static void main(String[] args) { try{ FileOutputStream fos = new FileOutputStream("123.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos, 5); for(int i='1'; i<='9'; i++) bos.write(i); fos.close(); }catch (IOException e){ e.printStackTrace(); } } }
- 위 코드는 FileOutputStream으로 123.txt 파일을 만들고, 버퍼의 크기가 5인 BufferedOutputStream으로 1부터 9까지 출력하는 예제이지만 실제로 실행해보면 1부터 5까지만 저장이 됨 → BufferedOutputStream은 버퍼가 가득 찼을 때만 출력하기 때문에 1부터 5까지 write()했을 땐 버퍼가 가득차서 출력을 했지만 6에서 9까지 write()할 때는 버퍼가 가득 차지 않아 출력하지 못함
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class BufferedOutputStreamEx { public static void main(String[] args) { try{ FileOutputStream fos = new FileOutputStream("123.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos, 5); for(int i='1'; i<='9'; i++) bos.write(i); fos.close(); bos.close(); }catch (IOException e){ e.printStackTrace(); } } }
- 남은 6부터 9를 출력하기 위해 위와 같이 코드를 작성 → FileOutputStream의 인스턴스 fos가 close()에 의해 닫힌 뒤 BufferedOutputStream의 인스턴스 bos를 close()하면 오류가 발생함. 보조 스트림이 스트림에 의존적이라는 걸 확인
import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class BufferedOutputStreamEx { public static void main(String[] args) { try{ FileOutputStream fos = new FileOutputStream("123.txt"); BufferedOutputStream bos = new BufferedOutputStream(fos, 5); for(int i='1'; i<='9'; i++) bos.write(i); bos.close(); fos.close(); }catch (IOException e){ e.printStackTrace(); } } }
- BuffredOutputStream의 인스턴스 bos에 close()를 호출하여 버퍼에 저장된 내용을 출력하게 하고 FileOutputStream fos를 close()하니 에러가 발생하지 않고 원하던 대로 1부터 9까지 출력
3.4 SequenceInputStream
- 여러 개의 입력스트림을 하나의 스트림으로부터 데이터를 읽어오는 것처럼 처리할 수 있도록 도와줌
- 큰 파일을 여러 개의 작은 파일로 나누었다가 하나로 합치는 것 같은 작업을 할 때 사용
- FilterInputStream이 아니라 InputStream을 상속
3.5 PrintStream
- PrintStream은 print, println, printf를 오버로딩하여 데이터를 다양한 형태로 출력
- 문자기반 스트림과 동일한 역할로 JDK1.1부터 PrintWriter를 추가했으나 System.out이 PrintStream이므로 둘다 사용하게 됨
- 다양한 언어를 처리하는데 적합하므로 PrintWriter를 사용하는 것이 좋음
3.3 DataInputStream과 DataOutputStream
- DataInputStream과 DataOutputStream은 각각 FilterInputStream, FilterOutputStream의 자식 클래스이자 DataInput, DataOutput 인터페이스를 구현
- DataInput과 DataOutput을 구현한 덕분에 이진 스트림에서 바이트를 읽고 이를 Java의 기본자료형으로, Java 기본 자료형에서 이진 스트림의 바이트로 재구성할 수 있음
자료형 메소드 설명 int read(byte[] b) Reads some number of bytes from the contained input stream and stores them into the buffer array b. int read(byte[] b, int off, int len) Reads up to len bytes of data from the contained input stream into an array of bytes. boolean readBoolean() See the general contract of the readBoolean method of DataInput. byte readByte() See the general contract of the readByte method of DataInput. char readChar() See the general contract of the readByte method of DataInput. double readDouble() See the general contract of the readChar method of DataInput. float readFloat() See the general contract of the readFloat method of DataInput. void readFully(byte[] b) See the general contract of the readFully method of DataInput. void readFully(byte[] b, int off, int len) See the general contract of the readFully method of DataInput. int readInt() See the general contract of the readInt method of DataInput. String readLIne() Deprecated. This method does not properly convert bytes to characters.long readLong() See the general contract of the readLongmethod of DataInput. short readShort() See the general contract of the readShortmethod of DataInput. int readUnsignedByte() See the general contract of the readUnsignedByte method of DataInput int readUnsignedShort() See the general contract of the readUnsignedShort method of DataInput. String readUTF() See the general contract of the readUTF method of DataInput. static String readUTF(DataInput in) Reads from the stream in a representation of a Unicode character string encoded in modified UTF-8 format; this string of characters is then returned as a String. int skipBytes(int n) See the general contract of the skipBytesmethod of DataInput. 위 메소드 중 read 가 들어가는 메소드는 읽을 값이 없으면 EOFException이 발생한다. NPE처럼 줄여서 부르는 예외이름인 줄 알았는데 찾아보니 EOFException이란 말을 그대로 쓴다. End Of File, End Of Stream의 약자인 듯 하다. 입력받는 중에 기대와 다르게 파일이나 스트림의 끝에 도달했을 때 발생하는 오류이다. 사용할 때 예외처리하기보다 스트림의 끝에 특별한 값을 반환한다고 한다.
자료형 메소드 설명 void flush() Flushes this data output stream. int size() Returns the current value of the counter written, the number of bytes written to this data output stream so far. void write(byte[] b, int off, int len) Writes len bytes from the specified byte array starting at offset off to the underlying output stream. void write(int b) Writes the specified byte (the low eight bits of the argument b) to the underlying output stream. void writeBoolean(boolean v) Writes a boolean to the underlying output stream as a 1-byte value. void writeByte(int v) Writes out a byte to the underlying output stream as a 1-byte value. void writeBytes(String s) Writes out the string to the underlying output stream as a sequence of bytes. void writeChar(int v) Writes a char to the underlying output stream as a 2-byte value, high byte first. void writeChars(String s) Writes a string to the underlying output stream as a sequence of characters. void writeDouble(double v) Converts the double argument to a long using thedoubleToLongBits method in class Double, and then writes that long value to the underlying output stream as an 8-byte quantity, high byte first. void writeFloat(float v) Converts the float argument to an int using thefloatToIntBits method in class Float, and then writes that int value to the underlying output stream as a 4-byte quantity, high byte first. void writeInt(int v) Writes an int to the underlying output stream as four bytes, high byte first. void writeLong(long v) Writes a long to the underlying output stream as eight bytes, high byte first. void writeShort(int v) Writes a short to the underlying output stream as two bytes, high byte first. void writeUTF(String str) Writes a string to the underlying output stream usingmodified UTF-8 encoding in a machine-independent manner. 더보기import java.io.*; import java.util.Arrays; public class DataOutputStreamEx { public static void main(String[] args) { ByteArrayOutputStream bos = null; DataOutputStream dos = null; byte[] result = null; try { bos = new ByteArrayOutputStream(); dos = new DataOutputStream(bos); dos.write(10); dos.writeFloat(20.0f); dos.writeBoolean(true); result = bos.toByteArray(); String[] hex = new String[result.length]; for(int i=0; i< result.length; i++){ if(result[i]<0) hex[i] = String.format("%02x", result[i] + 256); else hex[i] = String.format("%02x", result[i]); } System.out.println("10진수 : " + Arrays.toString(result)); System.out.println("16진수 : " + Arrays.toString(hex)); dos.close(); }catch (IOException e){ e.printStackTrace(); } } }
교재 속 예제 15-9를 실행해봤는데 책과 결과가 달랐다. 책은 [0, 0, 0, 10, 65, -06, 0, 0, 1] /n [00, 00, 00, 0a, 41, a0, 00, 00, 01]이었다. 첫 줄부터 하나씩 비교하다보니 writeInt()라고 썼어야 했는데 write()라고만 썼다. 이 부분을 고치니 바로 해결할 수 있었다. 책을 보니 int와 float이 4byte라 ByteArrayOutputStream에 네 개의 공간을 차지한 듯 하다. 하지만 write()을 할 땐 10이 byte범위인 -128~127사이에 있어서 1byte만 차지한 것 같다. 궁금해서 write()메소드로 127을 출력해보니 127 그대로 출력하고 그 다음 128은 -128로 출력한다. 256은 0, 257은 1, 258은 2가 나왔다. write()로는 부호가 있는 byte범위의 정수만 입력할 수 있다.
더보기import java.io.*; public class DataOutputStreamEx2 { public static void main(String[] args) { int sum = 0; int score = 0; FileInputStream fis = null; DataInputStream dis = null; try { fis = new FileInputStream("score.dat"); dis = new DataInputStream(fis); while (true){ score = dis.readInt(); System.out.println(score); sum += score; } }catch (EOFException ee){ System.out.printf("점수의 총합은 %d입니다.\n", sum); }catch (IOException ie){ ie.printStackTrace(); }finally { try { if(dis != null) dis.close(); }catch (IOException ie){ ie.printStackTrace(); } } } }
import java.io.*; public class DataOutputStreamEx2 { public static void main(String[] args) { int sum = 0; int score = 0; try (FileInputStream fis = new FileInputStream("score.dat"); DataInputStream dis = new DataInputStream(fis)) { while (true){ score = dis.readInt(); System.out.println(score); sum += score; } }catch (EOFException ee){ System.out.printf("점수의 총합은 %d입니다.\n", sum); }catch (IOException ie){ ie.printStackTrace(); } } }
코드를 보다가 try - with - resource을 복습했다.
4. 문자기반 스트림
- 문자기반 스트림은 바이트 단위로 데이터를 다루던 바이트기반 스트림 달리 문자 단위로 데이터를 다루는 점을 제외하면 사용 방법은 동일
- 그렇다면 문자기반 스트림과 바이트기반 스트림은 무엇이 다를까?
→ 바이트기반 스트림은 데이터를 읽어올 때 read(byte[] b)을 이용하고 문자기반 스트림은 read(char[] c)를 이용
Stream클래스는 byte 기반 클래스로 byte 단위로 데이터를 읽고 쓰기때문에 이미지처럼 이진수로 된 파일을 다루거나 작은 데이터를 다루는데 유용하다. Reader/Writer 클래스는 문자 기반 클래스로 하나의 문자씩 데이터를 읽고 쓰기 때문에 텍스트 파일이나 다른 텍스트 스트림을 다루는데 유용하다.
https://stackoverflow.com/questions/4367539/what-is-the-difference-between-reader-and-inputstream
InputStream은 데이터를 byte 단위로 읽고 어떤 번역의 과정을 거치지 않는다. 이진 파일이나 이미지 파일을 읽을 때 InputStream을 사용한다. Reader는 문자기반 스트림으로 Input Stream 원본에서 디코딩 과정을 거쳐 유니코드 문자로 돌려준다. 텍스트로의 어떤 유형이든 Reader를 사용한다.
사실 책에 char가 2byte라서 무조건 문자기반 스트림이 2byte 단위로 스트림을 처리하는 것이 아니고 Reader/Writer 클래스와 그 자식 클래스들은 특정 인코딩을 읽어서 유니코드로 변환하고 유니코드를 특정 인코딩으로 변환하여 저장한다는 이야기가 나와서 그걸 찾고싶었는데 찾지 못했다.
https://stackoverflow.com/questions/4367539/what-is-the-difference-between-reader-and-inputstream
한가지 더 보자면 'a‡a'는 우리가 보기에 3글자이지만 ‡가 유니코드 문자라서 InputStream으로 'a‡a'를 읽으면 a는 바이트의 범위에 들지만 '‡'는 8225로 바이트를 초과해 226, 128, 161 총 3 byte가 된다. 하지만 Reader로 읽으면 8227이 되어 97 8227 97이 된다.
4.1 Reader와 Writer
- Reader 클래스와 Writer 클래스 모두 Object 클래스의 자식 클래스로 Buffered, CharArray, Filter, Stream, Piped, String Reader/Writer를 부모 클래스
- Reader 클래스는 URLReader, Writer 클래스는 PrintWriter로 각각 고유한 클래스의 부모 클래스
자료형 메서드 설명 abstract void close() Closes the stream and releases any system resources associated with it. void mark(int readAheadLimit) Marks the present position in the stream. boolean markSupported() Tells whether this stream supports the mark() operation static Reader nullReader() Returns a new Reader that reads no characters. int read() Reads a single character. int read(char[] cbuf) Reads characters into an array. abstract int read(char[] cbuf, int off, int len) Reads characters into a portion of an array. int read(CharBuffer target) Attempts to read characters into the specified character buffer. boolean ready() Tells whether this stream is ready to be read. void reset() Resets the stream. long skip(long n) Skips characters. long transferTo(Writer out) Reads all characters from this reader and writes the characters to the given writer in the order that they are read. 자료형 메서드 설명 Writer append(char c) Appends the specified character to this writer. Writer append(CharSequence csq) Appends the specified character sequence to this writer. Writer append(CharSequence csq, int start, int end) Appends a subsequence of the specified character sequence to this writer. abstract void close() Closes the stream, flushing it first. abstract void flush() Flushes the stream. static Writer nullWriter() Returns a new Writer which discards all characters. void write(char[] cbuf) Writes an array of characters. abstract void write(char[] cbuf, int off, int len) Writes a portion of an array of characters. void write(int c) Writes a single character. void write(String str) Writes a string. void write(String str, int off, int len) Writes a portion of a string. 4.2 FileReader와 FileWriter
- Reader와 Writer 클래스 페이지에 FileReader와 FileWriter가 없어 이상하다 생각했는데 Reader와 Writer의 자식 클래스인InputStreamReader/OutputStreamWriter의 자식 클래스
- 문자 파일을 텍스트로 읽고, 텍스트를 문자 파일로 씀
- byte로 인코딩 할 때는 특정 charset이나 플랫폼 기반 charset을 따름
4.3 PipedReader와 PipedWriter
- 쓰레드 간 데이터를 주고받을 때 사용
- 입력 스트림과 출력스트림을 생성한 다음 둘 중 하나의 스트림에 connect()를 호출해서 하나의 스트림으로 연결하여 데이터를 주고받고, 입출력을 마친 뒤 한쪽 스트림만 닫아도 나머지 스트림이 닫힘
- 생성자를 선언할 때 매개변수로 아무것도 받지 않거나 정수로 pipeSize만 받으면 아직 연결되지 않음을 의미
함
더보기import java.io.IOException; import java.io.PipedReader; import java.io.PipedWriter; import java.io.StringWriter; public class PipedReaderWriter { public static void main(String[] args) { InputThread inThread = new InputThread("InputThread"); OutputThread outThread = new OutputThread("OutputThread"); inThread.connect(outThread.getOutput()); inThread.start(); outThread.start(); } } class InputThread extends Thread{ PipedReader input = new PipedReader(); StringWriter sw = new StringWriter(); InputThread(String name){ super(name); } public void run(){ try{ int data = 0; while ((data=input.read()) != -1) sw.write(data); System.out.println(getName() + " received : " + sw.toString()); }catch (IOException e){} } public PipedReader getInput(){ return input; } public void connect(PipedWriter output){ try { input.connect(output); }catch (IOException e){} } } class OutputThread extends Thread{ PipedWriter output = new PipedWriter(); OutputThread(String name){ super(name); } public void run() { try { String msg = "Hello!"; System.out.println(getName() + " sent : " + msg); output.write(msg); output.close(); }catch (IOException e) { } } public PipedWriter getOutput(){ return output; } public void connect(PipedReader input){ try { output.connect(input); }catch (IOException e) { } } }
4.4 StringReader와 StringWriter
- StringReader와 StringWriter는 CharArrayReader와 CharArrayWriter와 같이 입출력 대상이 메모리인 스트림
- StringWriter에 출력되는 데이터는 StringBuffer에 데이터를 저장하고 getBuffer()와 toString()와 같은 메소드를 통해 StringBuffer에 저장된 데이터를 얻을 수 있음
- 근본적으로 String도 char 배열이라면 거의 같은데 왜 두 개의 클래스가 있는걸까? CharArrayWriter는 reset() 메소드로 이미 할당된 버퍼를 버릴 필요 없이 다시 쓸 수 있도록 초기화할 수 있다는 것과 size() 메소드로 현재 버퍼 사이즈를 알 수 있다는 것 두 가지 메소드가 더 있고 거의 동일하다.
- 무엇이 다른가 싶어 검색해봤다. JDK 7 문서에 따르면 StringWriter는 StringBuffer가 있어 thread-safe하다고 되어있지만 CharArrayWriter는 단순하게 char 배열이 있다고 한다. 그렇다면 StringWriter는 thread-safe하고 CharArrayWriter는 thread-safe하지 않은가? 이런 의문을 품을 수 있기 때문에서인지 JDK 11 문서에서는 'thread-safe'라고 언급한 것이 사라졌다.
- JDK 11버전 문서는 CharArrayWriter에 대해 Writer를 char형 버퍼로 구현한 클래스로 스트림에 데이터를 출력할 때 버퍼가 자동으로 커진다고 말하고, StringWriter는 string을 만드는데 쓰이는 string buffer에 결과값을 쌓는 character stream이라고 되어있다. 해석이 제대로 되지 않아서 잘못 알고 있을 수 있지만 아무튼 char형 배열과 String을 쓴다는 것 외 다른게 없나보다.
5. 문자기반의 보조스트림
5.1 BufferedReader와 BufferedWriter
- BufferedReader와 BufferedWriter는 버퍼를 이용하여 입출력 효율을 높임
5.2 InputStreamReader와 OutputStreamWriter
- 바이트기반 스트림을 문자기반 스트림으로 연결하는 역할
- 바이트기반 스트림 데이터를 지정된 인코딩의 문자데이터로 변환
- 코딩 테스트를 연습할 때 BufferedReader(new InputStreamReader(System.in))을 사용했었는데 System.in이 InputStream인가보다.
더보기import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Properties; public class InputStreamReaderEx { public static void main(String[] args) { Properties prop = System.getProperties(); System.out.println(prop.get("sun.jnu.encoding")); InputStreamReader isr = new InputStreamReader(System.in); BufferedReader br = new BufferedReader(isr); System.out.printf("사용중인 OS의 인코딩 : %s \n", isr.getEncoding()); } }
- 교재에서는 MS949가 나왔는데 내 PC에서는 UTF-8이 나왔다. 예제 내용 중 신기한 메소드가 있어 확인해보니 String 클래스의 메소드였다. equalsIgnoreCase() 메소드이다. 두 문자열을 비교하여 같은 길이에 일치하고 대소문자 관계 없이 일치한다면 true를 반환한다. 아래 예제에서도 abc와 ABC를 비교할 때 equals는 false를 반환하지만 equalsIgnoreCase는 true를 반환한다. 사용할 기회가 된다면 사용해야겠다.
public class StringEx { public static void main(String[] args) { String input = "abc"; System.out.println(input.equals("ABC")); System.out.println(input.equalsIgnoreCase("ABC")); } } //false //true
'스터디플래너 > 공부하기' 카테고리의 다른 글
[자바의정석] Ch16. 네트워킹(Networking) (1) 2022.10.08 [자바의 정석] Ch.15 입출력(I/O) (1) 2022.10.03 [자바의 정석] Ch.15 입출력(I/O) (0) 2022.09.18 [자바의 정석] Ch.14 람다와 스트림 (0) 2022.09.10 [자바의 정석] Ch.14 람다와 스트림 (0) 2022.09.03