I/O
I/O는 프로그램에 있는 내용을
- 파일에 읽거나 저장할 일이 있을 때
- 다른 서버나 디바이스로 보낼 일이 있을 때
사용
JVM을 기준으로 읽을 때에는 Input, 파일로 쓰거나 외부로 전송할 때에는 Output이라는 용어를 사용
java.io 패키지에서
바이트 기반의 데이터를 처리하기 위해서 Stream이라는 클래스를 제공
읽는 작업은 InputStream, 쓰는 작업은 OutputStream을 통해 작업
char 기반의 문자열로만 되어 있는 파일은 Reader와 Writer 클래스로 처리
Stream ??
끊기지 않고 연석적인 데이터를 말한다
JDK 1.4부터 보다 빠른 I/O처리를 위해 NIO 추가
NIO는 스트림 기반이 아니라, 버퍼와 채널기반으로 데이터를 처리
Java 7에서 NIO2 추가
File and Files
java.io 패키지에 File 클래스 존재
파일만 가리키는 것이 아니라, 파일의 경로 정보도 포함
File 클래스는 정체가 불분명하고, 유닉스 계열의 파일에서 사용하는 몇몇 기능을 제대로 제공하지 못함
So, Java 7부터 NIO2를 통해
java.nio.file 패키지에 있는 Files 클래스에서 File 클래스의 메소드들을 대체하여 제공함
File 클래스는 객체를 생성하여 데이터를 처리하고
Filse 클래스는 모든 메소드가 static으로 선언되어, 객체를 생성할 필요 없음
File
파일 및 경로 정보를 통제하기 위한 클래스
생성한 파일 객체가 가리키고 있는 것이
- 존재하는지
- 파일인지 경로인지
- 일거나, 쓰거나, 실행할 수 있는지
- 언제 수정되었는지
를 확인하는 기능 and 파일의
- 이름을 바꾸고
- 삭제하고
- 생성하고
- 전체 경로를 확인
하는 등의 기능 제공
File 객체가 가리키는 것이 파일이 아닌 경로일 경우, 해당 경로에 있는
- 파일의 목록을 가져오거나
- 경로를 생성하고
- 경로를 삭제하는
등의 기능도 있음
File 클래스의 생성자
생성자 | 설명 |
File(File parent, String child) | 이미 생성되어 있는 File 객체(parent)와 그 경로의 하위 경로 이름(child)로 새로운 File 객체를 생성 |
File(String pathname) | 지정한 경로 이름으로 File 객체를 생성 |
File(String parent, String child) | 상위 경로(parent)와 하위 경로(child)로 File 객체를 생성 |
File(URI uri) | URI에 따른 File 객체를 생성 |
child 값은 경로 or 파일 이 될 수 있음
So, File(String pathname) 생성자는 경로만 지정하는것이 아님
만약 전체 경로와 파일이름이 pathname에 지정되어 있다면 파일을 가리키는 File 객체가 됌
File 클래스를 이용한 파일 경로 and 파일 상태 확인
pathName의 문자열에 파일의 경로를 적어서 파일객체의 매개 변수로 넘겨주어 경로를 가리키는 파일객체 file이 생성됨
그리고 그 파일객체 file에서 File클래스의 exists() 메소드를 호출해 file객체가 가리키는 경로가 존재하는지 확인
하나의 문제점이 있음
OS 마다 각 디렉터리를 구분하는 기호가 다름 여기서는 역 슬레시로 각 디렉터리를 구분했지만 다른 OS
예를 들어 유닉스의 경우 윈도우와 디렉터리를 구분하는 기호가 다름
이러한점을 해결하기 위해
File 클래스에 static 변수로 존재하는 separator을 사용해야 함
위 소스코드의 실행 결과는 false가 나옴
왜냐하면, 해당 디렉터리가 존재하지 않기 때문
So, 디렉터리를 만들어 주어야 하는데
이를 File 클래스를 사용해서 만들어 주려면 mkdir()이나 mkdirs() 메소드를 사용하면 됌
만들어줄 디렉터리의 경로를 받아 생성한 파일객체 file에서 File 클래스에서 제공하는 mkdir() 메소드를 사용해서
file객체가 가리키는 디렉터리를 만듬
여기서 mkdir()... mkdirs()??
mkdir()은 디렉터리를 하나만 만들고
mkdirs()는 여러 개 만드는데
예를 들자면 C:\A 라는 경로만 존재할 경우
mkdir()를 사용하면 한 번에 C:\A\B까지 만드는게 한계
But
mkdirs()를 사용하면 한 번에 C:\A\B\C....\....
가능
위makeDir()메소드의 호출 결과는 디렉토리가 문제없이 만들어졌을 경우 true를 리턴함
우리가 File 객체를 만들때에는 이 객체가 파일을 가리키는지 경로를 가리키는지 알 수 있지만
File객체를 매개 변수로 받았을 경우엔 File객체가 파일을 나타내는지, 경로를 나타내는지 알 수 없는 문제가 생김
이를 해결하기 위해 isFile()과 isDirectory()가 존재
isFile()은 파일인지 확인
isDirectory()는 디렉터리인지 확인
추가적으로 숨긴 파일인지 확인하려면 isHidden()을 사용하면 됌
정상적인 상황이라면 makeDir() 메소드를 수행한 후 이 메소드 checkFileMathod()를 호출했을 때의 결과는
순서대로 true, false, false를 리턴함
파일이 가지고있는 권한을 확인할때 사용하는 메소드는 can으로 시작함
크게 권한 문제가 없으므로, 이 메소드들의 실행 결과는 모두 true임
파일이나 경로가 언제 생성되었는지 확인하는 File클래스에서 제공하는 메소드는
lastModified()가 있음 이 메소드는 long타입의 현재 시간을 리턴해줌
So, java.util.Date를 import해와서 사용해주면 됌
파일을 삭제하고자 할 때엔 delete() 메소드를 사용하면 됌
정상적으로 삭제한 경우 true를 리턴하고 마지막에 exists()를 사용했으니 false가 나올 것임
File 클래스를 이용한 파일 처리
위에서는 파일의 디렉터리 즉, 경로를 매개 변수로받아 만든 File객체를 다루었다면
이젠 디렉터리가 아닌 파일을 매개 변수로 받아 만든 File객체를 처리하는 메소드들을 사용해 보자
먼저 createNewFile() 메소드를 사용해서 비어있는 새로운 파일을 생성해주자
파일의 경로 pathName과 파일의 이름 fileName을 문자열 형태로 만들어놓고
checkFile 메소드에서 파일의 경로와 파일을 매개 변수로 받아 생성된 파일을 가리키는 File객체 file을 만듬
createNewFile() 메소드는 IOException을 던진다고 정해져 있기 떄문에 try-catch로 묶어주어야 함
이를 위해 java.io.IOException 클래스를 import 해와야 함
이제 checkFile() 메소드를 호출해보면 pathName으로 넘어간 경로에 fileName으로 넘어간 이름을 가진 파일을 생성하고 잘 생성되었다면 true를 리턴함 같은 이름의 파일을 다시 생성하는것과 같은 잘못 생성하는 경우 false를 리턴함 이 파일은 아무런 데이터를 추가하지 않았기 때문에 데이터는 전혀 없음.
파일의 정보를 확인해보자
File객체 file의 정보를 확인해주는 메소드임
File객체 file에서 호출한 메소드들의 이름 끝 부분을 보자
File로 끝나는 애들은 File객체를 리턴하고
Path로 끝나는 애들은 전체 경로를 String으로 리턴함
getName() 메소드는 파일일 경우엔 파일의 이름, 경로일 경우엔 전체 경로를 String으로 리턴함
getPath() 뭔가 getName() 보고 추측해봐도 이름만 보더라도 이 메소드는 경로만 리턴할 것 처럼 보임
하지만 getPath()의 결고는 경로만이 아니라 파일 이름까지 포함해서 출력함
So, 파일의 이름을 제외한 경로만을 확인하려면
경로만을 확인하려하는 File객체가 파일을 가리키고 있다면, 즉 생성자의 매개 변수에 파일명이 포함되어 있다면
(경로만 포함되어 있으면 경로를 가리키는 것, 경로 + 파일 이름이라면 파일을 가리키는 것)
getParenr()메소드를 사용해서 파일 이름을 제외한 경로만을 출력할 수 있음
실행 결과는 다음과 같음
결과를 보면 Absolute와 Canonical의 결과가 동일해보임
하지만 File객체 file의 경로가 상개 경로일 경우에는 결과가 달라짐
만약 C:\A\a 가 있고 C:\A\b 가 있다고 가정할때
a 디렉터리에서 b 디렉터리로 이동하려면 ..\b와 같이 상대 경로로 이동할 수 있음
이 경우 Absolute경로는 C:\A\a\..\brkehlrh
Canonical경로는 C:\A\b가 됌
디렉터리에 있는 목록을 살펴보기 위한 list 메소드들
File 클래스에는 list로 시작하는 6개의 메소드가 있음
모두 디렉터리에 있는 목록을 배열로 만들어 리턴해주는 기능을 가졌음
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
static File[] | listRoots() | JVM이 수행되는 OS에서 사용중인 파일 시스템의 root 디렉터리의 목록을 File 배열로 리턴함 static 메소드이기 때문에, 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 배열로 리턴함 |
What the FileFilter and FilenameFilter ??????????
FileFilter, FilenameFilter모두 파일의 목록들을 배열로 만들때 파일이 배열에 추가될 조건을 가진 필터 역할을 한다
예를 들어 설명하자면 디렉터리에 .txt로 끝나는 텍스트 파일과 .jpg로 끝나는 이미지 파일이 섞여있다고 가정해보자
여기서 .txt 파일만을 배열로 만들고싶을 때 list류 메소드에 Filter를 매개 변수로 넘겨서 .txt 파일만 배열에 담기도록 할 수 있다
FileFilter, FilenameFilter모두 인터페이스라서 직접 구현하여 사용해야한다
우리가 Filter에서 구현해야할 것은
이 메소드다
FileFilter 에서는
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
boolean | accept(File pathName) | 매개 변수로 넘어온 File 객체가 조건에 맞는지 확인한다 |
FilenameFilter 에서는
리턴 타입 | 메소드 이름 및 매개 변수 | 설명 |
boolean | accept(File dir, String name) | 매개 변수로 넘어온 디렉터리(dir)에 있는 경로나 파일이름(name)이 조건에 맞는지 확인한다 |
이다
filter를 매개 변수로 받는 list로 시작하는 메소드를 실행하면
메소드에서 파일이나 경로를 만날 때마다 위에 있는 accept() 메소드가 자동으로 수행된다
따라서, accept() 메소드를 어떻게 구현했느냐에 따라서, 필요한 파일의 목록이 달라진다
FileFilter을 구현해서 .jpg인 파일만 가져오도록 해보았다
먼저 File 객체 file이 file을 가리키는 File 객체인지 확인하고
파일의 이름을 받아와서
endWith() 메소드를 사용해 파일 이름의 끝에 .jpg가 붙었는지 확인하고 맞다면 true 아니라면 false를
리턴하도록 Overriding 했다
이를 FilenameFilter로 구현해보면 다음과 같다
로직적인 설명은 생략하고
위의 FileFilter와 다른점은 accept()의 매개 변수로 fileName이 넘어오기 때문에 별도로
File 객체의 getName() 메소드를 호출할 필요가 없다
이렇게만 보면 FilenameFilter가 FileFilter보다 더 좋아 보이지만
FilenameFilter는 파일 이름만 보지 디렉터리인지 파일인지 구분하지 못하기 때문에
디렉터리인데 이름만 .jpg가 붙은 것인지 아니면 실제로 .jpg 파일인지 구분하지 못 한다
그래서 .jpg로 끝나는 디렉터리가 있으면 필터로 걸러낼 수가 없다는 단점이 있다
사용은 이렇게 하면 된다
File 클래스에 대한 설명은 여기서 마친다
JDK 7 이상의 버전을 사용하면 File 클래스를 사용하는 것보다 java.nio.file 패키지에 있는
Files 클래스를 사용하는 것이 보다 효과적이다
InputStream and OutputStream
I/O는 기본적으로 InputStream과 OutputStream이라는 abstract 클래스를 통해서 제공됌
데이터를 읽을때는 InputStream의 자식 클래스를
데이터를 쓸 때는 OutputStream의 자식 클래스를 사용하면 된다
먼저 API에 있는 InputStream의 선언문을 보자
앞서 말한 대로 abstract 클래스로 선언되어 있기 때문에, 이 클래스를 제대로 사용하려면 확장한 자식 클래스들을 잘 살펴보고 활용할줄 알아야한다(OutputStream 역시)
클래스 선언문에 Closeable 인터페이스를 구현한 것을 볼 수 있다
Closeable에는 close() 라는 메소드만 선언되어 있는데
어떤 리소스를 열었던 간에 이 인터페이스를 구현하면 해당 리소스는 close() 메소드를 이용하여 닫으라는 것을 의미한다
해당 리소스를 다른 클래스에서도 작업할 수 있도록, java.io패키지에 있는 클래스를 사용할 때에는 하던 작업이 종료되면 clsoe() 메소드로 "항상" 닫아 주어야한다
리소스 라는 것은 파일이 될 수도 있고, 네트워크 연결도 될 수 있다.
스트림을 통해서 작업할 수 있는 모든 것을 리소스라고 생각하면 된다
InputStream 클래스의 메소드는
available(), mart(), reset(), martSupported(), read(), skip(), close()가 있는데
이 중에서 꼭 기억해야하는 메소드는 read()와 close()다
데이터를 읽을 때에는 read() 메소드를 사용하면 되고
리소스를 닫을 때에는 close() 메소드를 호출해야 한다
스트림을 다룰 때 다른 메소드는 호출하지 않아도 close() 메소드는 반드시 호출해야만 한다
이 메소드 호출은 선택이 아니라 필수다
메소드의 자세한 부분은 API를 참고하자
OutputStream
OutputStream의 API 선언문을 보자
InputStream과 마찬가지로 abstract 클래스로 선언되어 있다
Closeable과 Flushable 인터페이스를 구현했는데
Flushable 인터페이스는 뭘까?
하나의 메소드만 선언되어 있으며, flush()이다
어떤 리소스에 데이터를 쓸 때, 매번 쓰기 작업을 "요청할 때마다 저장"하면 효율이 안 좋아진다
그래서, 대부분 저장을 할 때 버퍼를 갖고 데이터를 쌓아 두었다가,
어느 정도 차게 되면 한번에 쓰는 것이 좋다
그러한 버퍼를 사용할 때, flush() 메소드는"현재 버퍼에 있는 내용을 기다리지 말고 무조건 저장해"
라고 시키는 것이다
outputStream 메소드에는
write()와 flush(), close() 메소드가 있고 자세한 설명은 API문서를 참고하자
스트림의 사용법은 조금 뒤의 장에 정리하겠다. 지금은 이런게 있구나 하고 다음에 알아보자
Reader and Writer
위에서 살펴본 Stream은 byte를 다루기 위한 것이고
이번에 다룰 Reader와 Writer은 char 기반의 문자열을 처리하기 위한 클래스다
Reader 클래스의 API 선언부를 보자
Reader 클래스도 abstract로 선언되어 있다
abstract 메소드는 close()와 read() 메소드다
API를 통해 메소드를 보자
InputStream에 선언되어 있는 메소드와 많이 중복되는 것을 볼 수 있다
Writer 클래스의 API 선언부를 살펴보자
못보던 것이 구현되어있는 것을 확인할 수 있다
Appendable이 무었일까?
Appendable 인터페이스는 Java 5 부터 추가되었으며, 각종 문자열을 추가히기 위해서 선언되었다
메소드를 보자
OutputStream 클래스에 선언된 메소드와 대부분 동일하지만, append()라는 메소드가 존재한다는 점이 다르다
append() 메소드의 매개 변수로 넘어가는
CharSequence는 인터페이스다
이 인터페이스를 구현한 대표적인 클래스에는 String, StringBuilder, StringBuffer가 있다
그래서, 매개 변수로 CharSequence를 넘긴다는 것은 대부분의 문자열을 다 받아서 처리한다는 말이다
문자열이 String 타입이라면 그냥 write() 메소드를 사용해도 별 상관은 없겠지만
StringBuilder나 StringBuffer로 문자열을 만들면 append() 메소드를 사용하는 것이 훨씬 편할 것이다
텍스트 파일을 쓰고 읽어보자
지금까지 살펴본 클래스들을 사용하여 파일에 데이터를 쓰고, 읽는 예제를 살펴보자
텍스트 파일을 써보자
char 기반의 내용을 파일로 쓰기 위해서는 FileWriter라는 클래스를 사용해야한다
이 클래스의 생성자를 보자
중간에 FileDescriptor 객체를 받는 생성자가 있는데
이 클래스를 사용할 일은 거의 없다
Writer에 있는 write나 append() 메소드를 사용하여 데이터를 쓰면, 메소드를 호출했을 때마다
파일에 쓰기 때문에 매우 비효율적
이러한 단점을 보완하기 위해
BufferedWriter 클래스가 있다
BufferedWriter는 버퍼라는 공간에 저장할 데이터를 보관해 두었다가, 버퍼가 차게되면 데이터를 저장하도록 도와준다
매우 효율적인 저장이 가능
매개 변수로 Writer을 받듯이, FileWriter을 매개 변수로 사용하면 파일에 저장할 수 있다
본격적으로 텍스트 파일을 써보자
main() 메소드 내부에 있는
fullPath에 텍스트 파일의 위치+이름을 문자열 형태로 넣어두고 writeFile 메소드를 호출할 때 넘겨준다
//numberCount는 이따가 나올 반복을 위해 필요한 것이니 그냥 있구나, 이걸로 뭘 하겠구나 하고 넘기자
writeFile() 메소드의 내부를 살펴보자
FileWriter 객체인 fileWriter을 파일 경로+이름 그리고 boolean값을 매개 변수로 받아 생성한다
(여기서 boolean값은 데이터를 단순 추가할지, 덮어쓸지를 결정하는 boolean값이다)
이 객체를 생성할 때 IOException이 발생할 수 있기 때문에 try-catch로 묶어주었다
이 Exception은 일반적으로 다음의 상황에서 발생한다
- 매개 변수로 넘어온 파일이름이 파일이 아닌 경로를 의미할 경우
- 해당 파일이 존재하지는 않지만, 권한등의 문제로 생성할 수가 없는 경우
- 해당파일이 존재하지만, 여러 가지 이유로 파일을 열 수가 없는 경우
그리고, 버퍼를 이용하기 위해 BufferedWriter의 객체 bufferedWriter를 FileWriter 객채 fileWriter를 생성자 매개 변수로 받아 생성한다
그리고 for문 반복을 통해
1부터 1씩 더해가며 출력하기
한줄 띄우기
를 반복하고
finally에서 bufferedWriter, fileWriter 순으로
close() 한다
왜 finally 안에서 하냐면 try에서 close()를 호출한다면 중간에 예외가 발생했을때 close() 메소드가 호출되지 않는다
이런점을 피하기 위해서 finally에서 닫아주는 것이고
생성한 객체들을 닫아줄 때에는 가장 마지막에 연 객체부터 순서대로 닫아주어야만 정상적인 처리가 가능하다
실행 결과는 Write success !!! 가 출력이 되고
지정한 경로(C:\SfromE\text)에 지정한 이름(numbers.txt)을 가진 파일에 0부터 10까지의 값이 한 줄씩 적혀있을 것이다
택스트 파일을 읽어보자
while문 내부에서 bufferedReader 객체의 readLine() 메소드를 호출하여 데이터를 읽어 들였다
그리고 data라는 변수에 그 값을 할당했다
실행결과는 0에서 10까지 cmd창에 출력해주고 Read success !!! 가 출력되고 끝날 것이다
그런데 이렇게 코드를 작성하면, 코드의 길이도 길어지고, 가독성도 많이 떨어진다 따라서
java,util 패키지에 있는 Scanner를 import해오면 매우 쉽게 파일을 읽을 수 있다
Scanner 클래스는 텍스트 기반의 기본 자료형이나 문자열 데이터를 처리하기 위한 클래스다
게다가 정규 표현식을 사용하여 데이터를 잘라 처리할 수도 있다
Scanner 클래스의 생성자는 종류가 다양한데, 여기서는 File의 객체를 매개 변수로 받아 파일의 내용을 읽는 데 사용했다
Scanner 클래스의 hasNextLine() 메소드는 다음줄이 있는지 확인하는 데 사용되며
nextLine() 메소드는 다음 줄의 내용을 문자열로 한 줄씩 리턴해 준다
추가로 위의 코드를 Java 7에서 제공하는 Files 클래스를 사용하면
한 줄로 파일을 읽을 수도 있다
String data = new String(Files.readAllBytes(paths.get(fileName)));
'Java_2' 카테고리의 다른 글
Java_2_7 (0) | 2020.06.02 |
---|---|
Java_2_6 (0) | 2020.05.28 |
Java_2_4 (0) | 2020.05.20 |
Java_2_3_4 (0) | 2020.04.04 |
Java_2_3_3 (0) | 2020.03.17 |
댓글