I/O
- I/O: Input/Output의 약자로 입력, 출력을 통칭하는 용어다.
- 프로그램에 있는 어떤 내용을 파일에 읽거나 저장할 일이 있을 때 /
다른 서버나 디바이스로 프로그램에 있는 내용을 보낼 일이 있을 때 사용한다. - JVM을 기준으로 읽을 때는 Input, 파일로 쓰거나 외부로 전송할 때 Output이라 한다.
- java.io 패키지 클래스와 여러 종류의 스트림 클래스를 제공한다.
- 읽는 작업은 InputStream
- 쓰는 작업은 OutputStream
- 바이트가 아닌 char 기반의 문자열로만 되어 있는 파일은 Reader와 Writer 클래스로 처리한다.
- Stream : 끊기지 않고 연속적인 데이터를 의미한다.
- 보다 빠른 I/O를 위해 NIO가 추가
- NIO는 스트림 기반이 아닌 버퍼(buffer)와 채널(channel) 기반으로 데이터를 처리한다.
java.io.File / java.nio.Files
- java.io.File 클래스는 파일과 파일의 경로 정보를 포함한다.
- 하지만 정체가 불분명하고 유닉스 계열 파일에서 한계가 존재한다.
- File 클래스를 대체하기 위해 java.nio.Files 가 등장
- File 클래스의 메소드들을 대체하여 제공한다.
- File은 객체를 생성해 데이터를 처리하지만 Files는 모든 메소드가 static 이기에 별도의 객체 생성이 필요 없다.
- File 클래스의 기능
- 생성한 파일의 객체가 가리키는 것이 존재하는지 / 파일인지 경로인지 / 읽기, 쓰기, 실행이 가능한지 / 언제 수정되었는지 / 이름 변경 / 삭제 / 생성 / 전체 경로 확인 기능을 제공한다.
- File 객체가 가리키는 것이 파일이 아닌 경로일 경우에는 파일의 목록을 가져오거나, 경로를 생성 / 삭제하는 기능을 제공한다.
- File 생성자
- 생성자에서 매개 변수로 String 값을 넣을 때 경로가 될 수도 있고 파일 이름이 될 수도 있다.
- 만약 File(String pathname) 생성자에 전체 경로와 파일 이름이 지정되어 있다면 파일을 가리키는 File 객체가 된다.
- 생성자로 URI에 따른 객체도 생성할 수 있다.
URI : Uniform Resource Identifier의 약자로 URI는 인터넷의 리소스 “자원 자체” 를 식별하는 고유한 문자열 시퀀스다.
- 파일의 경로 설정 시 유의할 점
- OS마다 각 디렉토리를 구분하는 기호가 다르다
- 유닉스 계열은 / 사용
- 윈도우는 \ 사용
- 그렇기에 File 클래스의 static 변수인 separator를 사용하는 것이 가장 안전하다.
- String 안에 \를 한 번만 쓰는 경우 그 뒤에 있는 단어에 따라서 "Escape Character"로 인식한다.
- \를 나타내기 위해서는 \\로 두 번 연속으로 사용해야 한다.
- OS마다 각 디렉토리를 구분하는 기호가 다르다
Escape Character : \를 한 번 쓰고 그 뒤에 단어에 따라서 미리 약속한 특수 기호로 인식한다.
\t는 탭, \n은 줄 바꿈 등..
// 윈도우 OS 경로 설정
String pathName1 = "D:\\godofjava\\text";
// 유닉스 계열 OS 경로 설정
String pathName2 = /godofjava/text;
// OS 별 지정된 구분자로 경로 설정
String pathName3 = File.separator + "godofjava" + File.separator + "text";
- File 클래스의 메소드
- 존재하지 않는 디렉토리는 어떻게 만들까?
- File 클래스의 mkdir() / mkdirs() 메소드가 존재한다.
- 둘의 차이점은 mkdir()은 디렉토리 하나만 만들고 mkdirs()는 여러 개의 하위 디렉토리를 만든다는 점이다.
- \godofjava\ 디렉토리만 존재하는데 \godofjava\text\text2 라는 경로를 만들어야 하는 경우
- mkdir()은 하나의 디렉토리 밖에 만들지 못하기 때문에 해당 경로의 디렉토리가 생성되지 않는다.
- mkdirs()는 여러 하위 디렉토리를 만들 수 있기 때문에 text, text2 디렉토리를 만들 수 있다.
- \godofjava\ 디렉토리만 존재하는데 \godofjava\text\text2 라는 경로를 만들어야 하는 경우
- isDirectory() : File 객체가 경로, 디렉토리인지 확인한다.
- isFile() : File 객체가 파일인지 확인한다.
- isHidden() : File 객체가 숨긴 파일인지 확인한다.
- canRead() : File 객체를 읽을 수 있는지 확인한다.
- canWrite() : File 객체에 쓸 수 있는지 확인한다.
- canExecute() : File 객체를 실행할 수 있는지 확인한다.
- lastModified() : 파일이나 경로가 언제 생성되었는지 확인하며 long 타입으로 시간을 리턴해준다.
- delete() : 파일을 삭제하기 위해 사용한다. 정상적으로 삭제한 경우 true를 리턴한다.
- createNewFile() : 새로운 파일을 생성한다. 매개 변수로 경로와 파일 명을 지정하고 생성 시 true를 리턴한다.
- IOException을 예외로 발생시키기에 사용 시 try-catch로 묶어줘야한다.
- Absolute와 Canonical 차이
- getCanonicalPath() : 전체 경로를 String으로 리턴하며 절대적으로 유일하게 표현할 수 있는 절대 경로를 리턴한다.
- getAbsolutePath() : 전체 경로를 String으로 리턴하지만 현재 디렉토리를 기준으로 대상의 상대 경로를 리턴한다.
- getParent() : 파일 이름을 제외한 경로만 출력한다.
- 존재하지 않는 디렉토리는 어떻게 만들까?
- File 클래스의 list 메소드
- static File[] listRoots() : OS에서 사용중인 파일 시스템의 루트 디렉토리 목록을 File 배열로 리턴한다.
- String[] list() : 현 디렉토리의 하위에 있는 목록을 String 배열로 리턴한다. 매개 변수로 조건을 지정하여 조건에 맞는 목록만 받아올 수 있다.
- File[] listFiles() : 현 디렉토리 하위 목록을 File 배열로 리턴한다. 매개 변수로 조건을 넘겨줄 수 있다.
- list 메소드에서 조건은 FileFilter / FilenameFilter 인터페이스의 accept() 메소드를 구현함으로 파일 확장자와 같은 것들을 조건으로 지정할 수 있다. (.jpg, .mp3 ....)
- 두 Filter 인터페이스는 모두 넘어온 File과 관련된 정보를 이용하여 파일을 필터링할 목적으로 사용된다.
- FileFilter 인터페이스에는 accept(File pathName)으로 선언되어 있는 메소드가 있다.
- FilenameFilter 인터페이스에는 accept(File dir, String name) 으로 선언되어 있는 메소드가 있다.
- FilenameFilter는 매개 변수로 파일 명을 받을 수 있으며 넘어온 File 객체가 파일인지 경로인지 구분할 수 없다.
InputStream / OutputStream
InputStream
- abstract 클래스다. (= 자식 클래스를 통해 읽으면 된다.)
- Closeable 인터페이스를 구현하고 있다.
- close() 메소드만 선언되어 있다.
- Closeable 인터페이스를 구현한다는 것은 어떤 리소스를 열었던 간에 close() 메소드로 닫아야 한다는 것을 의미한다.
- java.io 패키지에 있는 클래스를 사용할 때는 해당 리소스를 다른 클래스에서도 작업할 수 있도록 하던 작업이 종료되면 close() 메소드로 "항상" 닫아줘야 한다.
- 리소스 : 파일이 될 수도, 네트워크 연결이 될 수도 있으며 스트림을 통해서 작업할 수 있는 모든 것을 의미.
- InputStream 클래스의 메소드
- int available() : 스트림에서 중단없이 읽을 수 있는 바이트 개수를 리턴한다.
- void mark(int readlimit) : 스트림의 현재 위치를 표시한다. 매개 변수는 표시해 둔 자리의 최대 유효 길이로 값이 넘어가면 표시해 둔 자리는 더 이상 의미가 없다.
- void reset() : 현재 위치를 mark() 메소드가 호출되었던 위치로 되돌린다.
- boolean markSupported() : mark()나 reset() 메소드가 수행 가능한지 확인한다.
- abstract int read() : 스트림에서 다음 바이트를 읽는다.
- int read(byte[] b) : 매개 변수 바이트 배열에 데이터를 담는다. 다른 매개 변수들을 통해 특정 위치부터 지정한 길이만큼의 데이터를 담을 수도 있다.
- long skip(long n) : 지정한 길이만큼 데이터를 건너 뛴다.
- void close() : 스트림에서 작업중인 대상을 해제한다. 이 메소드 이후 다른 메소드를 사용해 데이터를 처리할 수 없다.
- 스트림 다룰 때 close() 메소드 호출은 선택이 아닌 필수!!!
OutputStream
- InputStream과 마찬가지로 abstract 클래스이며 Closeable 인터페이스를 구현하고 있다.
- 추가적으로 Flushable 인터페이스를 구현하고 있으며 flush() 메소드만 선언하고 있다.
- OutputStream 클래스의 메소드
- void write(byte[] b) : 매개 변수 바이트 배열을 저장한다. 특정 위치에서 길이만큼만 저장할 수도 있다.
- abstract void write(int b) : 매개 변수로 받은 바이트를 저장한다. 타입은 int지만 실제 저장되는 것은 바이트로 저장된다.
- void flush() : 버퍼에 쓰려고 대기하고 있는 데이터를 강제로 쓰도록 한다.
- 일반적으로 어떤 리소스에 데이터를 쓸 때 "요청할 때마다 저장"하는 방식으로 매번 쓰기 작업 시 효율이 좋지 않다.
- 그렇기에 대부분 저장할 때 버퍼를 통해 데이터를 쌓아 두었다가 어느정도 차게 되면 한 번에 쓰는 것이 좋다.
- 이렇게 "현재 버퍼에 저장되어 있는 데이터를 기다리지 않고 무조건 저장"하도록 하는 명령어가 flush()
- void close() : 쓰기 위해 열었던 스트림을 해제한다.
Reader / Writer
- Stream은 byte를 다루기 위함이라면 Reader / Writer는 char 기반의 문자열을 다루기 위한 클래스.
- Reader 클래스의 메소드
- boolean ready() : Reader에서 작업할 대상이 읽을 준비가 되었는지 확인한다.
- int read() : 하나의 char를 읽는다.
- int read(char[] cbuf) : 지정한 char 배열에 데이터를 담는다. 담은 데이터의 개수를 리턴한다. 특정 위치부터 지정한 길이만큼의 데이터를 담을 수도 있다.
- int read(CharBuffer target) : 매개 변수 CharBuffer 객체에 데이터를 담는다.
- long skip(long), abstract void close(), boolean markSupported(), void mark(int), void reset() : InputStream의 메소드와 동일하다.
- Writer 클래스의 메소드
- Writer append(char c) : 매개 변수로 넘어온 char를 추가한다.
- Writer append(CharSequence csq) : 매개 변수 CharSequence를 추가한다. 시작 위치와 끝 위치를 지정할 수 있다.
- void write(), abstract void flush(), abstract void close() : OutputStream의 메소드와 동일하다.
CharSequence : 인터페이스로 String, StringBuilder, StringBuffer 클래스에서 구현하고 있다.
매개 변수로 CharSequence를 넘긴다는 것은 문자열을 받아서 처리한다는 의미.
- char 기반의 내용을 파일로 쓰기 위해서 FileWriter 클래스를 사용한다.
- 생성자로 FileDescriptor 객체를 받기도 한다.
- FileDescriptor 클래스는 리눅스 혹은 유닉스 계열의 시스템에서 프로세스가 파일을 다룰 때 사용하는 것이다.
- Writer에 있는 write(), append() 사용 시 메소드 호출할 때마다 파일에 데이터를 쓰기 때문에 매우 비효율적이다.
- BufferedWriter 클래스를 사용해 매개 변수로 FileWriter를 넘겨주면 버퍼 공간에 저장할 데이터를 보관했다가 버퍼가 차면 데이터를 저장함으로 매우 효율적인 저장이 가능하다.
- FileWriter 객체 생성 시 IOException이 발생할 수 있는 상황
- 매개 변수로 넘어온 파일 이름이 파일이 아닌 경로를 의미할 경우
- 해당 파일이 존재하지는 않지만 권한등의 문제로 생성할 수 없는 경우
- 파일이 존재하지만 파일을 열 수 없는 경우
- FileWriter / BufferedWriter 객체를 try문 안에서 선언하면 finally에서 close() 메소드를 호출할 수 없다!!
- 변수들을 try 중괄호 안에서 선언하면 catch / finally / try-catch 문장 밖에서 참조하려 할 때 컴파일 에러가 발생한다.
- 따라서 try 문장 전에 변수를 선언해야 한다.
- finally에서 close()를 호출하는 이유?
- try 문장 마지막에 호출하는 경우 try 중간에 예외가 발생하면 close() 처리하지 못하는 경우가 발생한다.
- 그렇기에 catch 블록 각각에서 close()를 처리해주거나 finally 블록에서 close()를 처리해줘야 한다.
- 스트림 객체를 close() 할 때 주의사항은?
- FileWriter, BufferedWriter 순서로 객체를 생성했다면 생성한 역순으로 객체를 close() 해줘야 한다.
- 마지막에 생성한 객체를 먼저 닫아준다.
- 변수들을 try 중괄호 안에서 선언하면 catch / finally / try-catch 문장 밖에서 참조하려 할 때 컴파일 에러가 발생한다.
- FileWriter 생성자
- 생성자로 true를 넘겨줄 경우 기존 파일 끝에 새로운 내용이 추가된다. (붙여쓰기)
- false를 넘겨줄 경우 기존 파일 내용은 없어지고 새로운 내용이 작성된다. (덮어쓰기)
- Scanner 클래스
- 텍스트 기반의 기본 자료형이나 문자열 데이터를 처리하기 위한 클래스
- 정규 표현식을 사용해 데이터를 잘라 처리할 수 있다.
간단 내용 정리
1. I/O는 각각 무엇의 약자인가요?
Input, Output
2. java.io.File 클래스는 파일만 지정할 수 있나요?
X, 파일과 경로를 나타낼 수 있다.
3. OS 마다 다른 경로 구분자를 처리하기 위해서는 File 클래스의 어떤 상수를 사용해야 하나요?
separator
4. File 클래스에서 디렉터리를 만드는 mkdir()과 mkdirs() 메소드의 차이는 무엇인가요?
mkdir()은 하나의 디렉토리만 생성 가능하며 mkdirs()는 하위 디렉토리 여러 개를 만들 수 있다.
5. File 클래스의 list() 메소드와 listFiles() 메소드의 차이는 무엇인가요?
list()는 String 배열로, listFiles()는 File 배열로 리턴한다.
6. FileFilter와 FilenameFilter의 차이는 무엇인가요?
두 Filter 인터페이스는 모두 넘어온 File과 관련된 정보를 이용하여 파일을 필터링할 목적으로 사용된다.
FileFilter 인터페이스에는 accept(File pathName)으로 선언되어 있는 메소드가 있고,
FilenameFilter 인터페이스에는 accept(File dir, String name) 으로 선언되어 있는 메소드가 있다는 점이 다르다.
FilenameFilter는 매개 변수로 넘어온 File 객체가 파일인지 경로인지 구분할 수 없다.
7. InputStream 이라는 abstract 클래스는 어떤 작업을 하기 위해서 만들어 진 것인가요?
byte로 된 데이터를 다루기 위해서(읽기 위해서)
8. OutputStream 이라는 abstract 클래스는 어떤 작업을 하기 위해서 만들어 진 것인가요?
byte로 된 데이터를 출력하기 위해서(쓰기 위해서)
9. Reader 라는 abstract 클래스는 어떤 작업을 하기 위해서 만들어 진 것인가요?
char 기반 문자열로 된 데이터를 다루기 위해서(읽기 위해서)
10. Writer 라는 abstract 클래스는 어떤 작업을 하기 위해서 만들어 진 것인가요?
char 기반 문자열로 된 데이터를 출력하기 위해서(쓰기 위해서)
11. BufferedReader나 BufferedWriter를 사용하는 이유는 무엇인가요?
write()나 read() 메소드를 호출할 때마다 파일에 읽고 쓰기하면 매우 비효율적이므로 버퍼에 저장해 놨다가 버퍼가 차게 되면 데이터를 저장하도록 도와줌으로 매우 효율적인 저장이 가능해진다.
12. 파일을 읽고, 문자열을 처리하기 위해서 필요한 Scanner 클래스가 속해있는 패키지는 무엇인가요?
java.util 패키지
출처 : 이상민, <자바의 신 VOL.2> 로드북
'Java > 자바의 신 VOL.2' 카테고리의 다른 글
10. 다른 서버로 데이터를 보내려면 어떻게 하면 되나요? (1) | 2024.07.05 |
---|---|
09. Serializable과 NIO도 살펴봅시다 (0) | 2024.07.03 |
07. 쓰레드는 개발자라면 알아두는 것이 좋아요 (1) | 2024.07.03 |
06. 자바랭 다음으로 많이 쓰는 애들은 컬렉션 - Part3(Map) (0) | 2024.06.28 |
05. 자바랭 다음으로 많이 쓰는 애들은 컬렉션 - Part2(Set과 Queue) (0) | 2024.06.28 |