Java
I/O
I/O는 Input, Output의 약자로 입력과 출력을 통칭하는 용어이다.
JVM을 기준으로 읽을때는 Input, 파일을 쓰거나 외부로 전송할 때는 Output 이라한다.
자바는 바이트 기반의 데이터를 처리하기 위해 스트림을 제공하고, I/O 처리를 한다.
바이트가 아닌, char 기반의 문자열로만 되어 있는 파일은 Reader와 Writer 라는 클래스로 처리한다.
JDK 1.4부터 NIO가 추가되어서 스트림 기반이 아니라, 버퍼와 채널 기반으로 데이터를 처리한다.
스트림
자바에서 스트림은 끊기지 않고 연속적인 데이터를 말한다.
File
java.io.File 클래스는 파일만 가리키는 것이 아닌 파일의 경로도 포함한다.
File 클래스는 심볼릭 링크와 같은 유닉스 계열의 파일에서 사용하는 몇몇 기능을 제대로 제공하지 못한다.
JDK 7에서 NIO2가 등장하며 java.nio.file.Files 에서 File 클래스에 있는 메서드를 대체하여 제공한다.
File 클래스는 객체를 생성하여 데이터를 처리하지만, Files 클래스는 모든 메서드가 static 으로 되어 있기 때문에, 별도의 객체 생성이 필요 없다.
File클래스는 파일 및 경로 정보를 통제하기 위한 클래스이다. File은 생성한 파일 객체가 가리키고 있는 것이
- 존재하는지,
- 파일인지 경로인지,
- 읽거나, 쓰거나, 실행할 수 있는지
- 언제 수정되었는지
를 확인하는 기능과 해당 파일의
- 이름을 바꾸고,
- 삭제하고,
- 생성하고,
- 전체 경로를 확인
하는 등의 기능을 제공한다.
File 객체가 가리키는 것이 파일이 아닌 경로일 경우 해당 경로에 있는
- 파일의 목록을 가져오거나,
- 경로를 생성하고,
- 경로를 삭제하는
등의 기능도 있다.
File 생성자
- File(File parent, String child) : 이미 생성되어 있는 File 객체와 그 경로의 하위 경로 이름으로 새로운 File 객체를 생성한다.
- File(String pathname) : 지정한 경로 이름으로 file 객체를 생성한다.
- File(String parent, String child) : 상위 경로와 하위 경로로 File 객체를 생성한다.
- File(URI uri) : URI에 따른 File 객체를 생성한다.
child 값은 경로가 될 수도 있고, 파일 이름도 될수 있다.
pathname에는 전체 경로 + 파일이름이 지정될 수도 있다.
URI는 Uniform Resource Identifier의 약자로, 리소스를 가리키기 위한 경로를 뜻한다.
주요 메서드
- mkdir() : 경로에 디렉토리가 존재하지 않다면 단일 폴더 생성
- mkdirs() : 경로에 디렉토리가 존재하지 않다면 여러 개 하위 디렉토리 생성
- isDirectory() : 경로에 지정된 것이 디렉토리인가?
- isFile() : 경로에 지정된 것이 파일인가?
- isHidden() : 숨김 파일인가?
- canRead() : 읽을 수 있는 권한이 있나?
- canWrite() : 쓸수 있는 권한이 있나?
- canExecute() : 실행할 수 있는 권한이 있나?
- lastModified() : 파일이나 경로가 언제 생성되었는지를 확인, long 타입의 시간 리턴
- createNewFile() : 폴더가 아닌 파일을 생성
- getName() : 파일일 경우 파일이름, 경로는 전체 경로 String 리턴
- getPath() : 전체 경로 String 리턴
- getAbsolutePath() : 절대경로 String 리턴
- getAbsoluteFile() : 절대경로에 위치한 File 리턴
- getCanonicalPath() : 상대경로 String 리턴
- getCanonicalFile() : 상대경로에 위치한 File 리턴
- getParent() : 현재 파일이름을 제외한 경로 String 리턴
- static File[] listRoots() : JVM이 수행되는 OS에서 사용중인 파일 시스템의 루트 디렉터리 목록을 File 배열로 리턴한다.
- String[] list() : 현재 디렉터리의 하위에 있는 목록을 String 배열로 리턴한다.
- String[] list(FilenameFilter filter) : 현재 디렉터리의 하위에 있는 목록 중, 매개 변수로 넘어온 filter의 조건에 맞는 목록을 String 배열로 리턴한다.
- File[] listFiles() : 현재 디렉터리의 하위에 있는 목록을 File 배열로 리턴한다.
- File[] listFiles(FileFilter filter) : 현재 디렉터리의 하위에 있는 목록 중, 매개 변수로 넘어온 filter의 조건에 맞는 목록을 File 배열로 리턴한다.
- File[] listFiles(FilenameFilter filter) : 현재 디렉터리의 하위에 있는 목록 중, 매개 변수로 넘어온 filter의 조건에 맞는 목록을 File 배열로 리턴한다.
FilenameFilter
public interface FilenameFilter {
boolean accept(File dir, String name);
}
- 매개변수로 넘어온 경로나 파일 이름이 조건에 맞는지 확인한다.
- file 객체를 제공해 주기 때문에, 파일인지, 디렉터리인지 구분이 가능하다.
FileFilter
public interface FileFilter {
boolean accept(File pathname);
}
- 매개변수로 넘어온 file 객체가 조건에 맞는지 확인한다.
필터는 위 두개의 인터페이스를 구현하여 사용자가 사용한다.
InputStream & OutputStream
자바의 IO는 기본적으로 InputStream과 OutputStream이라는 추상클래스를 통해 제공된다.
Stream으로 읽을 수 있는 것은 리소스들인데, 리소스는 파일이 될 수도 있고, 네트워크 연결도 될 수도 있다.
InputStream
- int available() : 스트림에서 중단 없이 읽을 수 있는 바이트 개수를 리턴한다.
- void mark(int readlimit) : 스트림의 현재 위치를 표시한다. 여기서 매개변수로 넘긴 int 값은 표시에 둔 자리의 최대 유효값이다. 이 값이 넘어가면, 표시해둔 자리는 더 이상 의미가 없어진다.
- void reset() : 현재 위치를 mark() 메서드가 호출되었던 위치로 되돌린다.
- boolean markSupported() : mark()나 reset() 메서드가 수행 가능한지를 확인한다.
- abstract int read() : 스트림에서 다음 바이트를 읽는다.
- int read(byte[] b) : 매개변수로 넘어온 바이트 배열에 데이터를 담는다. 리턴값은 데이터를 담은 개수이다.
- int read(byte[] b, int off, int len) : 매개변수로 넘어온 바이트 배열에 특정 위치(off)부터 지정한 길이(len) 만큼의 데이터를 담는다. 리턴값은 데이터를 담는 개수이다.
- long skip(long n) : 매개변수로 넘어온 길이(n) 만큼의 데이터를 건너 뛴다.
- void close() : 스트림에서 작업중인 대상을 해제한다. 이 메서드를 수행한 이후에는 다른 메서드를 사용하여 데이터를 처리할 수 없다.
데이터를 읽을 때는 read(), 리소스를 닫을 때는 close() 메서드를 호출하면 된다.
InputStream을 상속받은 주요 스트림은 다음과 같다.
- FileInputStream : 파일을 읽는 데 사용한다. 주로 쉽게 읽을 수 있는 텍스트 파일을 읽기 위한 용도보다 이미지와 같이 바이트 코드로 된 데이터를 읽을 때 사용한다.
- FilterInputStream : 다른 입력 스트림을 포괄하며, 단순히 InputStream 클래스가 재정의 되어 있다.
- ObjectInputStream : ObjectOutputStream 으로 저장한 데이터를 읽는데 사용한다.
FileInputStream과 ObjectInputStream은 객체를 생성해서 데이터를 처리하면 된다. FilterInputStream 클래스 생성자는 protected로 선언되어 있기 때문에, 상속 받은 클래스에서만 객체를 생성할 수 있다.
OutputStream
OutputStream은 InputStream과 동일하게 Closeable을 상속받지만, Flushable 인터페이스도 추가로 상속받는다.
어떤 리소스에 데이터를 쓸 때, 매번 쓰기 작업을 요청할 때마다 저장 하면 효율이 안좋아진다.
따라서, 저장할 때 버퍼를 갖고 데이터를 쌓다가, 한번에 쓰는 것이 좋다.
그러한 버퍼를 사용할 때 flush()는 버퍼에 쓰려고 대기하고 있는 데이터를 강제로 쓰도록 한다.
- void write(byte[] b) : 매개 변수로 받은 바이트 배열을 저장한다.
- void write(byte[] b, int off, int len) : 매개 변수로 받은 바이트 배열의 특정 위치(off) 부터 지정한 길이(len) 만큼 저장한다.
- abstract void write(int b) : 매개 변수로 받은 바이트를 저장한다. 타입은 int 이지만, 실제 저장되는 것은 바이트로 저장된다.
- void flush() : 버퍼에 쓰려고 대기하고 있는 데이터를 강제로 쓰돌독 한다.
- void close() : 쓰기 위해 열은 스트림을 해제한다.
Reader와 Writer
Stream은 byte를 다루기 위한 것이고, Reader와 Writer는 char 기반의 문자열을 처리하기 위한 래스이다. 즉, 텍스트 에디터로 쉽게 볼수 있는 파일들을 처리하기 위한 클래스이다.
Reader
- boolean ready() : Reader에서 작업할 대상이 읽을 준비가 되어 있는지를 확인한다.
- void mark(int readAheadLimit) : Reader의 현재 위치를 표시해둔다.
- void reset() : 현재 위치를 mark() 메서드가 호출되었던 위치로 되돌린다.
- boolean markSupported() : mark()나 reset() 메서드가 수행 가능한지를 확인한다.
- int read() : 하나의 char를 읽는다.
- int read(char[] cbuf) : 매개 변수로 넘어온 char 배열에 데이터를 담는다. 리턴 값은 데이터를 담은 개수이다.
- abstract int read(char[] chuf, int off, int len) : 매개 변수로 넘어온 char 배열에 특정 위치(off)부터 지정한 길이(len) 만큼의 데이터를 담는다. 리턴값은 데이터를 담은 개수이다.
- int read(CharBuffer target) : 매개변수로 넘어온 CharBuffer 클래스의 객체에 데이터를 담는다. 리턴값은 데이터를 담은 개수이다.
- long skip(long n) : 매개 변수로 넘어온 개수 만큼의 char를 건너 뛴다.
- abstract void close() : Reader에서 작업중인 대상을 해제한다. 이 메서드를 수행한 이후에는 다른 메서드를 사용하여 데이터를 처리할 수 없다.
Reader를 상속한 주요 클래스 다음과 같다.
- BufferedReader
- InputStreamReader
Writer
Appendable 인터페이스를 구현하여 append() 메서드가 존재한다. 문자열을 추가할 수 있다.
- Writer append(char c) : 매개 변수로 넘어온 char를 추가한다.
- Writer append(CharSequence csq) : 매개변수로 넘어온 CharSequence를 추가한다.
- Writer append(CharSequence csq, int start, int end) : 매개변수로 넘어온 CharSequence를 추가하며 쓰여지는 해당 문자열의 시작 위치(start)와 끝 위치(end)를 지정한다.
- void write(char[] cbuf) : 매개변수로 넘어온 char의 배열을 추가한다.
- abstract void write(char[] cbuf, int off, int len) : 매개변수로 넘어온 char의 배열을 특정 위치(off)부터 특정 길이(len) 만큼을 추가한다.
- void write(int c): 매개변수로 넘어온 int 값에 해당하는 char를 추가한다.
- void write(String str) : 매개 변수로 넘어온 문자열을 쓴다.
- void write(String str, int off, int len) : 매개 변수로 넘어온 문자열을 추가하며, 쓰여지는 해당 문자열의 시작 위치(start)와 끝 위치(end)를 지정하면 된다.
- abstract void flush() : 버퍼에 있는 데이터를 강제로 대상 리소스에 쓰도록 한다.
- abstract void close() : 쓰기 위해 열은 스트림을 해제한다.
텍스트 파일 써보기
자바에서 char 기반의 내용을 파일로 쓰기 위해선 FileWriter 클래스를 사용한다.
FileWriter 생성자
- FileWriter(File file) : File 객체를 매개변수로 받아 객체를 생성한다.
- FileWriter(File file, boolean append) : FIle 객체를 매개변수로 받아 객체를 생성한다. append 값을 통하여 해당 파일 뒤에 붙일지(append = true), 해당 파일을 덮어 쓸지 (append = false)를 정한다.
- FileWriter(FileDescriptor fd) : FileDescriptor 객체를 매개변수로 받아 객체를 생성한다.
- FileWriter(String fileName) : 지정한 문자열의 경로와 파일 이름에 해당하는 객체를 생성한다.
- FIleWriter(String fileName, boolean append) : 지정한 문자열의 경로와 파일 이름에 해당하는 객체를 생성한다. append 값에 따라서, 데이터를 추가할지, 덮어쓸지를 정한다.
Writer에 있는 write()나 append() 메서드를 사용하여 데이터를 쓰면, 메서드를 호출할 때 마다 파일에 쓰기 때문에 매우 비효율적이다. 이러한 단점을 보완하기 위해 BufferedWriter 클래스가 있다.
- BufferedWriter(Writer out) : Writer 객체를 매개 변수로 받아 객체를 생성한다.
- BufferedWriter(Writer out, int size) : Writer 객체를 매개변수로 받아 객체를 생성한다. 두번째 매개변수에 있는 size를 사용하여, 버퍼의 크기를 정한다.
BufferedWriter는 버퍼라는 공간에 저장할 데이터를 보관해두었다가, 버퍼가 차면 데이터를 저장하도록 도와준다. 즉, 효율적인 저장이 가능하다.
BufferReader 대신 Scanner 클래스를 사용할 수도 있다.
Scanner 클래스는 텍스트 기반의 기본 자료형이나, 문자열 데이터를 처리하기 위한 클래스이고, 정규 표현식을 사용하여 데이터를 잘라 처리할 수도 있다.
Serializable
개발하다 보면 생성한 객체를 파일로 저장해야 할 수도 있고, 저장할 객체를 읽어야 할 수도 있다. 그리고 객체를 다른 서버로 보내거나, 다른 서버에서 받을 수도 있다.
이럴 때 필요한 것이 Serializable 이다. Serializable 인터페이스를 구현하면 JVM에서 해당 객체는 저장하거나 다른 서버로 전송할 수 있도록 해준다.
즉, Serializable은 객체의 상태를 바이트 스트림으로 변환하거나, 바이트 스트림을 객체의 상태로 역직렬화하는데 사용되는 인터페이스이다.
해당 인터페이스를 구현한 클래스는 직렬화가 가능하고, 파일에 저장하거나, 네트워크를 통해 전송할 수 있다.
객체가 직렬화되면 해당 객체의 필드 데이터와 클래스 정보가 포함된 바이트스트림이 생성된다.
Serializable 인터페이스를 구현한 후에는, serialVersionUID 값을 지정해주는 것을 권장한다. 지정하지 않으면, 자바 소스가 컴파일 할 때 자동으로 생성된다.
static final long으로 선언해야 하며, 변수명도 serialVersionUID로 선언해야 한다.
위 값은 해당 객체의 버전을 명시하는데 사용된다.
자바에서 ObjectOutputStream, ObjectInputStream을 사하면 객체를 저장하거나, 읽을 수 있다.
객체를 저장하기 위해선 해당 객체의 클래스가 Serializable 인터페이스를 구현하고 있어야한다.
또한 해당 클래스의 변수가 추가되었을 때는 UID가 달라지기 떄문에 예외가 발생하며 읽을 수 없다.
UID를 명시적으로 지정하면 변수가 변경되더라도 예외는 발생하지 않는다.
transient
transient 키워드를 붙여 변수를 선언하면 해당 변수는 Serializable의 대상에서 제외된다.
즉, 필요없는 변수나, 전송되면 안되는 변수를 설정할 수 있다.
NIO
NIO가 생긴 이유는 속도 때문이다.
NIO는 스트림을 사용하지 않고 채널과 버퍼를 사용한다.
채널은 물건을 중간에서 처리하는 도매상, 버퍼는 소매상으로 생각하면 된다.
즉 우리는 도매상과 이야기 할일은 거의 없다.
NIO의 Buffer
Buffer의 주요 정보는 다음과 같다.
- capacity : 버퍼의 최대 저장 용량
- position : 다음 읽기 또는 쓰기 작업이 시작될 위치를 나타낸다.
- Limit : 현재 읽기 또는 쓰기 작업이 가능한 범위를 제한합니다.
- mark : 임시 위치를 표시하고 나중에 해당 위치로 돌아가는데 사용됩니다.
Buffer의 주요 메서드는 다음과 같다.
- int capacity() : 버퍼에 담을 수 있는 크기 리턴
- int limit() : 버퍼에서 읽거나 쓸 수 없는 첫 위치 리턴
- int position() : 현재 버퍼의 위치 리턴
버퍼에 데이터를 담거나, 읽는 작업을 수행하면 현재의 ‘위치’가 이동한다. 그래야 그 다음 ‘위치’에 있는 것을 바로 쓰거나, 읽을 수 있다.
- Buffer flib() : limit 값을 현재 position으로 지정한 후, position을 0(가장 앞)으로 이동
- Buffer mark() : 현재 position mark
- Buffer reset() : 버퍼의 position을 mark 한 곳으로 이동
- Buffer rewind() : 현재 버퍼의 position을 0으로 이동
- int remaining() : limit-position 계산 결과를 리턴
- boolean hasRemaining() : position와 limit 값에 차이가 있을 경우 true를 리턴
- Buffer clear() : 버퍼를 지우고 현재 position을 0으로 이동하며, limit 값을 버퍼의 크기로 변경
NIO는 단지 파일을 쓰고 읽을 때만 사용하는 것이 아닌 파일 복사 및 네트워크로 데이터를 주고받을 때 사용할 수 있다.
'Back-end' 카테고리의 다른 글
SOLID 원칙, 객체지향 프로그래밍 (0) | 2023.08.22 |
---|---|
2023.08.21 TIL (0) | 2023.08.21 |
2023.08.20 TIL (0) | 2023.08.20 |
2023.08.19 TIL (0) | 2023.08.19 |
static lazy 로딩 (0) | 2023.08.19 |