java IO!
Java 입출력
IO |
NIO |
|---|---|
| 스트림방식 Non-buffer | 버퍼방식 |
| 동기방식 | 동기/비동기 모두 지원 |
| 블로킹 방식 | 블록킹/논블록킹 모두 지원 |
IO는 스트림(Stream)이라는 단방향 통로를 생성해서 외부 데이터와 통신, 연결 클라이언트 수가 적고 대용량, 순차처리에 적합
NIO는 채널(Channel)이라는 양방향 통로를 생성해서 외부 데이터와 통신, 연결 클라이언트 수가 많고 소규모 빠른 대응에 적합
https://www.slideshare.net/kslisenko/networking-in-java-with-nio-and-netty-76583794
java io
java IO 에선 입력 출력과정이 각각의 스트림(stream)에서 이루어진다.
스트림은 항상 단방향으로 이루어지기에 서버와 클라이언트간 통신을 위해 입력스트림, 출력스트림 2가지 스트림이 필요하다.
java io 의 특징은 스트림으로 데이터를 전송받기에 버퍼 역할을 하는 저장소에 저장해두지 않으면 데이터 재활용이 불가능하다.
또한 send(), read(), write(), recv(), recvfrom() 등의 메서드를 사용해 커널 버퍼 <-> 유저버퍼 데이터 접근, 읽기, 쓰기를 진행하면 시스템콜이 발생하며
해당 작업을 수행하는 동안 스레드는 블로킹되어 더이상의 코드진행이 불가능하다.
클라이언트 연결이 많으면 빠른 대응을 위해 더 많은 스레드가 생성되고 시스템 부하가 걸릴 확률이 높아진다.
하지만 적은 연결의 경우 최적화된 병렬 연산 진행이 가능하기에 빠른 처리가 가능하다.
입력 스트립
| 문자 스트림 클래스 | 설명 | 바이트 스트림 클래스 |
|---|---|---|
Reader |
문자/바이트 입력 스트림을 위한 추상클래스 | InputStream |
BufferedReader |
문자/바이트 버퍼 입력, 라인 해석 | BufferedInputStream |
LineNumberReader |
문자/바이트 입력 시, 라인 번호를 유지 | LineNumberInputStream |
CharArrayReader |
문자/바이트 배열에서 읽어들임 | ByteArrayInputStream |
InputStreamReader |
바이트 스트림을 문자 스트림으로 변환 | 없음 |
FileReader |
파일에서 바이트로 읽어들어 문자/바이트 스트림으로 변환 | FileInputStream |
FilterReader |
필터적용 문자/바이트 입력을 위한 추상클래스 | FilterInputStream |
PushBackReader |
읽어들인 문자/바이트를 되돌림(Push back) | PushbackInputStream |
PipedReader |
PipedWriter, PipedOutputStream 에서 읽어들임 |
PipedInputStream |
StringReader |
문자열에서 읽어들임 | SgringBUfferedInputStream |
출력 스트림
| 문자 스트림 클래스 | 설명 | 바이트 스트림 클래스 |
|---|---|---|
Writer |
문자 출력 스트림을 위한 추상클래스 | OutputStream |
BufferedWriter |
문자/바이트 스트림에 버퍼출력, | BufferedOutputStream |
CharArrayWriter |
문자/바이트 스트림에 문자/바이트 배열 출력 | ByteArrayOutputStream |
FilterWriter |
필터적용 문자/바이트 출력을 위한 추상클래스 | FilterOuputStream |
OutputStreamWriter |
문자 스트림을 바이트 스트림으로 변환 | 없음 |
FileWriter |
문자/바이트 스트림을 바이트 파일로 변환 | FileOutputStream |
PrintWriter |
Writer/Stream 값과 객체를 프린트 |
PrintStream |
PipedWriter |
PipedReader/PipedOutputStream 에 출력 |
PipedOutputStream |
없음 |
자바 기본형 데이터를 읽을 때 유용 | DataInputStream |
없음 |
자바 기본형 데이터를 출력할 때 유용 | DataOutputStream |
StringWriter |
문자열 출력 | 없음 |
바이트 기반 스트림은 데이터를 주고받을때 기본단위가 1byte. 바이트, 바이트 배열, 정수를 주고받기 편하게 구성됨.
바이트 스트림 최상위 부모는 InputStream, OutputStream 이며 바이트 기반 하위 객체들도 해당 suffix 를 가진다.
문자 기반 스트림은 데이터를 주고받을때 기본단위가 2byte이다. 문자, 문자열, 문자배열을 주고받기 편하게 구성됨.
문자 스트림 최상위 부모는 Reader, Wirter 이며 문자 기반 하위 객체들도 해당 suffix 를 가진다.
보통 텍스트파일은 문자가 들어가있어 문자 스트림으로,
실행파일의 경우 문자가 아닌 byte가 들어있어 바이트 스트림으로,
이미지나 동영상도 마찬가지로 바이트 스트림으로 읽어와야한다.
FileInputStream
InputStream 클래스를 상속하는 파일 입력 클래스
1 | public class FileInputStream extends InputStream {...} |
파일 입력시 사용하는 바이트 스트림으로 사진, 동영상 등의 바이너리 파일을 위의 스트림 객체를 사용해서 IO 할 수 있다.
1 | String path = "자바IO.PNG"; //사진 파일 |
FileInputStream 의 read는 3개로 오버로딩 되어있다
int read()-1byte씩 읽어서 0~255 10진수 int형으로 반환한다.int read(byte[] b)- 배열 크기만큼 읽어 배열에 저장하고 읽어들인 바이트 길이를 반환한다.int read(byte[] b, int off, int len)-offset,len을 사용해 위치지정 가능. 읽은 바이트 길이를 반환한다.
2번째 read 를 사용해 하번에 1024byte 씩 읽어오면 빠른 입출력 스트림 open 횟수가 줄어들면서 빠른 처리가 가능하다.
1 | byte[] buffer = new byte[1024]; |
FileOuputStream
OutputStream 클래스를 상속하는 파일 입출력 클래스
1 | public class FileOutputStream extends OutputStream {...} |
파일명을 인자로 받는 생성자는 2종류가 있다.
1 | FileOutputStream(String name) // defualt append false |
문자열을 파일에 바이트 스트림으로 변경하여 msg.ini 파일에 저장
1 | String message = "안녕하세요"; |
msg.ini 안에는 실행할때마다 “안녕하세요”가 들어간다.
바이트 스트림과 바이트 배열로 문자열 저장이 가능하지만 번거롭다.FileWriter를 사용하면 문자 2byte씩 입력할 수 있기때문에 byte배열로 변경할 필요없이 바로 입력 가능하다.
1 | String message = "안녕하세요"; |
문자열을 입출력 할때는 FileReader/FileWriter를 사용하는것이 효율적이다.
바이너리 파일을 입출력 할때는 FileInputStream/FileOutputStream을 사용하지 않으면 파일이 깨진다.
보조스트림
효율을 위해 일정수준 모아서 처리, 특정 작업을 별도로 하기위해 보조스트림을 사용한다.
데이터를 모았다가 처리하는 버퍼가 있는 BufferedReader, BufferedInputStream
바이트 단위로 저장하는 ByteArrayInputStream, ByteArrayOutputStream
데이터 타입 단위로 저장하는 DataInputStream, DataOutputStream
객체 단위로 저장하는 ObjectInputStream, ObjectOutputStream
등 이 있다.
보조스트림 - ByteArrayInputStream, ByteArrayOutputStream
스트림에서 한번에 모았다 입출력 하는 read(byte[] b)를 사용하는것이 효율적이고 이런 버퍼를 기본적으로 탑재한 보조 스트림 이 존재한다.
입출력 대상이 메모리(바이트 배열) 이므로 close 를 할 필요없다.
1 | public class ByteArrayInputStream extends InputStream { |
바이트 배열을 read해서 지정한 buffer에 저장.
1 | byte [] inSrc = {0,1,2,3,4,5,6,7,8,9}; |
자기 버퍼보다 큰 배열을 write할 경우 2배씩(left shift) 늘려나간다.
writeTo 메서드를 통해 아래처럼 파일에 출력할 수 도 있다.
1 | File fin = new File("uml.png"); |
넓은 크기의 메모리 버퍼를 확보해두고 읽고 쓰기 때문에 속도는 빠르지만 많은 메모리를 필요로 한다.
문자열 전용 보조 스트림으로 CharArrayReader, CharArrayWriter 가 있다.
보조스트림 - PrintWriter
1 | // 쓰기 |
1 | // 읽기 |
PrintWriter는 우리가 자주쓰는 System.out.println() 명령어에서 out에 해당하는 객체이다
출력 위치가 콘솔에서 파일로 변경되었다 FileWriter의 fw.write(String.format(...)) 방식으로 진행해도 똑같다.
위처럼 저장한후 다시 name, kor, avg, gender 등의 변수에 저장한값을 FileReader를 통해 읽어와 보자.
문자열로 저장했기 때문에 파싱하는 과정에서 정규표현식으로 틀을 지정해주어야 한다.
보조스트림 - DataInputStream, DataOutputStream
데이터 -> 문자열 -> 데이터 변경하는 과정에서 파싱이 필요한데DataInputStream, DataOutputStream 을 사용하면 데이터 -> 데이터 형식으로 파일 입출력이 가능하다.
1 | // 쓰기 |
자료형에 따라 write하는 함수명이 다르지만 위의 단순 FileWriter, FileReader 를 사용하는 것 보단 편하다.
보조스트림 - ObjectInputStream, ObjectOutputStream
DataInputStream, DataOutputStream 이 기본 데이터형을 읽고 쓰는 보조스트림이라면ObjectInputStream, ObjectOutputStream 은 객체를 데이터화(직렬화) 시켜서 스트림으로 저장, 읽어올때 사용하는 보조클래스이다.
Student클래스 객체를 만들고 직렬화, 파일 입출력이 가능한지 테스트
이 객체를 student.dat에 저장해보자.
1 |
|
직렬화 시키기 때문에 당연히 바이트 스트림을 사용한다.ObjectOutputStream 보조스트림을 사용해서 writeObject() 메서드를 통해 데이터를 저장.
실제 dat파일을 메모장으로 열면
1 | ы sr days28.Student덾?d? D avgI engZ genderI korI matI totL namet |
이런식으로 저장되어 있다.
직렬화 가능한 클래스로 만드려면 implements Serializable 구현이 필수 오버라이딩해야하는 메서드는 없지만 writeObject 에서 Serializable 구현한 객체만 매개변수로 받는다.
보조스트림 - BufferedReader, BufferedInputStream
이름처럼 별도의 메모리공간 Buffered에 읽은 데이터를 일정 수준까지 보관한다.
1 | public static void fileCopy_byte(String original, String copy) |
버퍼 없이 한글자 읽어 파일에 저장하기 때문에 copy파일에도 한글자씩 저장된다.
파일에 실제 입출력이 계속 반복되기 때문에 비효율 적이다.
효율을 위해서 버퍼를 사용한 BufferedInputStream, BufferedOutputStream을 사용하면 된다.
1 | public static void fileCopy_byte_buffer(String original, String copy) //25830900 |
BufferedOutputStream 에서 파일출력은 write 한다고 바로 파일에 출력되지 않고 입력된 데이터 크기가 기존 가지고 있는 버퍼보다 크거나, 쌓인 데이터 + 입력 데이터가 버퍼보다 크게 될 경우 출력된다(auto flush).
혹은 flush 메서드를 호출하거나 close 하기 직전에도 출력이 이루어진다.
보조스트림 - RandomAccessFile
하나의 클래스로 입출력 모두 가능한 입출력 스트림 클래스.DataInputStream 처럼 자료형에 바로 접근 가능하다.
이름처럼 파일을 순차적으로 읽어오거나 순차적으로 쓰는게 아닌
특정 위치에서 바로 읽기, 쓰기가 가능하다.
기본적으로 순차적으로 접근하는 다른 스트림에 반해 RandomAccessFile은 아무 곳이나 접근 가능하다
파일 시작에서 8byte 마다 영어점수가 입력됨을 알고 있다면 영어 점수 수정시 모든 데이터를 접근할 필요 없이 해당부분만 읽어오거나 덮어쓰기가 가능하다.
생성자에 파일명과 읽기, 쓰기, 실행 mode를 주어야 한다. (rwx)
1 | String s = "I Love normal Java"; |
실행시 random.txt 파일이 만들어지고 안에 I Love normal Java 문자열이 쓰여진다.
1 | String q = "javabook"; |
seek() 메서드를 통해 파일포인터 를 7번째 byte로 이동하고I Love normal Java -> I Love javabookava 로 뒷 부분이 새로운 문자열로 덮어 씌어진다.
1 | try (RandomAccessFile raf = new RandomAccessFile("./random.txt", "rw")) { |
2번째 byte로 이동. 뒤의 문자열을 읽어 출력하면 "I " 가 빠진 "Love javabookava" 이 출력된다.
보통 DataInputStream, DataOutputStream과 같이 쓰이는 경우가 많다.
보조스트림 - InputStreamReader
바이트에서 문자 스트림으로 변환해주는 보조 스트림
1 | InputStream is = System.in; |
대부분의 InputStream 를 구현하는 read 메서드는 1byte를 읽어와 10진수로 반환한다(0 ~ 255).
UTF-8 인코딩을 사용하는 환경에서 키보드로 한글을 입력하면 한 글자가 3byte이기 때문에 read로 읽어오면 앞의 1byte만 잘라오기 때문에 알아볼 수 없다.
이를 온전히 문자단위로 읽어와서 자바에서 사용하는 UTF-16 인코딩으로 바꾸기 위해선InputStreamReader 라는 바이트 스트림을 문자스트림으로 변환해주는 보조스트림이 필요하다.
1 | char[] buffer = new char[BUFFER_SIZE]; |
InputStreamReader 내부에서 StreamDecoder의 read() 메서드를 호출해서 키보드에 친 문자들을 읽는다.3byte 문자를 자동으로 2byte로 변환해서 read()하고 buffer에 저장하는 방식이다.
우리가 예전에 사용했던 BufferedReader 로 키보드입력을 받는것도 InputStreamReader 보조클래스를 사용해서 받아온다.
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
InputStream을 보조스트림인 InputStreamReader 로 받아서 문자형태로 받을수 있도록 하고Buffer가 있는 보조스트림인 BufferedReader 로 받아서 문자열형태로 받을 수 있도록 하여 입력이 빨라진다.
아래 링크에서 사용예제 확인
File 객체
파일의 정보를 갖고있는 File클래스
크기, 속성, 이름, 등의 정보를 메스돌 가져올 수 있음.
파일객체를 통해 디렉토리 생성, 삭제, 등등도 가능하다.
객체 생성은 보통 파일 이름을 문자열로 전달해서 생성한다.
File - separator, separatorChar
OS 마다 사용하는 이름 구분자가 다르다. 윈도우는 \ 리눅스는 /separator, separatorChar static 메서드를 통해 구분자를 가져올 수 있다.
File - isFile, isDirectory, canExecute, canRead, canWrite, length, lastModified
생성된 File 객체가 디렉토리인지 파일인지 boolean 으로 반환
1 | File f1 = new File("C:\\Users\\kgy19\\Desktop\\write_copy.exe"); //file |
1 | File f1 = new File("C:\\Users\\kgy19\\Desktop\\write_copy.exe"); |
1 | Date date = new Date(f1.lastModified()); |
File - getName, getParent, getPath, getCanonicalPath, getAbsolutePath
파일명 얻어오기, 소속 디렉터리명 얻어오기
1 | File f1 = new File("C:\\Users\\kgy19\\Desktop\\write_copy.exe"); |
단 File 객체를 처음에 상대경로로 지정시 getParent 에서 상대경로를 출력한다.
1 | File f1 = new File(".\\src\\write.exe"); |
File f3 = f1.getParentFile(); 현재 디렉토리를 File 객체로 받을 수 도 있다.
1 | File f1 = new File("/Users/user/Documents/java/nio-socket/uml.png"); //file |
f1 의 경우 어떤값을 쓰던 일정하게 절대경로가 출력된다.f2 의 경우 상대경로로 File 객체를 생성했는데 getCanonicalPath를 사용해야 절대경로가 출력된다.
1 | String pathname = "C:\\Class\\JavaClass\\javaPro"; |
출력값
1 | [파일] 2018. 12. 24 오후 11:46:51 .classpath 232 bytes |
File - exists, mkdirs
exist는 파일이 이미 존재하는지 true, false로 반환.
mkdirs는 상위디렉토리까지 모두 생성.
바탕화면 test폴더 안에 20190212(화) 형식으로 폴더를 다시 만들어보자.
날짜를 위해 LocalDate를 사용, 날짜 출렷 포멧을 위해 DateTimeFormatter사용,
File의 생성자에 저장될 폴더 절대경로와 저장할 파일(폴더)명을
parent와 child로 전달.
1 | LocalDate today = LocalDate.now(); |
출력값
1 | 20190212(화) |
기존에 폴더가 존재했다면 폴더를 삭제하고 다시 만든다.
자식 폴더, 파일 모두 출력하기
재귀함수를 사용, 디렉토리면 반복, 파일이면 출력하고 반환하여 파일 디렉토리 목록을 출력
1 | public static void main(String[] arg) { |
출력값
1 | 2018-12-16 15:53오후 FILE 436byte desktop.ini |
io.socket
작성 예정…