Java IO: 讀取classpath資源
classpath
classpath
是JVM用到的一個環境變數,它用來指示JVM如何搜尋class
。因為Java是編譯型語言,原始碼檔案是.java
,而編譯後的.class
檔案才是真正可以被JVM執行的位元組碼。因此,JVM需要知道,如果要載入一個abc.xyz.Hello
的類,應該去哪搜尋對應的Hello.class
檔案。所以,classpath
就是一組目錄的集合,它設定的搜尋路徑與作業系統相關。例如,在Windows系統上,用;
分隔,帶空格的目錄用""
括起來,可能長這樣:
C:\work\project1\bin;C:\shared;"D:\My Documents\project1\bin"
在Linux系統上,用:
分隔,可能長這樣:
/usr/shared:/usr/local/bin:/home/liaoxuefeng/bin
現在我們假設classpath
是.;C:\work\project1\bin;C:\shared
,當JVM在載入abc.xyz.Hello
這個類時,會依次查詢:
- <當前目錄>\abc\xyz\Hello.class
- C:\work\project1\bin\abc\xyz\Hello.class
- C:\shared\abc\xyz\Hello.class
注意到.
代表當前目錄。如果JVM在某個路徑下找到了對應的class
檔案,就不再往後繼續搜尋。如果所有路徑下都沒有找到,就報錯。
classpath的設定方法
classpath
的設定方法有兩種:
-
在系統環境變數中設定
classpath
環境變數,不推薦; -
在啟動JVM時設定
classpath
變數,推薦。
強烈不推薦在系統環境變數中設定classpath
,那樣會汙染整個系統環境。在啟動JVM時設定classpath
才是推薦的做法。實際上就是給java
命令傳入-classpath
或-cp
引數:
java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello
或者使用-cp
的簡寫:
java -cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello
沒有設定系統環境變數,也沒有傳入-cp
引數,那麼JVM預設的classpath
為.
,即當前目錄:
java abc.xyz.Hello
上述命令告訴JVM只在當前目錄搜尋Hello.class
。
在IDE中執行Java程式,IDE自動傳入的-cp
引數是當前工程的bin
目錄和引入的jar包。通常,我們在自己編寫的class
中,會引用Java核心庫的class
,例如,String
、ArrayList
等。這些class
應該上哪去找?不需要告訴JVM如何去Java核心庫查詢class
,JVM可以自主找到自己的核心庫。 不要把任何Java核心庫新增到classpath中!JVM根本不依賴classpath載入核心庫!更好的做法是,不要設定classpath
!預設的當前目錄.
對於絕大多數情況都夠用了。
從classpath讀取檔案
很多Java程式啟動的時候,都需要讀取配置檔案。例如,從一個.properties
檔案中讀取配置:
String conf = "C:\\conf\\default.properties";
try (InputStream input = new FileInputStream(conf)) {
// TODO:
}
這段程式碼要正常執行,必須在C盤建立conf
目錄,然後在目錄裡建立default.properties
檔案。但是,在Linux系統上,路徑和Windows的又不一樣。因此,從磁碟的固定目錄讀取配置檔案,不是一個好的辦法。
有沒有路徑無關的讀取檔案的方式呢?
從classpath讀取檔案就可以避免不同環境下檔案路徑不一致的問題:如果我們把default.properties
檔案放到classpath中,就不用關心它的實際存放路徑。
在classpath中的資原始檔,路徑總是以/
開頭,我們先獲取當前的Class
物件,然後呼叫getResourceAsStream()
就可以直接從classpath讀取任意的資原始檔:
try (InputStream input = getClass().getResourceAsStream("/default.properties")) {
// TODO:
}
呼叫getResourceAsStream()
需要特別注意的一點是,如果資原始檔不存在,它將返回null
。因此,我們需要檢查返回的InputStream
是否為null
,如果為null
,表示資原始檔在classpath中沒有找到:
try (InputStream input = getClass().getResourceAsStream("/default.properties")) {
if (input != null) {
// TODO:
}
}
如果我們把預設的配置放到jar包中,再從外部檔案系統讀取一個可選的配置檔案,就可以做到既有預設的配置檔案,又可以讓使用者自己修改配置:
Properties props = new Properties();
props.load(inputStreamFromClassPath("/default.properties"));
props.load(inputStreamFromFile("./conf.properties"));
這樣讀取配置檔案,應用程式啟動就更加靈活。
小結
把資源儲存在classpath中可以避免檔案路徑依賴;Class
物件的getResourceAsStream()
可以從classpath中讀取指定資源;
根據classpath讀取資源時,需要檢查返回的InputStream
是否為null
。