在之前文章中介紹了怎樣在java中實現對txt文件的讀取和寫入的操作,並且可以通過儲存為json格式方便資料的使用,但是如果需要對txt中的資料修改或刪除操作,通常的做法是通過讀取操作,將所有的資料讀取出來放在一個臨時的變數中,例如String中,但是如果資料量比較少則這樣操作沒有問題,但是一旦資料量比較大,例如需要對5G的資料進行刪除操作,則會存在記憶體不足的情況。
因此在對於資料的刪除和修改操作時,建議使用RandomAccessFile來實現,由於其特有的處理方式,在讀取和寫入操作時,使用該類操作仍然可以提高操作效率。
RandomAccessFile類繼承自Object,實現了Closeable、DataInput和DataOutput,沒有繼承位元組流或字元流中的任何一個類,通過DataInput和DataOutput可以方便讀寫操作,且效率較高。
1.寫入
public boolean addFile(String filePath,String data) {
RandomAccessFile rFile = null;
try {
File file = new File(filePath);
rFile = new RandomAccessFile(file, "rw");//讀取檔案
long point = rFile.length();
rFile.seek(point);// 到達檔案尾
rFile.writeBytes(data + "\r\n");
/*rFile.writeUTF(data + "\r\n");
rFile.writeInt(2);*/
rFile.close();
} catch (Exception e) {
return false;
}
return true;
}
複製程式碼
在第5行中建立RandomAccessFile物件時,需要傳入指定的modle,該modle包含四種方式,如下所示:
- "r":只讀方式開啟,如果呼叫任何方式的write時,會丟擲IOException;
- "rw":讀寫方式,如果該檔案不存在,則嘗試建立該檔案;
- "rws":讀寫方式,相比較"rw",該方式要求對檔案的內容或後設資料的每個更新都同步寫入到底層儲存裝置;
- "rwd":讀寫方式,相比較"rw",該方式要求對檔案內容的每個更新都同步至底層儲存裝置。
在第8行中寫入的操作是通過writeBytes方式實現,其實RandomAccessFile的寫入操作不僅包含對於位元組的寫入操作,還包括例如9行writeUTF對於字串的寫入,該方式的寫入預設採用"UTF-8"的方式,因此不必擔心中文亂碼的問題,同時還支援對8中基本資料型別的操作。
以上操作是將資料直接寫入至檔案的末尾位置,實現資料的追加新增,仔細觀察上述的程式碼的第6和7行,在第6行中通過length()方法獲取當前檔案所有的位元組數,通過seek(int)方法跳轉至檔案末尾的位置,該操作有點類似C語言中的指標概念,即通改變指標指向的位置,修改儲存的資料內容,預設新建立時位於0的位置。
因此,我們可以藉助該方法修改指定位置的資料,前提是我們知道欲修改資料的位置,如下示例程式碼:
rFile.seek(point-NUM); //如果已知欲插入的位置,可以直接跳轉至插入點
byte[] ch = new byte[LINE_NUM]; //定義欲插入資料的位元組數
String str = new String(ch); //初始化 使用空行佔位
rFile.seek(point); //重新回到之前檔案的末尾位置
rFile.writeBytes(data); //寫入需要插入的資料
複製程式碼
2.讀取
讀取操作與本文提到的之前bufferedReader.readLine()方式,基本相同,但我們通過設定RandomAccessFile為"r"方式,可以減少記憶體和cpu的消耗,且讀取速度較快。
同樣在讀取操作時,我們不僅可以指定讀取方式為逐行讀取,也可以使用readUTF()方式避免中文亂碼;也支援對8種基本資料型別的讀取。
public String readLine(String filePath) {
StringBuilder stContent = null;
RandomAccessFile rFile = null;
if (new File(filePath).exists()) {
try {
stContent = new StringBuilder();
rFile = new RandomAccessFile(filePath, "r");
String line = null;
//rFile.readUTF();
//rFile.readInt();
while (null != (line = rFile.readLine())) {//迴圈遍歷
stContent.append(line);
}
rFile.close();
} catch (Exception e) {
//異常處理
LogUtils.i("e "+e.toString());
}
}
return stContent.toString();
}
複製程式碼
3. 增加
在新增操作中已經提到可以通過修改point的方式,在指定位置增加資料,但是該操作會影響到增加資料後面的資料,例如之前的資料為"abcdefg",在"c"後增加"123",則修改後的資料則為"abc123g",因此如果不影響之前的資料,需要通過以下操作:
- 通過檔案流,先將之前的資料快取起來;
- 在指定位置增加新的資料;
- 將之前檔案流快取的資料,重寫新增到檔案中。
public static void insert(long pos, String content) {
File tempFile = null;
try {
tempFile = File.createTempFile("temp", null);
//在虛擬機器終止時,請求刪除此抽象路徑名錶示的檔案或目錄
tempFile.deleteOnExit();
FileOutputStream fos = new FileOutputStream(tempFile);
RandomAccessFile raf = new RandomAccessFile(filePath, "rw");
raf.seek(pos);
byte[] buffer = new byte[4];
int num = 0;
while (-1 != (num = raf.read(buffer))) {
fos.write(buffer, 0, num);
}
raf.seek(pos);
raf.writeBytes(content);
FileInputStream fis = new FileInputStream(tempFile);
while (-1 != (num = fis.read(buffer))) {
raf.write(buffer, 0, num);
}
} catch (IOException e) {
e.printStackTrace();
}
}
複製程式碼
4.修改
對於檔案中已有的內容需要進行修改操作,可以通過修改RandomAccessFile位置的方式實現,但是該方式操作起來比較複雜,且需要詳細計算位置引數,本文中建議使用String的split方法,將包含需要替換的資料分隔後,替換操作,流程如下:
- 逐行讀取檔案,判斷是否包含需要替換的內容;
- 查詢到需要替換的內容後,使用split方法,將該行分隔;
- 移動RandomAccessFile的位置至行首,先寫入分隔前部分的內容,再寫入替換的內容,最後寫入分隔後部分的內容。
public static void change(String oldStr,String newStr){
try {
RandomAccessFile raf = new RandomAccessFile(filePath, "rw");
String line;
while (null!=(line=raf.readLine())) {
if(line.contains(oldStr)){
String[] split = line.split(oldStr);
raf.seek(split[0].length());
raf.writeBytes(newStr);
raf.writeBytes(split[1]);
}
}
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
複製程式碼
5.刪除
刪除的操作與修改類似,只是將修改的內容至為空,即可。
6.小結
本文首先簡介了RandomAccessFile類,重點介紹了其“指標”的概念和讀取操作的模式等。通過實際樣例程式碼和流程分析,詳細講解了通過RandomAccessFile類實現了增刪改查的操作。