java讀取apollo配置「java判斷文件夾是否存在該文件」

所謂的 IO 即 Input(輸入)/Output(輸出) ,當軟體與外部資源(例如:網路,資料庫,磁碟文件)交互的時候,就會用到 IO 操作。而在IO操作中,最常用的一種方式就是流,也被稱為IO流。IO操作比較複雜,涉及到的系統調用比較多,相對操作內存而言性能很低。然而值得興奮的是,Java提供了很多實現類以滿足不同的使用場景,這樣的實現類有很多,我只挑選些在日常編碼中經常用到的類進行說明,本節主要介紹和文件操作相關的流,下面一起來瞧瞧吧。

Java中常用IO流之文件流的基本使用姿勢

File

File是Java在整個文件IO體系中比較基礎的類,它可以實現對文件,文件夾以及路徑的操作,譬如:創建文件或文件夾,獲取絕對路徑,判斷是否存在,重命名,刪除,獲取當前目錄下的文件或文件夾等操作。

File file = new File("example"); //相對路徑
 
System.out.println(file.getAbsolutePath()); //獲取絕對路徑
 
System.out.println(file.getName()); //獲取名稱
 
System.out.println(file.exists()); //判斷文件或文件夾是否存在
 
boolean result = file.mkdirs();// 把 example 當成文件夾來創建,mkdirs()為級聯創建
System.out.println(result);
 
result = file.createNewFile();// 把 example 當成文件夾來創建
System.out.println(result);

在使用File的時候有幾點需要注意:

  1. 傳入File中的參數路徑可以存在也可以不存在。
  2. 傳入File中的參數路徑如果是相對路徑,那麼這個路徑是相對於當前Java Project根目錄的。
  3. 當傳入的路徑不存在的時候,是無法根據 isDirectory() 或 isFile() 來判斷是文件夾還是文件

當有需求進行遍歷指定目錄下所有指定後綴名或是指定名稱文件或文件夾時,需要在ListFile的參數中提供一個名為filter的過濾器來幫助實現過濾功能,這個過濾器Java是不進行提供的,要根據自己的需求來實現。如果要使用這個方法需要實現FileFilter 類。如下實現了一個過濾指定文件的後綴名的過濾器。

class ExtendNameFilter implements FileFilter {
 
 private String extendName;
 
 public ExtendNameFilter(String extendName) {
 this.extendName = extendName;
 }
 
 public boolean accept(File dir){
 if(dir.isDirectory())
 return true;
 return dir.getName().endsWith(this.extendName);
 }
}
// 篩選指定文件夾下文件以.java結尾的文件
File[] files = file.listFiles(new ExtendNameFilter(".java")); 
for(File f : files) {
 System.out.println(f.getName());
 }

File還有很多常用的操作,由於篇幅有限這裡就不逐個演示,更多操作的使用方式和如上示例在調用方法上沒有任何區別,主要注意參數和返回值即可。

位元組,字元和編碼格式

對於位元組,字元和編碼格式這裡不做概念性的描述,詳細的釋義網上有很多,請自行查閱。但從表現形式上對於它們可以大致這樣理解:位元組和字元對於系統數據而言表現形式是不同的,可以通過打開一些文件來觀察,如果打開的是圖片或者是可執行程序文件,那麼就會看到一些類似於亂碼的東西;而如果是文本文件,基本上會看到明文數據,例如「你好」,「Hello World」等。對於前一種看不懂的就是使用位元組來表示的,能看的懂得就是使用字元來表示的。而字元也是通過位元組來存儲的,只不過,在不同的編碼格式中所使用的位元組數是不一樣的,具體哪些字元需要多少個位元組表示需要對應的編碼表。例如:使用GBK編碼存儲漢字字元,則用2個位元組來表示,但在UTF8中則使用3個位元組來表示

FileOutputStream & FileInputStream 位元組流

File只是能操作文件或文件夾,但是並不能操作文件中的內容,要想操作文件的內容就需要使用文件IO流,其操作文件的內容主要有兩種方式:以位元組的方式和以字元的方式。而該小節主要講以位元組文件流的形式操作文件內容,以字元文件流的方式操作我留到下一小節進行說明。

在Java中以位元組流的形式操作文件內容的類主要是FileOutputStream 和 FileInputStream。 分別是 OutputStream(位元組輸出流) 和 InputStream(位元組輸入流) 抽象基類的子類。下面以圖片的複製來展示下該流的用法。

File sourceFile = new File("sourceFile.jpg"); 
File destFile = new File("destFile.jpg");
FileInputStream fis=null; // 讀取源文件的流
FileOutputStream fos = null; // 輸出到目標文件的流
try {
 fis = new FileInputStream(sourceFile);
 fos = new FileOutputStream(destFile);
 byte[] bytes= new byte[1024];
 int len = 0;
 while((len=fis.read(bytes))!=-1) {
 fos.write(bytes, 0, len);
 }
}
catch(IOException ex) {}
finally {
 try { fis.close();} catch(IOException ex) {} 
 try { fos.close();} catch(IOException ex) {} 
}

在使用 FileOutputStream 和 FileInputStream 的過程中需要注意的地方:

  1. FileInputStream 所要操作的文件必須存在,否則就會拋出異常。而 FileOutputStream 寫入的目的文件則不需要存在,當不存在時會被創建,存在的時候會被覆蓋,也可以使用 FileOutputStream 造函數的第二個參數,來實現追加文件內容。
  2. 在使用 FileInputStream 讀取位元組的時候,當讀取到位元組的末尾,再繼續讀取,無論多少次都會返回 -1,而返回值len表示本次讀取了多少個位元組。通常情況下每次讀取1024個位元組,可以達到空間和時間的平衡。但是具體情況也是需要具體分析的。
  3. 位元組流是不存在緩衝區的,所以不需要使用flush操作刷新緩衝區,位元組的讀取和寫入都是通過操作系統來實現的。
  4. 只要是流就是需要關閉的,無論是否在異常情況下都需要關閉流,防止佔用系統資源,導致其他程序無法對該文件進行操作。但是在關閉流的時候也有可能會報異常,所以也需要 try…catch。

FileOutputStream 和 FileInputStream主要用來操作位元組表現形式的文件,例如圖片,可執行程序等。當然操作字元表現形式的文件也是沒有問題的,只不過這麼干不規範。

OutputStreamWriter & InputStreamReader

這小節主要講以字元流的形式操作文件,在Java中對應操作的主要類為 OutputStreamWriter 和 InputStreamReader 。有時候又稱它們為轉換流,具體原因一會在說,先看一個例子。

File sourceFile = new File("sourceFile.txt");
File destFile = new File("destFile.txt");
 
FileInputStream fis= new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(destFile); 
InputStreamReader reader=null;
OutputStreamWriter writer=null;
try {
 reader= new InputStreamReader(fis,"utf-8");
 writer =new OutputStreamWriter(fos,"gbk");
 char[] cbuf =new char[1024];
 int len=0;
 while((len=reader.read(cbuf))!=-1) {
 System.out.println(String.copyValueOf(cbuf,0,len));
 writer.write(cbuf, 0, len);
 }
}
catch(IOException ex) {
 try{reader.close();}catch(IOException ex) { }
 try{writer.close();}catch(IOException ex) { }
}

上述示例主要實現了一個文件的複製,與位元組流的使用方式不同的是,字元流的構造函數需要傳遞位元組流和編碼格式。這是因為操作文件內容都是以位元組的形式來操作的。字元輸入流根據編碼表對位元組流讀取的位元組轉義成字元,同時也說明了傳遞編碼表格式參數的重要性。如果被讀取文件編碼格式是UTF-8且不傳遞這個參數,那麼這個參數為操作系統的默認編碼表(對於Windows而言是GBK),如果默認的編碼表與UTF-8不同(與系統編碼表格式相同,可不傳遞此參數),在轉義為字元的過程中就會出現問題。假如文件內容為「好」,在UTF-8中對應的位元組為-10-20-30。那麼就以系統的默認編碼表來轉義,假如默認為GBK,「好」字的編碼為-50-60,由原來3個位元組表示漢字,現在變成了2個位元組表示漢字,又由於編碼表不兼容,所以導致出現亂碼。而在使用字元輸出流的時候,將字元按照編碼表參數轉化為位元組後再寫入對應編碼格式的文件中去。如果輸出的內容是以追加的方式,那麼需要保證前後兩個輸出文件內容的編碼格式一樣,否則也會出現亂碼。假如之前的輸出文件是GBK格式,你使用字元輸出流輸出的字元格式為UTF8並追加到文件中去,這個時候亂碼就產生了。綜上過程,也就知道大家為什麼又稱 FileOutputStream 和 InputStreamReader 為轉換流了。

傳遞給字元流的位元組流不需要單獨的進行關係,在字元流關閉的時候會調用位元組流的close()方法。

FileWriter & FileReader

FileWriter 和 FileReader 分別是 OutputStreamWriter 和 InputStreamReader 的子類,只不過他們是只能操作系統默認編碼表的字元流。也可以這麼簡單的理解: OutputStreamWriter 和 InputStreamReader 的構造函數不支持傳遞第二個參數,就是操作系統默認的編碼表。所以在使用上只需要注意操作的文件編碼格式是否與系統默認的編碼格式一致即可。既然不傳遞第二個參數就可以達到相同的效果,為什麼還會有這個兩個類呢?因為這兩個類操作簡單。下面還是以複製文件為例。

File sourceFile = new File("sourceFile.txt"); 
File destFile = new File("destFile.txt");
 
FileReader reader=null; 
 FileWriter writer=null; 
try { 
 reader= new FileReader(sourceFile); 
 writer =new FileWriter(destFile);
 char[] cbuf =new char[1024]; 
 int len=0; 
 while((len=reader.read(cbuf))!=-1) {
 System.out.println(String.copyValueOf(cbuf,0,len)); 
 writer.write(cbuf, 0,len); 
 } 
} 
catch(IOException ex) { } 
finally {
 try{reader.close();}catch(IOException ex) { }
 try{writer.close();}catch(IOException ex) { } 
}

無論是使用 FileWriter & FileReader 還是 OutputStreamWriter & InputStreamReader ,在他們的內部都會存在緩衝區的,默認大小為8192位元組。如果不對流進行關閉的話,數據會繼續存在緩衝區,不會存儲到文件上,除非手動調用flush方法或者是在緩衝區中寫入的數據超過了緩衝區的大小,數據才會刷新到文件上。而調用close方法的內部會先調用flush刷新緩衝區。

BufferedOutputStream & BufferedInputStream & BufferedWriter & BufferedReader

這四個Buffered開頭的類分別是為位元組流和字元流提供一個合適的緩衝區來提高讀寫性能,尤其是在讀寫數據量很大的時候效果更佳顯著。其用法和不帶Buffered的流沒有任何區別,只不過在不帶Buffered流的基礎上提供了一些更加便利的方法,例如newLine(),ReadLine()和ReadAllBytes(),他們會根據操作系統的不同添加合適的換行符,根據合適的換行符來讀取一行數據和讀取所有位元組。來看一下用法以緩衝字元流為例

File sourceFile = new File("sourceFile.txt");
File destFile = new File("destFile.txt");
BufferedWriter bw =null;
BufferedReader br =null;
try {
 FileReader reader= new FileReader(sourceFile); 
 FileWriter writer=new FileWriter(destFile); 
 bw =new BufferedWriter(writer);
 br =new BufferedReader(reader);
 String line =null;
 while((line=br.readLine())!=null) {
 bw.write(line);
 bw.newLine();
 }
}
catch(IOException ex) {}
finally {
 try { bw.close();} catch(IOException ex) {} 
 try { br.close();} catch(IOException ex) {} 
}

上述的代碼中有兩點需要注意:

  1. 當按照行來讀取字元的時候,當下一行沒有內容,繼續讀取下一行的內容,結果會返回 null,可以此來判斷文件中是否還有字元。
  2. 當讀取的文件行返回為null後,仍然會執行一次循環,此時調用newLine() 會在寫入的文件中多添加一個換行符,這個換行符無關緊要,可以不用考慮處理掉。

ObjectOutputStream & ObjectInputStream

在編寫程序的過程中,難免會遇到和外部程序進行數據交流的需求,例如調用外部服務,並傳輸一個對象給對方,此時需要把傳輸對象序列化為流才能和外部程序進行交互。又比如需要對一個對象進行深拷貝,也可以將對象序列化為流之後再反序列化為一個新的對象。Java提供了ObjectOutputStream 和 ObjectInputStream 來實現對對象的序列化和反序列化。序列化後的流為位元組流,為了清晰的看到序列化後的結果,以下將序列化後的流輸出到文件中然後在反序列化為一個對象,具體來看一看吧。

Student stu =new Student("vitamin",20,1);
File destFile = new File("destFile.txt");
// 序列化對象到文件中
ObjectOutputStream oos= null;
try {
 FileOutputStream fos = new FileOutputStream(destFile); 
 oos =new ObjectOutputStream(fos); 
 oos.writeObject(stu);
}
catch(IOException ex) {}
finally {
 try {oos.close();}catch(IOException ex) {}
}
// 反序列化文件中的流為對象
ObjectInputStream ois= null;
try {
 FileInputStream fis = new FileInputStream(destFile);
 ois =new ObjectInputStream(fis);
 Student newStu = (Student)ois.readObject();
 System.out.println(newStu.toString());
}
catch(Exception ex) {}
finally {
 try {ois.close();}catch(IOException ex) {}
}
// Student 類定義
class Student implements Serializable{
 private String Name;
 public int Age;
 public transient int Sex;
 public static String ClassName;
 
 private final static long serialVersionUID= -123123612836L;
 
 public Student(String name,int age,int sex) {
 this.Name =name;
 this.Age = age;
 this.Sex = sex;
 }
 
 @Override
 public String toString() {
 return String.format("Name=%s,Age=%d,Sex=%d", this.Name,this.Age,this.Sex);
 }
}

對象要想成功實現序列化和反序列化需要注意以下幾點:

  1. 對象要想實現序列化,被序列化的對象要實現標記介面 Serializable。
  2. 無論屬性訪問許可權如何,都可以進行序列化和反序列化,但靜態屬性無法被序列化和反序列化。
  3. 如果在對象序列化的過程中,不想讓某個屬性參與其中,可以使用關鍵字 transient 進行標記。
  4. 序列化到文件後是不要進行flush操作的,同位元組流一樣也不存在緩衝區。
  5. 如果對象在序列化後,對對象的屬性的修改(比如訪問屬性的變更,欄位類型的變更)都會導致在反序列後出現類似錯誤 :
  6. Student; local class incompatible: stream classdesc serialVersionUID = -123123612836, local class serialVersionUID = -1225000535040348600 這是由於對象在編譯成class文件過程中會對屬性生成一個 serialVersionUID ,這個屬性也會存儲到序列化後的對象中,每次屬性的變更都會導致它進行修改,如果出現前後不一致,則導致出現以上錯誤。如果想避免這個問題,需要在對象內指定 serialVersionUID ,具體數值什麼都可以。但是屬性的定義一定要是 final static long。
  7. 反序列化後的對象是Object類型,不是Student。如果需要使用Student對象的屬性或方法,需要進行強制類型轉化。
  8. 對象在序列化和反序列化的過程中,拋出的不只有IOException。如果刪除Student類定義或是Student.class文件,然後對序列化後的流調用toString()方法 System.out.println(ois.readObject()); ,就會拋出異常: java.lang.ClassNotFoundException: Student , 如果反序列化後的對象轉為非Student對象,也會報其他的非IOException異常。所以在處理異常的時候,需要考慮到這些情況。

Properties

在學習Java的過程中肯定會接觸到用Map結構來存儲Key/Value關係的數據,在我之前的博客 Java中關於泛型集合類存儲的總結 中講到過它的一個實現類 HashMap。 但是除了HashMap外還有一個實現類HashTable ,它可以實現和HashMap一樣的功能,但是由於是線程安全的(同步的)並且存儲的對象是Object類型,這就導致它的性能對於線程不安全(非同步)HashMap會有所降低,所以不是很常用。但是HashTable有一個子類 Properties 卻很常用,它可以在文件中存儲 Key=Value 形式的數據,可以用其來讀取配置。

Properties prop =new Properties();
prop.setProperty("Name", "vitamin");
prop.setProperty("Age", "20");
 
File file =new File("destFile.txt");
FileWriter writer =new FileWriter(file);
prop.store(writer, "this is a test conf"); // 存儲到文件中並設置備註,如果備註是中文則會被轉碼
 
FileReader reader =new FileReader(file);
prop.load(reader);
System.out.println(prop.getProperty("Name")); // vitamin
System.out.println(prop.getProperty("Sex")); // null
System.out.println(prop.getProperty(" Name")); // null

Properties雖然繼承自HashTable,但是它的Key和Value只能是String類型,然而實現內部仍然調用的是put(Object,Object)方法。Properties是允許你直接調用put(Object,Object)方法的,畢竟都是Map的實現類,但是這樣調用了之後,在運行時會報錯並警告你只能設置String類型的數據。

Properties通過load和store方法將Key=Value的對應關係從文件中載入並轉化為Properties對象和將Properties對象轉化為Key=Value對應關係存在到文件中。注意:在文件中存儲的Key=Value關係形式,在等號兩側是否有空格很重要,如果有空格,雖然看上去是沒什麼問題,但是對於Properties對象而言卻不是你想要的結果,可以自己嘗試一下。如果需要在被Load的文件中添加註釋的話,則在行首添加 # 即可。

#this is a test conf
#Sat Sep 21 15:03:54 CST 2019
Age=20
Name=vitamin

PrintStream & PrintWriter

最後再來說一下Java提供的列印流 PrintStream 和 PrintWriter,可以在輸出的數據上做一些格式化操作。 提起 PrintStream 你可能會感到很陌生,但你是否留意過經常使用的System.out.print() 方法的內部實現,它的底層就是使用 PrintStream 來操作的,PrintStream 繼承自文件位元組流 FileOutputStream。對於後者 PrintWriter 更加常用,因為它實現了前者的所有方法,並且可以實現對字元流的列印,這是PrintStream所沒有的。所以 PrintWriter 也更加靈活。下面通過示例來感受下 PrintWriter吧

File file =new File("destFile.txt");
PrintWriter pw =null;
try{
 pw = new PrintWriter(file);
 pw.printf("Name=%s", "vitamin");
 pw.flush();
}
catch(IOException ex) {}
finally {
 pw.close(); 
}

值得注意的一點是 PrintWriter 的 close() 方法不會拋出IOException,因為在底層這個異常已經被捕捉並處理了。

PrintWriter的內部是有緩衝區的(當構造函數傳入的是File類型時,內部使用的是BefferedWriter來實現的),所以需要手動調用flush()方法。但是PrintWriter的構造函數支持第二個參數:是否啟用自動刷新緩衝。當設置為true後,僅當調用 println , printf , format 方法時才會生效。

IO流的選擇

上面說了這麼多的IO流,到底什麼場景下需要該使用什麼流呢?來看一張圖

Java中常用IO流之文件流的基本使用姿勢

除了上面的圖之外還需要在額外問自己幾個問題:

  1. 是否需要進行序列化和反序列化操作?如果是則選擇 ObjectInputStream 或 ObjectOutputStream。
  2. 是否需要讀取Key=Value形式或者是想要存儲成Key=Value形式的配置?如果是可以選擇 Properties 操作起來更加方便。
  3. 是否需要列印指定格式的數據到輸出文件?可以考慮使用 PrintWriter,其實它就是在流的基礎上提供了一些更加簡潔的操作。

end:如果你覺得本文對你有幫助的話,記得關注點贊轉發,你的支持就是我更新動力。

原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/250190.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
投稿專員的頭像投稿專員
上一篇 2024-12-13 13:30
下一篇 2024-12-13 13:30

相關推薦

發表回復

登錄後才能評論