RandomAccessFile
提供對文件內容的訪問,可以讀文件,也可以寫文件,同時支持隨機訪問文件,可以訪問文件的任意位置。
下面先來看一下構造方法源碼:
public RandomAccessFile(String name, String mode) throws FileNotFoundException { this(name != null ? new File(name) : null, mode); } public RandomAccessFile(File file, String mode) throws FileNotFoundException { String name = (file != null ? file.getPath() : null); int imode = -1; if (mode.equals("r")) imode = O_RDONLY; else if (mode.startsWith("rw")) { imode = O_RDWR; rw = true; if (mode.length() > 2) { if (mode.equals("rws")) imode |= O_SYNC; else if (mode.equals("rwd")) imode |= O_DSYNC; else imode = -1; } } if (imode < 0) throw new IllegalArgumentException("Illegal mode \"" + mode + "\" must be one of " + "\"r\", \"rw\", \"rws\"," + " or \"rwd\""); SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(name); if (rw) { security.checkWrite(name); } } if (name == null) { throw new NullPointerException(); } if (file.isInvalid()) { throw new FileNotFoundException("Invalid file path"); } fd = new FileDescriptor(); fd.attach(this); path = name; open(name, imode); }
通過上述源碼我們可以發現,第一個構造函數的實質是調用第二個構造函數,所以這裏我們主要關注第二個構造函數。
此構造方法有兩個參數,要想打開一個文件,需要先獲得這個文件的File實例對象,第二個參數則是文件訪問模式mode。
從源碼中我們可以看到mode有四種:
r:只讀模式,只能從該文件中讀取數據,不能寫入;
rw:讀寫模式,對該文件既可讀取數據,也可以寫入數據,如果該文件尚不存在,則嘗試創建該文件;
rws:打開以便讀取和寫入,對於 "rw",還要求對文件的內容或元數據的每個更新都同步寫入到底層存儲設備;
rwd:打開以便讀取和寫入,對於 "rw",還要求對文件內容的每個更新都同步寫入到底層存儲設備;
對於後兩種模式可能有些難懂,下面是JDK API 1.6.0版本中的定義:
"rws" 和 "rwd" 模式的工作方式極其類似
FileChannel
類的 force(boolean)
方法,分別傳遞 true 和
false 參數,除非它們始終應用於每個 I/O 操作,並因此通常更爲高效。如果該文件位於本地存儲設備上,那麼當返回此類的一個方法的調用時,可以保證由該調用對此文件所做的所有更改均被寫入該設備。這對確保在系統崩潰時不會丟失重要信息特別有用。如果該文件不在本地設備上,則無法提供這樣的保證。
"rwd" 模式可用於減少執行的 I/O 操作數量。使用 "rwd" 僅要求更新要寫入存儲的文件的內容;使用 "rws" 要求更新要寫入的文件內容及其元數據,這通常要求至少一個以上的低級別 I/O 操作。
也就是說,每次write數據時,"rw"模式,數據不會立即寫到硬盤中;而“rwd”,數據會被立即寫入硬盤。如果寫數據過程發生異常,“rwd”模式中已被write的數據被保存到硬盤,而“rw"則全部丟失。
接下來通過一段簡單的代碼來了解一下RandomAccessFile類的具體作用,這裏順帶講一下常用的幾個編碼:
1 public static void main(String[] args) throws IOException{ 2 File file = new File("demo"); 3 if(!file.exists()){ 4 file.mkdir(); 5 } 6 File demo = new File(file, "raf.dat"); 7 if(!demo.exists()){ 8 demo.createNewFile(); 9 } 10 11 RandomAccessFile raf = new RandomAccessFile(demo, "rw"); 12 //指針的位置 13 System.out.println(raf.getFilePointer()); 14 15 raf.write('A'); //write只寫一個字節,後8位 16 System.out.println(raf.getFilePointer()); 17 raf.write('B'); 18 19 int i = 0x7fffffff; 20 //write方法每次只能寫一個字節,如果要把這個i寫進去需要4次 21 raf.write(i >>> 24);//高8位 22 raf.write(i >>> 16); 23 raf.write(i >>> 8); 24 raf.write(i); 25 System.out.println(raf.getFilePointer()); 26 27 //可以直接寫一個int 28 raf.writeInt(i); 29 30 String s = "中"; 31 byte[] gbk = s.getBytes("GBK"); 32 raf.write(gbk); 33 System.out.println(raf.getFilePointer()); 34 35 //讀文件,必須把指針移到頭部 36 raf.seek(0); 37 //一次性讀取,把文件中的內容都讀到字節數組中 38 byte[] buf = new byte[(int)raf.length()]; 39 raf.read(buf); 40 System.out.println(Arrays.toString(buf)); 41 raf.close(); 42 }
第二行獲取一個File對象,而參數沒有給具體路徑,那就直接默認是你項目所在的路徑下創建該目錄。在該文件下創建raf.dat文件。
11行,獲取raf.dat文件的RandomAccessFile對象,以rw模式打開。
raf.getFilePointer() 用來獲取文件指針的位置,返回一個int類型的數值。此時是空文件,所以12行輸出結果爲0
15行使用write()方法寫入數據,write()方法一次只能寫入一個字節(後8位),同時指針指向下一個位置,準備再次寫入,所以16行輸出結果爲1
19行定義一個二進制32位的int型變量,因爲write()方法一次只寫一個字節,所以需要4次才能將該數據寫入文件,通過移位操作依次寫入
15,17,21-24行共寫入6次,所以25行輸出6
28行中,RandomAccessFile類提供了直接寫入一個int類型值的方法writeInt(),我們來看一下這個方法的源碼:
1 /** 2 * Writes an {@code int} to the file as four bytes, high byte first. 3 * The write starts at the current position of the file pointer. 4 * 5 * @param v an {@code int} to be written. 6 * @exception IOException if an I/O error occurs. 7 */ 8 public final void writeInt(int v) throws IOException { 9 write((v >>> 24) & 0xFF); 10 write((v >>> 16) & 0xFF); 11 write((v >>> 8) & 0xFF); 12 write((v >>> 0) & 0xFF); 13 //written += 4; 14 }
可以發現,該方法的實現原理就是分4次寫入一個int型數據,位與上0xFF是爲了保留低8位,把前面的0都清除掉,看了源碼是不是恍然大悟~
接着看代碼,30行定義一箇中文字符,31行以“GBK”的編碼方式將其轉換爲字節數組,write()方法可以直接寫入一個字節數組,33行輸出12
小談一下字符編碼,GBK編碼:中文字符佔2個字節,英文字符佔1個字節;
UTF-8編碼:中文字符佔3個字節,英文字符佔1個字節;
UTF-16BE編碼:中文字符佔2個字節,英文字符佔2個字節;
所以33行輸出12,如果將31的編碼改爲UTF-8,再運行,可以發現,33行將會輸出13
36行seek()方法將文件指針移動到頭部,然後一次性讀取,將文件內容全部讀取到字節數組中
41行,對文件內容操作(讀寫)完成後,一定要執行close()方法進行關閉(Oracle官方說明)。