자바의 Input과 Ontput에 대해 학습하세요.
스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
InputStream과 OutputStream
Byte와 Character 스트림
표준 스트림 (System.in, System.out, System.err)
파일 읽고 쓰기
자바에서는 데이터를 외부에서 읽고 다시 외부로 출력하는 작업에 스트림(stream)
을 사용한다.
프로그램이 네트워그상의 다른 프로그램과 데이터 교환을 하기 위해서든 양쪽 모두 입력 스트림과 출력 스트림이 필요하다
입력 스트림
출력 스트림
실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 말하며, 자바에서는 파일이나 콘솔에서의 입출력을 스트림을 통해 다룬다.
스트림은 단방향 통신이만이 가능하기 때문에 입력과 출력을 동시에 처리할 수는 없다.
Java 프로그램은 한 번에 한 바이트 씩 스트림에서 읽거나 쓴다.
스트림은 비동기 I/O 방식을 지원하지 않기 때문에 스레드가 현재 작업을 떠나 즉시 I/O에 대한 작업을 시작한다.
또한 스트림은 blocking I/O이기 때문에 I/O 시작부터 읽을 수 있는 데이터가 있거나 데이터가 완전히 기록 될 때까지 스레드가 Block 된다.
Java에서 모든 기본 I/O는 Stream을 기반으로 하기 때문에 빈번하게 사용되는데, 사용을 끝내고 닫아주지 않으면 심각한 메모리 누수가 발생하기 때문에, 주의를 기울여서 사용해야 한다.
Buffer란, 임시로 데이터를 담아둘 수 있는 일종의 큐이다.
바이트 단위의 데이터가 입력될 때마다 Stream은 즉시 전송하게 되는데 이것은 디스크 접근이나 네트워크 접근같은 오버헤드가 발생하기 때문에 매우 비효율적인 방법이다.
Buffer는 중간에서 입력을 모아서 한번에 출력함으로 I/O 의 성능을 향상시키는 역할을 한다.
// Buffer를 사용하지 않고 출력
public static void nonBufferIO() {
for (int i = 0; i < 100000; i++) {
System.out.print(i); //입력이 있을 때마다 출력
}
System.out.println();
}
// Buffer를 사용하여 출력
public static void bufferIO() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 100000; i++) {
sb.append(i); //1. 버퍼에 모두 담고
}
sb.append("\n");
System.out.print(sb); //2. 출력
}
}
속도차이
nonBufferIO() : 281 (ms)
bufferIO() : 15 (ms)
첫번째 메소드는 0부터 10만까지 숫자를 출력하고
두번째 메소드는 입력을 모두 버퍼에 담은 후 입력이 끝나면 담아둔 데이터를 출력한다.
입력이 많아지고 데이터의 크기가 커질수록 이 차이는 더 커진다.
자바의 NIO의 모든 IO는 채널(Channel)
에서부터 시작한다.
채널은 스트림과 유사한데, 파일에서 데이터를 읽을 수 있는 파일채널(FileChannel)
이 있고, 네트워크를 통해 데이터를 주고 받을 수 있는 소켓채널(SocketChannel)
도 있다.
아까 말했듯 자바 에서는 입출력을 위해 Stream
이라는 개념이 있다고 했다.
스트림과 채널은 유사하지만 차이점이 있다.
스트림은 단방향 이지만 채널은 양방향이다.
스트림은 입력 스트림과 출력 스트림이 구분되어 있다, 데이터를 읽기 위해서는 입력 스트림을 생성하고 데이터를 쓰기 위해 출력 스트림을 생성해야 한다.
반면에 채널은 양방향 입출력이 가능해 한 채널에 읽기와 쓰기가 모두 가능하다.
채널은 비동기적으로 읽거나 쓸 수 있다.
입출력 수행시 Blocking 과 Non-blocking 특징 모두 지원
채널은 항상 버퍼로 데이터를 읽어들이거나 버퍼의 데이터를 쓰게 된다.
InputStream 은 Stream중 데이터를 read하는 입구이고, OutputStream은 데이터를 write하는 출구이다.
서로 다른 두 클라이언트가 Stream 을 사용하면 그림과 같이 진행 될 것이다.
InputStream과 OutputStream은 Stream을 이해하는 개념적인 의미이기도 하지만 Java에서는 바이트 시퀀스를 읽거나 쓰기위한 기본 기능을 정의 해놓은 추상클래스 이기도 하다.
Byte Stream 기반의 모든 입력관련 클래스들의 superclass 격인 추상클래스로
AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream
상속하고 있다.
InputStream 은 byte 단위로 파일을 읽어온다.
또한 Buffer를 사용하지 않음으로 큰 데이터를 읽어오는데 있어서 성능이 많이 부족한 편이다.
File file = new File(
"/Users/kimsunghoon/Desktop/ndxpro_v1_dataManager/ngsi-context/"
+ substring);
FileOutputStream outputStream = new FileOutputStream(file);
byte[] strToBytes = context.getBytes();
outputStream.write(strToBytes);
outputStream.close();
Java의 모든 바이트 스트림은 InputStream또는 OutputStream위에 빌드된다.
Java에서 제공하는 스트림 클래스들은 다형성을 이용해 스트림을 계층화가 가능하여, 더 큰 데이터 유형처리와 같은 더 높은 수준의 기능을 제공 할 수도 있다.
Byte Stream
InputStream, OutputStream 둘다 바이트 기반 입출력 스트림의 최상위 클래스로 추상 클래스이다.
관련된 모든 바이트 기반 입출력 스트림은 이 클래스를 상속바아서 만들어진다.
바이트단위로 데이터를 전송하며 입출력 대상에 따라 제공하는 클래스가 다름.
Character Stream
Reader, Writer 둘 다 문자 데이터 기반 입출력의 최상위 클래스이다.
관련된 모든 텍스트 기반 입출력은 이 클래스를 상속받아서 만들어 진다.
문자 기반 스트림은 기존의 바이트 기반 스트림에서
InputStream 을 Reader로, OutputStream을 Writer로 변경하면 사용할 수 있음
Java에서 콘솔과 같은 표준 입출력 장치를 위해 System이라는 표준 입출력 클래스를 정의하고 있다.
java.lang 패키지에 포함되어 있는 System 클래스는 표준 입출력을 위해 다음과 같은 클래스 변수를 제공한다.
System 클래스
클래스 변수 | 입출력 스트림 | 설명 |
---|---|---|
System.in | InputStream | 표준 입력 스트림 / 키보드로 데이터 입력 받음 |
System.out | PrintStream | 표준 출력 스트림 / 모니터로 데이터 출력 시킴 - println() / print() / printf() |
System.err | PrintStream | 표준 에러 출력 스트림 |
표준 출력 스트림
package com.example.testecs;
public class Print {
public static void main(String[] args) {
System.out.print(1);
System.out.println("------");
System.out.println("println");
System.out.print("print와 println 차이");
System.out.printf("%d %d %d %d %d", 1,2,3,4,5);
}
}
1------
println
print와 println 차이
1 2 3 4 5
public void print(String s) {
write(String.valueOf(s));
}
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
public PrintStream printf(String format, Object ... args) {
return format(format, args);
}
파일을 쓸때 FileWriter, BufferedWriter 객체를 이용한다.
여기서 FileWriter 객체를 생성할 때 생성자에 true를 주면 파일 이어서 쓰기가 가능하다
default는 false (false로 하면 파일을 새로 쓰게 됨)
package com.example.testecs;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
public class FileTest {
public static void main(String[] args) throws IOException {
FileWriter fileWriter = new FileWriter("Sunghoon.txt",true);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write("kim"); // 데이터 입력
bufferedWriter.newLine(); // 개행 삽입
bufferedWriter.write("sung");
bufferedWriter.newLine();
bufferedWriter.write("hoon");
bufferedWriter.flush(); // 버퍼 내용 파일에 쓰기
bufferedWriter.close();
// File file = new File("/Users/sunghoon/Desktop/test-ecs/testStream/"+"name");
File file = new File("Sunghoon.txt");
if (file.isFile()){
System.out.println("이미 있음");
}
}
}
파일 크기가 100K를 넘는다면 FileWriter를 단독으로 쓰기보다는 BufferedWriter와 FileWriter를 혼합하여 사용하는 것이 파일을 기록할 때 속도가 더 빠르다.
파일 읽을때 FileReader, BufferedReader 사용한다
FileReader의 생성자에는 파일경로를 작성한다.
지금은 자바프로젝트 경로에 있으므로 파일명만 작성해도 읽어낼 수 있으나, 다른 경로에 있을 경우 상대경로를 입력해주면 된다.
FileReader fileReader = new FileReader("Sunghoon.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);
String s = null;
while ((s = bufferedReader.readLine()) !=null){
System.out.println(s);
}
}
kim
sung
hoon
https://scshim.tistory.com/283
https://dev-coco.tistory.com/27
https://velog.io/@jaden_94/13%EC%A3%BC%EC%B0%A8-%ED%95%AD%ED%95%B4%EC%9D%BC%EC%A7%80-IO