코딩테스트를 수행하는 중 File을 다루는 과제를 만나게 되었다. 기본적인 개념이기 때문에, 쉽게 해결될 것이라고 생각했지만... 그 기본적인 것을 제대로 이해하지 못하고, 파일 경로 설정을 해멨기 때문에... 자바에서 String 타입의 파일 경로가 주어졌을 때 어떻게 파일을 탐색하는지 알아보고자 한다.
환경구성
- java 17
- window10
- intellij
String 타입의 파일경로가 주어졌을 때
파일을 탐색하는 과정을 이해하기 위해, 아래의 간단한 코드를 디버깅할 것이다.
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
System.out.println(file.exists());
System.out.println(file.getAbsoluteFile());
}
}
위 코드에서 언급된 "test.txt" 파일은 src 폴더 內 "Main.java"와 동일한 위치에 두었다.
먼저, File 객체를 생성하는 것에서 시작된다.
"fs" 파일시스템 객체를 의미하며, 필자는 윈도우10을 사용하고 있기 때문에, "WinNTFileSystem"이 사용된다.
fs.normalize(path) 함수에서는 넘겨받은 파일경로를 OS환경에 맞게 변환하는 과정을 거친다. 구체적인 코드는 아래와 같다.
윈도우 환경에서 파일간의 계층을 나타내기 위한 기호인 \\ 가 slash 변수에 할당되고, 리눅스 환경에서의 구분자인 / 또한 altSlash로 지정된다. 만약 파일경로에 / 기호가 있다면 \\ 로 변경된다. 이러한 처리는 아래 코드에 의해 수행된다.
if (c == altSlash)
return normalize(path, n, (prev == slash) ? i - 1 : i);
파일 경로를 OS환경에 맞게 변경된다. 이후 fs.prefixLength(this.path) 에 의해 해당 파일의 prefixLength가 지정된다. 필자는 처음봤을 때 파일의 prefix가 무엇인지 이해하지 못하였다.
스택오버플로우에서 확인해보니, 윈도우 환경에서 C:/ , D:/ 와 같은 루트를 나타내기 위한 변수임을 확인했다. 만약 UNIX 계열이라면, prefix는 / 일 수 있다.
File 객체 생성은 완료되었다. 이제 자바에서 File 찾아내는 과정을 디버깅할 것이다.
확인을 위해 File.exists() 함수를 호출했다.
834번 라인에서, 파일시스템의 hasBooleanAttributes(File f, int attributes) 함수를 호출하고 있다.
해당함수는 다시 getBooleanAttributes(File f) 메서드를 호출한다. attributes값은 FileSystem 추상 클래스에 사전에 지정된 값으로, 해당 파일이 어떤 파일인지를 간단하게 나타내기 위한 용도로 보인다.
getBooleanAttributes(File f) 메서드는, WinNTFileSystem 구현체의 경우 네이티브 함수로 구현되있기 때문에 내부 동작을 확인할 수 없었다.
다만, 아래의 FileSystem 추상 클래스의 명시된 주석을 통해, 파일 경로에 해당하는 파일에 대해 사전에 지정해놓았던 상태값(Attributes)을 반환함을 확인하였다. 만약 해당 파일이 존재하지 않거나, I/O error가 발생하면 0을 반환한다고 한다.
exists() 메서드에서는 FileSystem.BA_EXISTS 를 활용하고 있기 때문에, 만약 getBooleanAttributes 함수에서 1을 반환한다면, 해당 파일이 존재한다고 판단하는 것이다.
이번에는 테스트를 위해 작성한 코드의 결과물을 확인하겠다.
false
C:\Users\A\Desktop\project\TEST\javaiotest\javaiotest\test.txt
"test.txt" 파일을 찾지 못하는 것으로 확인되었다... Main.java 클래스에 두면 파일을 찾을 수 있다고 생각했는데 잘못된 생각이였다ㅠ 하단의 절대경로를 확인하면, java에서는
"C:\Users\A\Desktop\project\TEST\javaiotest\javaiotest" 를 루트 디렉토리처럼 지정하여 파일을 찾는 것을 알 수 있다. 앞의 경로는 구체적인 경로이지만, 아래 동그라미 표시한 것처럼, 프로젝트의 루트 디렉토리를 기준으로함을 알 수 있다.
일단, 루트 디렉토리를 기준으로 탐색한다는 것은 인지하였다. 그렇다면 상대경로를 사용할 때 루트 디렉토리만을 활용하는 것일까? classpath를 여러 곳 지정할 수 있는 것처럼, 파일을 찾기위한 기준 경로도 여러개가 아닐까??
이를 확인하고자 한다. 혹시나 하는 마음에, classpath로 지정된 곳에 "test.txt"파일을 두고, java에서 찾을 수 있는지 테스트하였다.
경로는 아래와 같고, 현재 classpath가 "C:\Users\A\Desktop\project\TEST\javaiotest\javaiotest\build\classes\java\main" 로 지정되있기 때문에, 해당 경로에 파일을 옮겨두었다.
그러나 결과는 마찬가지로, "test.txt"파일을 찾지 못하였다. '뤼튼'에서 검색해보니 아래와 같은 답변을 얻었다.
상대 경로 탐색 기준
- 현재 작업 디렉토리(Current Working Directory):
- 상대 경로는 현재 작업 디렉토리를 기준으로 파일의 위치를 지정합니다.
- 예를 들어, "./documents/file.txt"는 현재 작업 디렉토리의 documents 폴더 내부에 있는 file.txt 파일을 가리킵니다.
- JAR 파일 내부의 상대 경로:
- JAR 파일 내부에서 상대 경로를 사용하는 경우, JAR 파일의 루트 디렉토리를 기준으로 탐색합니다.
- 예를 들어, JAR 파일 내부의 "resources/file.txt"는 JAR 파일의 resources 폴더 내부에 있는 file.txt 파일을 가리킵니다.
- 클래스 로더(ClassLoader) 기준:
- 클래스 로더를 통해 리소스 파일을 로드하는 경우, 클래스 로더의 기준 디렉토리를 사용합니다.
- 예를 들어, getClass().getResource("/resources/file.txt") 에서 /resources/file.txt는 클래스 로더의 기준 디렉토리 내부에 있는 파일을 가리킵니다.
위 방법 중, "3. 클래스 로더(ClassLoader) 기준"에 착안하여 아래와 같은 코드를 작성하였다.
실행결과, 클래스패스에 위치해놓은 "test.txt"를 제대로 찾고 있음을 확인하였다.
"new File(파일경로)" 와 같은 형태로, File 객체를 생성할 경우, jvm은 작업 디렉토리(프로젝트의 루트 디렉토리)를 기준으로 탐색한다.
만약 작업 디렉토리가 아닌 특정 경로에 파일을 두고 관리하고 싶다면, 해당 경로를 클래스패스로 지정하고, 클래스로더를 통해 파일경로를 가져오는 방법을 활용하면 좋을 것 같다. 스프링에서 정적 리소스를 사용할 때, 아래와 같은 경로에서 가져오는데, 이 방식이 클래스로더를 기준으로 하는 것으로 보인다.
1. classpath:/static
2. classpath:/public
3. classpath:/resources/
4. classpath:/META-INF/resources
참고자료
https://www.baeldung.com/java-path-vs-file
'프로그래밍 언어 > 자바' 카테고리의 다른 글
[Java] 연산자 우선순위, 자료형, 기타 등등(24.05.20) (0) | 2024.05.20 |
---|---|
Object 클래스의 hashCode() native code 밑바닥까지 파헤치기 (2) | 2024.01.21 |
[Java] String & StringBuilder & StringBuffer (0) | 2023.12.12 |
[Java] 람다 (0) | 2023.12.04 |
[Java] 제네릭 (1) | 2023.12.04 |