네이버 클라우드 캠프 44일차 230627
#1 교육정리
1) File 클래스 사용법
2) 람다식
3) 바이너리 파일 입출력 다루기
4) FileInputStream 활용 - JPEG 파일 읽기 : 위도/경도 알아내기
#2 File 클래스 사용법
File 클래스는 말그대로 파일이라는 개념을 추상화한 클래스입니다. 파일에 대한 입출력은 앞으로 배울 스트림을 사용하여 수행합니다.
따라서 File 클래스에 별도의 입출력 기능은 없지만 파일 자체의 경로나 정보를 알 수 있고 파일을 생성할 수도 있습니다. File 클래스의 주요 생성자는 다음과 같습니다.
생성자 | 설명 |
File(String pathname) | 입력한 pathname을 매개변수로 받아 경로 파일의 객체를 생성합니다. |
File(String parent, String child) | parent 디렉토리 경로의 child 파일에 대한 객체를 생성한다. |
File(File parent, String child) | 파일 객체 parent의 child 파일에 대한 객체를 생성한다. |
File(URI uri) | uri 경로에 대한 파일 객체를 생성한다. |
예제 코드와 함께 다양한 메서드와 활용 예시를 확인해보겠습니다.
폴더 정보 조회)
// 폴더 정보 조회 - java.io.File 클래스
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0110 {
public static void main(String[] args) throws Exception {
// File 클래스
// => 디렉토리나 파일을 다룰 때 사용하는 클래스이다.
// => 디렉토리나 파일을 생성, 삭제, 변경할 수 있다.
// 현재 디렉토리를 조회
// => '.' 으로 표현한다.
// => JVM을 실행하는 위치가 현재 폴더이다.
// => 이클립스 : 프로젝트 디렉토리를 가리킨다.
// => 콘솔 : 현재 명령어를 실행하는 위치를 가리킨다.
//
File currentDir = new File("./src/main/java");
System.out.printf("폴더명: %s\n", currentDir.getName());
System.out.printf("경로: %s\n", currentDir.getPath());
System.out.printf("절대경로: %s\n", currentDir.getAbsolutePath());
System.out.printf("계산된 절대경로: %s\n", currentDir.getCanonicalPath());
System.out.printf("총크기: %d\n", currentDir.getTotalSpace());
System.out.printf("남은크기: %d\n", currentDir.getFreeSpace());
System.out.printf("가용크기: %d\n", currentDir.getUsableSpace());
System.out.printf("디렉토리여부: %b\n", currentDir.isDirectory());
System.out.printf("파일여부: %b\n", currentDir.isFile());
System.out.printf("감춤폴더: %b\n", currentDir.isHidden());
System.out.printf("존재여부: %b\n", currentDir.exists());
System.out.printf("실행가능여부: %b\n", currentDir.canExecute());
}
}
폴더명: java
경로: ./src/main/java
절대경로:
계산된 절대경로:
총크기: 0
남은크기: 0
가용크기: 0
디렉토리여부: false
파일여부: false
감춤폴더: false
존재여부: false
실행가능여부: false
상위 폴더 정보 조회)
// 폴더 정보 조회 - java.io.File 클래스
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0120 {
public static void main(String[] args) throws Exception {
// File 클래스
// => 파일이나 디렉토리 정보를 관리
// => 파일이나 디렉토리를 생성,삭제,변경
// 상위 폴더 정보 조회
// - ".." 으로 경로를 표시한다.
//
File currentDir = new File("./src/main/java/../../test/java");
존재 하지 않는 파일 정보 조회)
// 파일 정보 조회 - java.io.File 클래스
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0220 {
public static void main(String[] args) throws Exception {
// File 클래스
// => 파일이나 디렉토리 정보를 관리
// => 파일이나 디렉토리를 생성,삭제,변경
// 존재하지 않는 파일 정보 조회
//
File file1 = new File("./src/main/java/Hello100.java");
System.out.printf("파일명: %s\n", file1.getName());
System.out.printf("파일크기: %d\n", file1.length());
System.out.printf("경로: %s\n", file1.getPath());
System.out.printf("절대경로: %s\n", file1.getAbsolutePath());
System.out.printf("계산된 절대경로: %s\n", file1.getCanonicalPath());
// 존재하지 않는 폴더인 경우 크기를 알아낼 수 없다.
System.out.printf("총크기: %d\n", file1.getTotalSpace());
System.out.printf("남은크기: %d\n", file1.getFreeSpace());
System.out.printf("가용크기: %d\n", file1.getUsableSpace());
// 존재하지 않는 폴더인 경우 정보를 알아낼 수 없다. 모두 false
System.out.printf("디렉토리여부: %b\n", file1.isDirectory());
System.out.printf("파일여부: %b\n", file1.isFile());
System.out.printf("감춤여부: %b\n", file1.isHidden());
System.out.printf("존재여부: %b\n", file1.exists());
System.out.printf("실행가능여부: %b\n", file1.canExecute());
}
}
파일명: Hello100.java
파일크기: 0
경로: ./src/main/java/Hello100.java
절대경로:
계산된 절대경로:
총크기: 0
남은크기: 0
가용크기: 0
디렉토리여부: false
파일여부: false
감춤여부: false
존재여부: false
실행가능여부: false
디렉토리 생성)
// 디렉토리 생성
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0310 {
public static void main(String[] args) throws Exception {
// 1) 생성할 디렉토리 경로 설정
// - 폴더 경로를 지정하지 않으면 현재 폴더를 의미한다.
//
File dir = new File("temp"); // 현재 폴더를 생성하기
File dir = new File("temp/a"); // 하위 디렉토리 생성하기
File dir = new File("temp2/a"); // 존재하지 않는 폴더
// 존재하지 않는 폴더 아래에 새 폴더를 만들 때
// => 존재하지 않는 폴더가 자동 생성되지 않는다.
// => 따라서 그 하위 폴더를 생성할 수 없다.
File dir = new File("temp2/a/b"); // 존재하지 않는 중간 디렉토리도 생성하기
// 디렉토리를 생성할 때 존재하지 않는 중간 디렉토리도 만들고 싶다면,
// mkdirs()를 호출하라.
// mkdirs()
// - 지정된 경로에 디렉토리가 존재하지 않으면 그 디렉토리도 만든다.
if (dir.mkdirs()) { // 출력할때도 mkdirs 로 출력
if (dir.mkdir()) { // 디렉토리 생성
System.out.println("temp 디렉토리를 생성하였습니다."); // 생성시(폴더명과 경로를 알맞게 입력)
} else {
System.out.println("temp 디렉토리를 생성할 수 없습니다."); // 생성 안될 시(폴더명과 경로를 알맞게 입력 / 예로.이미 디렉토리가 존재 할 경우)
}
}
}
디렉토리 삭제)
// 디렉토리 삭제
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0330 {
public static void main(String[] args) throws Exception {
File dir = new File("temp");
if (dir.delete()) {
System.out.println("temp 디렉토리를 삭제하였습니다.");
} else {
// 디렉토리 안에 파일이나 다른 하위 디렉토리가 있다면 삭제할 수 없다.
// 또한 존재하지 않는 디렉토리도 삭제할 수 없다.
System.out.println("temp 디렉토리를 삭제할 수 없습니다.");
}
}
}
파일 생성)
// 파일 생성
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0410 {
public static void main(String[] args) throws Exception {
// 생성할 파일의 경로 설정
File file = new File("temp2/a/test.txt");
// 만약 존재하지 않는 폴더에 파일을 생성할 때
// - 해당 경로에 디렉토리가 없다면 파일을 생성할 수 없다.
// - 예외 발생!
if (file.createNewFile()) { // 파일 생성
System.out.println("test.txt 파일을 생성하였습니다.");
} else {
// 이미 파일이 있다면 다시 생성할 수 없다.
// 해당 경로에 디렉토리가 없다면 파일을 생성할 수 없다. 예외 발생!
System.out.println("test.txt 파일을 생성할 수 없습니다.");
}
}
}
파일 삭제)
// 파일 삭제
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0420 {
public static void main(String[] args) throws Exception {
// 삭제할 파일의 경로 설정
File file = new File("temp2/a/test.txt");
if (file.delete()) { // 파일 삭제
System.out.println("test.txt 파일을 삭제하였습니다.");
} else {
// 존재하지 않는 파일은 삭제할 수 없다.
// 경로가 존재하지 않으면 당연히 그 경로에 파일이 없으니까 삭제할 수 없다.
System.out.println("test.txt 파일을 삭제할 수 없습니다.");
}
}
}
디렉토리에 들어 있는 파일이나 하위 디렉토리 정보 알아내기)
// 디렉토리에 들어 있는 파일이나 하위 디렉토리 정보 알아내기
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0510 {
public static void main(String[] args) throws Exception {
// 현재 폴더의 정보를 알아낸다.
File dir = new File(".");
// 현재 폴더에 있는 파일이나 하위 디렉토리 이름을 알아내기
String[] names = dir.list();
for (String name : names) {
System.out.println(name);
}
}
}
디렉토리에 들어 있는 파일이나 하위 디렉토리 정보 알아내기 II)
// 디렉토리에 들어 있는 파일이나 하위 디렉토리 정보 알아내기 II
package com.eomcs.io.ex01;
import java.io.File;
import java.sql.Date;
public class Exam0520 {
public static void main(String[] args) throws Exception {
File dir = new File(".");
// 파일이나 디렉토리 정보를 File 객체로 받기
// => File은 디렉토리와 파일을 통칭하는 용어다.
//
File[] files = dir.listFiles();
for (File file : files) {
System.out.printf("%s %s %12d %s\n", file.isDirectory() ? "d" : "-",
new Date(file.lastModified()), // long 값,Date는 java.sql 패키지에 있는
file.length(), file.getName());
}
}
}
디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기)
// 디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FilenameFilter;
public class Exam0610 {
public static void main(String[] args) throws Exception {
class JavaFilter implements FilenameFilter { // 로컬 클래스
@Override
public boolean accept(File dir/* 부모 경로 */, String name /* 파일,디렉토리 이름 */) {
// 이 메서드는 list()에서 호출한다.
// 해당 폴더에 들어 있는 파일이나 디렉토리를 찾을 때 마다 호출한다.
// (하위 폴더 아래는 뒤지지 않는다)
// 이 메서드에서 해야 할 일은 찾은 파일이나 디렉토리를
// 리턴할 배열에 포함시킬지 여부이다.
// true를 리턴하면 배열에 포함되고,
// false를 리턴하면 배열에 포함되지 않는다.
// 파일,디렉토리 이름이 .java 로 끝나는 경우만 리턴 배열에 포함시키다.
return name.endsWith(".java");
}
}
File dir = new File(".");
// => 확장자가 .java 인 파일의 이름만 추출하기
// 1) 필터 준비
JavaFilter javaFilter = new JavaFilter();
// 2) 필터를 사용하여 디렉토리의 목록을 가져오기
String[] names = dir.list(javaFilter);
for (String name : names) {
System.out.println(name);
}
// 문제점:
// - temp.java 는 디렉토리이다.
// - 현재의 필터는 파일 이름으로만 검사한다.
// - 파일인지 디렉토리인지 여부는 검사하지 않는다.
// - 해결책?
// 다음 예제를 보라!
}
}
디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기)
// 디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FilenameFilter;
public class Exam0611 {
public static void main(String[] args) throws Exception {
class JavaFilter implements FilenameFilter {
@Override
public boolean accept(File dir, String name) {
// 해당 이름이 디렉토리 이름인지 파일 이름인지 알아내려면
// File 객체를 생성해야 한다.
//
File file = new File(dir, name);
// 디렉토리 정보와 이름을 합쳐 파일 객체를 생성할 수 있다.
return (file.isFile() && name.endsWith(".java"));
}
}
File dir = new File(".");
// => 확장자가 .java 인 파일의 이름만 추출하기
// 1) 필터 준비
JavaFilter javaFilter = new JavaFilter();
// 2) 필터를 사용하여 디렉토리의 목록을 가져오기
String[] names = dir.list(javaFilter);
for (String name : names) {
System.out.println(name);
}
}
}
익명 클래스로 FileFilter 만들기)
// 익명 클래스로 FileFilter 만들기
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FileFilter;
public class Exam0630 {
public static void main(String[] args) throws Exception {
// 필터 객체를 한 개만 만들 것이라면
// 익명 클래스로 정의하는 것이 낫다.
//
// 익명 클래스를 정의할 때
// 객체를 사용할 위치에 익명 클래스를 정의하는 것이
// 코드를 더 읽기 쉽게 만든다.
//
FileFilter filter = new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isFile() && file.getName().endsWith(".java"))
return true;
return false;
}
};
File dir = new File(".");
File[] files = dir.listFiles(filter);
for (File file : files) {
System.out.printf("%s %12d %s\n", file.isDirectory() ? "d" : "-", file.length(),
file.getName());
}
}
}
필터와 Lambda 표현식을 사용하여 디렉토리 이름만 추출하라)
// 활용: 필터와 Lambda 표현식을 사용하여 디렉토리 이름만 추출하라.
package com.eomcs.io.ex01;
import java.io.File;
public class Exam0660 {
public static void main(String[] args) throws Exception {
File file = new File(".");
File[] files = file.listFiles(f -> f.isDirectory());
for (File f : files) {
System.out.printf("%s %12d %s\n",
f.isDirectory() ? "d" : "-",
f.length(),
f.getName());
}
}
}
이 외에 파일 관련 메서드는 아래를 참조 바랍니다.
* File 클래스의 메소드
File 클래스의 메소드 | 설명 |
File getAbsoluteFile() | 파일의 절대 경로를 넘겨준다. |
String getAbsolutePath() | 파일의 절대 경로를 문자열로 넘겨준다. |
File getCanonicalFile() | 파일의 Canonical 경로를 넘겨준다. |
String getCanonicalPath() | 파일의 Canonical 경로를 문자열로 넘겨준다. |
String getName() | 파일이나 폴더의 이름을 넘겨준다. |
String getParent() | 부모 경로에 대한 경로명을 문자열로 넘겨준다. |
File getParentFile() | 부모 폴더를 File의 형태로 리턴한다. |
String getPath() | 파일의 경로를 문자열의 형태로 리턴한다. |
long getTotalSpace() | 하드디스크의 총 용량을 리턴한다. |
long getUsableSpace() | 하드디스크의 사용 가능한 용량을 리턴한다. |
long getFreeSpace() | 하드디스크의 남은 공간을 리턴한다. |
int hashCode() | hash code를 반환한다. |
long lastModified() | 해당 경로 파일의 최종 수정 일자를 반환한다. |
long length() | 해당 경로 파일의 길이를 반환한다. |
Path toPath() | java.nio.file.Path 객체로 반환한다. |
URI toURI() | URI 형태로 파일 경로를 반환한다. |
File[] listRoots() | 하드디스크의 루트 경로를 반환한다. |
String[] list() | 경로의 파일들과 폴더를 문자열 배열로 반환한다. |
String[] list(FilenameFilter filter) | filter에 만족되는 파일들과 폴더 이름을 문자열 배열로 반환한다. |
File[] listFiles() | 해당 경로의 파일들과 폴더의 파일을 배열로 반환한다. |
File[] listFiles(FileFilter filter) | filter에 만족되는 파일들과 폴더를 File 배열로 반환한다. |
File[] listFiles(FilenameFilter filter) | filter에 만족되는 파일들과 폴더를 File 배열로 반환한다. |
* File 생성/수정/삭제 메소드
File 생성 수정 삭제 메소드 | 설명 |
boolean createNewFile() | 주어진 이름의 파일이 없으면 새로 생성한다. |
static File createTempFile(String prefix, String suffix) | default temporary-file 디렉토리에 파일 이름에 prefix와 suffix를 붙여 임시파일을 생성한다. |
static File createTempFile(String prefix, String suffix, File directory) | 새로운 임시파일을 파일 이름에 prefix와 suffix를 붙여 directory 폴더에 생성한다. |
boolean delete() | 파일이나 폴더를 삭제한다. 단, 폴더가 비어있지 않으면 삭제할 수 없다. |
void deleteOnExit() | 자바가상머신이 끝날 때 파일을 삭제한다. |
boolean mkdir() | 해당 경로에 폴더를 만든다. |
boolean mkdirs() | 존재하지 않는 부모 폴더까지 포함하여 해당 경로에 폴더를 만든다. |
boolean renameTo(File dest) | dest 로 File 이름을 변경한다. |
* File 체크 메소드
File 체크 메소드 | 설명 |
boolean exists() | 파일의 존재 여부를 리턴한다. |
boolean isAbsolute() | 해당 경로가 절대경로인지 여부를 리턴한다. |
boolean isDirectory() | 해당 경로가 폴더인지 여부를 리턴한다. |
boolean isFile() | 해당 경로가 일반 file 인지 여부를 리턴한다. |
boolean isHidden() | 해당 경로가 숨김 file 인지 여부를 리턴한다. |
* File 권한 메소드
File 클래스 권한 관련 메소드 | 설명 |
boolean canExecute() | 파일을 실행할 수 있는지 여부를 리턴한다. |
boolean canRead() | 파일을 읽을 수 있는지 여부를 리턴한다. |
boolean canWrite() | 파일을 쓸 수 있는지 여부를 리턴한다. |
boolean setExecutable(boolean executable) | 파일 소유자의 실행 권한을 설정한다. |
boolean setExecutable(boolean executable, boolean ownerOnly) | 파일의 실행 권한을 소유자 또는 모두에 대해 설정한다. |
boolean setReadable(boolean readable) | 파일의 소유자의 읽기 권한을 설정한다. |
boolean setReadable(boolean readable, boolean ownerOnly) | 파일의 읽기 권한을 소유자 또는 모두에 대해 설정한다. |
boolean setReadOnly() | 파일을 읽기 전용으로 변경한다. |
boolean setWritable(boolean writable) | 파일의 소유자의 쓰기 권한을 설정한다. |
boolean setWritable(boolean writable boolean ownerOnly) | 파일의 쓰기 권한을 소유자 또는 모두에 대해 설정한다. |
메소드 참조 : https://xzio.tistory.com/305
#3 람다식
자바는 객체를 기반으로 프로그램을 구현합니다. 만약 어떤 기능이 필요하다면 클래스를 먼저 만들고, 클래스 안에 기능을 구현한 메서드를 만든 후 그 메서드를 호출해야 합니다.
다시 말해, 클래스가 없다면 메서드를 사용할 수 없습니다. 그런데 프로그래밍 언어 중에는 함수의 구현과 호출만으로 프로그램을 만들 수 있는 프로그래밍 방식이 있습니다.
이를 '함수형 프로그래밍(Functional Programming; FP)이라고 합니다.
자바에서 제공하는 함수형 프로그래밍 방식을 람다식(Lambda expression) 또는 익명 함수(Annonymous function)이라고 합니다.
람다식 구현하기)
람다식은 간단히 설명하면 함수 이름이 없는 익명 함수를 만드는 것입니다.
문법 :
(매개변수) -> {실행문;}
메서드에서 사용하는 매개변수가 있고, 이 메서드가 매개변수를 사용하여 실행할 구현 내용, 즉 메서드의 구현부를 {} 내부에 씁니다.
예를들어 두 수를 입력받아 그 합을 반환하는 add() 함수가 있을 때 이를 람다식으로 변환해 보겠습니다.
변환 전 :
int add(int x, int y) {
return x + y;
}
변환 후 :
(int x, int y) -> {return x + y;}
메서드 이름 add와 반환형 int를 없애고 -> 기호를 사용하여 구현합니다. 람다식의 의미를 살펴보면 두 입력 매개변수(x, y)를 사용하여 {return x + y;} 문장을 실행해 반환하라는 의미입니다.
람다식의 장/단점)
장점
1. 코드의 간결성 - 람다를 사용하면 불필요한 반복문의 삭제가 가능하며 복잡한 식을 단순하게 표현할 수 있습니다.
2. 지연연산 수행 - 람다는 지연연상을 수행 함으로써 불필요한 연산을 최소화 할 수 있습니다.
단점
1. 람다식의 호출이 까다롭습니다
2. 람다 stream 사용 시 단순 for문 혹은 while문 사용 시 성능이 떨어집니다.
3. 불필요하게 너무 사용하게 되면 오히려 가독성을 떨어 뜨릴 수 있습니다.
람다식 예제)
#2 번에서 학습한 예제 코드를 활용하여 아래와 같이 람다식을 적용해보겠습니다.
디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기)
변환 전 :
// 디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기 II
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FileFilter;
public class Exam0620 {
public static void main(String[] args) throws Exception {
class JavaFilter implements FileFilter {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(".java");
}
}
File dir = new File(".");
JavaFilter javaFilter = new JavaFilter();
File[] files = dir.listFiles(javaFilter);
for (File file : files) {
System.out.printf("%s %12d %s\n", file.isDirectory() ? "d" : "-", file.length(),
file.getName());
}
}
}
1단계 : 익명 클래스를 새로 만들기 -> 기존 클래스는 중복되므로 삭제한다. 기존의 file 배열의 파라미터인 javaFilter를 새로운 file 인스턴스로 바꾸어 줍니다.
// 디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기 II
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FileFilter;
public class Exam0620 {
public static void main(String[] args) throws Exception {
FileFilter file = new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(".java");
}
};
File dir = new File(".");
File[] files = dir.listFiles(file);
for (File file : files) {
System.out.printf("%s %12d %s\n", file.isDirectory() ? "d" : "-", file.length(),
file.getName());
}
}
}
2단계 : files 배열의 file 파라미터에 FileFilter 값을 넣어줍니다. 그리고 기존의 문장은 삭제합니다.
// 디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기 II
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FileFilter;
public class Exam0620 {
public static void main(String[] args) throws Exception {
File dir = new File(".");
File[] files = dir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isFile() && file.getName().endsWith(".java");
}
});
for (File file : files) {
System.out.printf("%s %12d %s\n", file.isDirectory() ? "d" : "-", file.length(),
file.getName());
}
}
}
3단계 : 람다문을 위해 new FileFilter ~ accept 까지 삭제합니다.
// 디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기 II
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FileFilter;
public class Exam0620 {
public static void main(String[] args) throws Exception {
File dir = new File(".");
File[] files = dir.listFiles((File file) {
return file.isFile() && file.getName().endsWith(".java");
}
);
for (File file : files) {
System.out.printf("%s %12d %s\n", file.isDirectory() ? "d" : "-", file.length(),
file.getName());
}
}
}
4단계 : 파라미터인 file만 남기고 File은 삭제합니다. 후에 중괄호도 삭제 해줍니다.
// 디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기 II
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FileFilter;
public class Exam0620 {
public static void main(String[] args) throws Exception {
File dir = new File(".");
File[] files = dir.listFiles(file)
return file.isFile() && file.getName().endsWith(".java");
);
for (File file : files) {
System.out.printf("%s %12d %s\n", file.isDirectory() ? "d" : "-", file.length(),
file.getName());
}
}
}
5단계 : 후에 람다식 문법에 맞게 return 과 파라미터 사이에 '->' 를 입력하고 'return'은 삭제하고 람다식에서 파라미터가 독립변수로 쓰일 때 괄호는 생략이 가능하여 삭제 해줍니다. 그리고 아래와 같이 코드정리를 해주면 람다식 완성입니다.
초기 코드에 비해 훨씬 간결해졌습니다^^
// 디렉토리에 들어있는 파일(디렉토리) 목록을 꺼낼 때 필터 적용하기 II
package com.eomcs.io.ex01;
import java.io.File;
import java.io.FileFilter;
public class Exam0620 {
public static void main(String[] args) throws Exception {
File dir = new File(".");
File[] files = dir.listFiles(file -> file.isFile() && file.getName().endsWith(".java"));
for (File file : files) {
System.out.printf("%s %12d %s\n", file.isDirectory() ? "d" : "-", file.length(),
file.getName());
}
}
}
#4 바이너리 파일 입출력 다루기
먼저 입출력을 다루기 전에 스트림이 무엇인지 간단히 알아보겠습니다.
스트림이란?)
자바에서 모든 입출력은 스트림(stream)을 통해 이루어집니다.
스트림이란 네트워크에서 유래된 용어입니다. 자료 흐름이 물의 흐름과 같다는 의미에서 사용되었습니다. 입출력 장치는 매우 다양하기 때문에 장치에 따라 입출력 부분을 일일이 다르게 구현을 하면 프로그램 호환성이 떨어질 수밖에 없습니다.
이런 문제를 해결하기 위해 자바는 입출력 장치와 무관하고 일관성 있게 프로그램을 구현할 수 있도록 일종의 가상 통로인 스트림을 제공하는 것입니다. 자료를 읽어 들이려는 소스(source)와 자료를 쓰려는 대상(target)에 따라 각각 다른 스트림 클래스를 제공합니다.
자바에서 입출력 기능을 사용하는 곳은 파일 디스크, 키보드, 모니터, 메모리 입출력, 네트워크 등이 있습니다. 이러한 곳에서 일어나는 모든 입출력 기능을 스트림 클래스로 제공합니다.
따라서 자바에서 자료를 입출력하려면 여러 스트림 클래스에 대해 알아야 하지만, 구현 방식이 서로 비슷하므로 크게 걱정할 필요가 없습니다.
입력 스트림 : 입력 장치로부터 자바 프로그램으로 데이터를 전달하는 객체
출력 스트림 : 자바 프로그램에서 출력 장치로 데이터를 보내는 객체
입출력 스트림 기본 단위 : 바이트
단방향 스트림, 선입선출 구조
JDK의 바이트 스트림 클래스 계층 구조)
FileReader의 생성자와 주요 메소드 및 코드예제)
예제)
// Byte Stream - 바이트 단위로 읽기
package com.eomcs.io.ex02;
import java.io.FileInputStream;
public class Exam0120 {
public static void main(String[] args) throws Exception {
// 1) 파일 데이터 읽기를 담당할 객체를 준비한다.
// - new FileInputStream(파일경로)
// - 해당 경로에 파일이 존재하지 않으면 예외가 발생한다.
//
FileInputStream in = new FileInputStream("temp/test1.data");
// 2) 1바이트를 읽는다.
// => read() 메서드의 리턴 타입이 int 라 하더라도 1바이트를 읽어 리턴한다.
// => 이유?
// 0 ~ 255까지의 값을 읽기 때문이다.
// byte는 -128 ~ 127까지의 값만 저장한다.
int b = in.read(); // 읽은 값은 0x4d 이다.
System.out.printf("%02x\n", b);
System.out.printf("%02x\n", in.read());
System.out.printf("%02x\n", in.read());
System.out.printf("%02x\n", in.read());
// read() 를 호출할 때마다 이전에 읽은 바이트의 다음 바이트를 읽는다.
// 3) 읽기 도구를 닫는다.
in.close();
}
}
FileWriter의 생성자와 주요 메소드 및 코드예제)
예제)
// Byte Stream - 바이트 배열의 특정 부분을 출력하기
package com.eomcs.io.ex02;
import java.io.FileOutputStream;
public class Exam0310 {
public static void main(String[] args) throws Exception {
FileOutputStream out = new FileOutputStream("temp/test1.data");
byte[] bytes = {0x7a, 0x6b, 0x5c, 0x4d, 0x3e, 0x2f, 0x30};
// write(byte[]) : 배열의 값 전체를 출력한다.
// write(byte[], 시작인덱스, 출력개수) : 시작 위치부터 지정된 개수를 출력한다.
//
out.write(bytes, 2, 3); // 2번 데이터부터 3 바이트를 출력한다.
out.close();
System.out.println("데이터 출력 완료!");
}
}
FileOutputStream의 생성자와 주요 메소드 및 코드예제)
예제)
// Byte Stream - 바이트 단위로 출력하기
package com.eomcs.io.ex02;
import java.io.FileOutputStream;
public class Exam0110 {
public static void main(String[] args) throws Exception {
// 1) 파일로 데이터를 출력하는 객체를 준비한다.
// - new FileOutputStream(파일경로)
// - 지정된 경로에 해당 파일을 자동으로 생성한다.
// - 기존에 같은 이름의 파일이 있으면 덮어쓴다.
// - 주의! 기존 파일이 삭제된다.
// - 파일 경로가 절대 경로가 아니면
// - 리눅스,유닉스: / 로 시작하지 않으면,
// - 윈도우: c:\, d:\ 등으로 시작하지 않으면,
// 현재 디렉토리가 기준이 된다.
FileOutputStream out = new FileOutputStream("temp/test1.data");
// 2) 1바이트를 출력한다.
// => write(int) : 1바이트를 출력하는 메서드이다.
// 파라미터의 타입이 int라고 해서 4바이트를 출력하는 것이 아니다.
// 오직 맨 끝 1바이트만 출력한다.
out.write(0x7a6b5c4d); // 출력하는 값은 0x4d 이다.
out.write(2); // 0x00000002
out.write(40); // 0x00000028
out.write(100); // 0x00000064
out.write(101); // 0x00000065
out.write(102); // 0x00000066
out.write(127); // 0x0000007f
out.write(255); // 0x000000ff
out.write('A'); // 0x0041
out.write('가'); // 0xac00
// 3) 출력 도구를 닫는다.
// - OS에서 관리하는 자원 중에서 한정된 개수를 갖는 자원에 대해
// 여러 프로그램이 공유하는 경우, 항상 사용 후 OS에 반납해야 한다.
// - 그래야 다른 프로그램이 해당 자원을 사용할 수 있다.
// 예) 파일, 메모리, 네트워크 연결 등
// - 이런 자원을 사용하는 클래스는 자원을 해제시키는 close()라는 메서드가 있다.
// - 보통 java.lang.AutoCloseable 인터페이스를 구현하고 있다.
// - 이번 예제에서 다루는 FileOutputStream 클래스에도 close()가 있다.
// - 따라서 FileOutputStream 객체를 사용한 후에는 close()를 호출하여
// 사용한 자원을 해제시켜야 한다.
// - close()를 호출하면,
// FileOutputStream이 작업하는 동안 사용했던 버퍼(임시메모리)를 비운다.
// - OS에서 제공한 파일과의 연결을 끊는다.
//
out.close();
// 물론, JVM을 종료하면 JVM을 실행하는 동안 사용했던 모든 자원은
// 자동으로 해제된다.
// 이런 예제처럼 짧은 시간 동안만 실행되는 경우,
// close()를 호출하지 않아도 자원을 해제시키는데 문제가 없다.
// 문제는 24시간 365일 멈추지 않고 실행하는 서버 프로그램인 경우
// 사용한 자원을 즉시 해제시키지 않으면
// 자원 부족 문제가 발생한다.
// 결론!
// - 간단한 코드를 작성하더라도 AutoCloseable 인터페이스를 구현한 클래스를 사용할 때는
// 사용하고 난 후 자원을 해제시는 close()를 반드시 호출하라.
//
System.out.println("데이터 출력 완료!");
}
}
// 파일 입출력 API 주요 클래스 (java.io 패키지)
// 1) 데이터 읽기
// InputStream (추상 클래스)
// +-- FileInputStream : 바이트 단위로 읽기 (binary stream)
//
// Reader (추상 클래스)
// +-- FileReader : 문자 단위로 읽기 (character stream)
//
// 2) 데이터 쓰기
// OutputStream (추상 클래스)
// +-- FileOutputStream : 바이트 단위로 쓰기 (binary stream)
//
// Writer (추상 클래스)
// +-- FileWriter : 문자 단위로 쓰기 (character stream)
// ## 바이너리 파일 vs 텍스트 파일
// 1) 바이너리 파일
// - character set(문자표) 규칙에 따라 작성한 파일이 아닌 파일.
// - 기본 텍스트 편집기(메모장, vi 에디터 등)로 편집할 수 없는 파일을 말한다.
// - 만약 텍스트 편집기로 변경한 후 저장하면, 파일 포맷이 깨지기 때문에 무효한 파일이 된다.
// - 예) .pdf, .ppt, .xls, .gif, .mp3, .jpg, .hwp, .mov, .avi, .exe, .lib 등
// - 바이너리 파일을 편집하려면 해당 파일 포맷을 이해하는 전용 프로그램이 필요하다.
//
// 2) 텍스트 파일
// - 특정 character set(문자표) 규칙에 따라 작성한 파일.
// - 기본 텍스트 편집기(메모장, vi 에디터 등)로 편집할 수 있는 파일을 말한다.
// - 예) .txt, .csv, .html, .js, .css, .xml, .bat, .c, .py, .php, .docx, .pptx, .xlsx 등
// - 텍스트 파일은 전용 에디터가 필요 없다.
// - 텍스트를 편집할 수 있는 에디터라면 편집 후 저장해도 유효하다.
// 바이너리 데이터 읽고, 쓰기
// - 읽고 쓸 때 중간에서 변환하는 것 없이 바이트 단위로 그대로 읽고 써야 한다.
// - InputStream/OutputStream 계열의 클래스를 사용하라.
//
// 텍스트 데이터 읽고, 쓰기
// - 읽고 쓸 때 중간에서 문자 코드표에 따라 변환하는 것이 필요하다.
// - Reader/Writer 계열의 클래스를 사용하라.
FileInputStream의 생성자와 주요 메소드 및 코드예제)
예제)
// Byte Stream - 읽은 데이터를 바이트 배열의 특정 위치에 저장하기
package com.eomcs.io.ex02;
import java.io.FileInputStream;
public class Exam0320 {
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("temp/test1.data");
// 데이터를 저장할 바이트 배열을 준비한다.
byte[] buf = new byte[100];
// 바이너리 데이터를 읽어 배열에 저장하기
// read(byte[])
// => 읽은 데이터를 바이트 배열에 채운다.
// => 리턴 값은 읽은 바이트 수이다.
// read(byte[], 저장할 위치, 저장하기를 희망하는 개수)
// => 읽은 데이터를 "저장할 위치"에 지정된 방부터 개수만큼 저장한다.
// => 리턴 값은 실제 읽은 바이트 개수이다.
int count = in.read(buf, 10, 40); // 40바이트를 읽어 10번 방부터 저장한다.
in.close();
System.out.printf("%d\n", count);
for (int i = 0; i < 100; i++)
System.out.printf("%d: %02x \n", i, buf[i]);
System.out.println();
}
}
FileInputStream을 활용하여 JPEG 파일 읽기 예제)
// FileInputStream 활용 - JPEG 파일 읽기
package com.eomcs.io.ex02;
import java.io.File;
import java.io.FileInputStream;
public class Exam0410 {
public static void main(String[] args) throws Exception {
// 1) 파일 정보를 준비한다.
File file = new File("sample/photo1.jpg");
// 2) 파일을 읽을 도구를 준비한다.
FileInputStream in = new FileInputStream(file);
// => SOI(Start of Image) Segment 읽기: 2바이트
int b1 = in.read(); // 00 00 00 ff
int b2 = in.read(); // 00 00 00 d8
int soi = b1 << 8 | b2;
// 00 00 00 ff <== b1
// 00 00 ff 00 <== b1 << 8
// | 00 00 00 d8 <== b2
// ------------------
// 00 00 ff d8
System.out.printf("SOI: %x\n", soi);
// => JFIF-APP0 Segment Marker 읽기: 2바이트
int jfifApp0Marker = in.read() << 8 | in.read();
System.out.printf("JFIF APP0 Marker: %x\n", jfifApp0Marker);
// => JFIF-APP0 Length: 2바이트
int jfifApp0Length = in.read() << 8 | in.read();
System.out.printf("JFIF APP0 정보 길이: %d\n", jfifApp0Length);
// => JFIF-APP0 정보: 16바이트(위에서 알아낸 길이)
byte[] jfifApp0Info = new byte[jfifApp0Length];
in.read(jfifApp0Info);
// => JFIF-APP0 Identifier: 5바이트
String jfifApp0Id = new String(jfifApp0Info, 0, 4);
System.out.printf("JFIF APP0 ID: %s\n", jfifApp0Id);
// SOF0(Start of Frame) 정보 읽기
// - 그림 이미지의 크기 및 샘플링에 관한 정보를 보관하고 있다
// - 0xFFC0 ~ 0xFFC2 로 표시한다.
// => SOF Marker 찾기
int b;
while (true) {
b = in.read();
if (b == -1) { // 파일 끝에 도달
break;
}
if (b == 0xFF) {
b = in.read();
if (b == -1) { // 파일 끝에 도달
break;
}
if (b >= 0xC0 && b <= 0xC2) {
break;
}
}
}
if (b == -1) {
System.out.println("유효한 JPEG 파일이 아닙니다.");
return;
}
// => SOF Length 읽기: 2바이트
int sofLength = in.read() << 9 | in.read();
System.out.printf("SOF 데이터 크기: %d\n", sofLength);
// => SOF 데이터 읽기: 17바이트(위에서 알아낸 크기)
byte[] sofData = new byte[sofLength];
in.read(sofData);
// => SOF 샘플링 정밀도: 1바이트
System.out.printf("SOF 샘플링 정밀도: %d\n", sofData[0]);
// => SOF 이미지 높이: 2바이트
int height = ((sofData[1] << 8) & 0xff00) | (sofData[2] & 0xff);
// => SOF 이미지 너비: 2바이트
int width = ((sofData[3] << 8) & 0xff00) | (sofData[4] & 0xff);
System.out.printf("SOF 이미지 크기(w x h): %d x %d\n", width, height);
// 3) 읽기 도구를 닫는다.
in.close();
}
}
생성자 그림 참조 : https://nlp.jbnu.ac.kr/PL2020/ch13.pdf
#5 FileInputStream 활용 - JPEG 파일 읽기 : 위도/경도 알아내기
위의 주제를 별도로 나눈 이유는 활용되는 메서드와 코드를 받아오는 예시도 들기 위해서 입니다.
우선 JPEG 파일을 보면 아래와 같이 위도와 경도를 확인 할 수 있습니다.
코드는 아래와 같습니다.
// FileInputStream 활용 - JPEG 파일 읽기 : 위도/경도 알아내기
package com.eomcs.io.ex02;
import java.io.File;
import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.GpsDirectory;
public class Exam0420 {
public static void main(String[] args) throws Exception {
File file = new File(
"/경로입력해주세요/gps-test.jpeg");
Metadata metadata = ImageMetadataReader.readMetadata(file);
GpsDirectory gpsDirectory = metadata.getFirstDirectoryOfType(GpsDirectory.class);
if (gpsDirectory != null) {
System.out.println(gpsDirectory.getGeoLocation().getLatitude());
System.out.println(gpsDirectory.getGeoLocation().getLongitude());
}
}
}
36.45679444444445
127.28533888888889
실행 시키기 위해서는 gradle을 build 하셔야 하며, 하셨다는 가정하에 설명하겠습니다.
아래 maven 사이트에서 아래와 같이 metadata 코드를 복사해서 build.gradle 파일에 입력해줍니다.
https://central.sonatype.com/artifact/com.drewnoakes/metadata-extractor/2.18.0
코드 삽입 :
후에 gradle clean eclipse를 해주고 -> gradle eclipse를 다시 해주고나서도 실행이 안되시느분이 있으시면 이클립스를 닫고 다시 실행시켜보세요!
그러면 아래와 같이 그림이 보입니다.
그리고 나서 위와 같이 코드를 입력하고 실행 시켜주시면 위도와 경도를 출력 할 수 있습니다.
코드를 실행하면서 주의할점은 JPEG 파일의 경로를 잘 입력해야 오류 없이 출력이 가능했습니다.