file(內存)----輸入流---->【程序】----輸出流---->file(內存)
當我們讀寫文本文件的時候,采用Reader是非常方便的,比如FileReader,InputStreamReader和BufferedReader。其中最重要的類是InputStreamReader, 它是字節轉換為字符的橋梁。你可以在構造器重指定編碼的方式,如果不指定的話將采用底層操作系統的默認編碼方式,例如GBK等。使用FileReader讀取文件:
[java] view plain copyFileReader fr = new FileReader("ming.txt"); int ch = 0; while((ch = fr.read())!=-1 ) { [java] view plain copySystem.out.PRint((char)ch); [java] view plain copy} 其中read()方法返回的是讀取得下個字符。當然你也可以使用read(char[] ch,int off,int length)這和處理二進制文件的時候類似。
事實上在FileReader中的方法都是從InputStreamReader中繼承過來的。read()方法是比較好費時間的,如果為了提高效率我們可以使用BufferedReader對Reader進行包裝,這樣可以提高讀取得速度,我們可以一行一行的讀取文本,使用readLine()方法。
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("ming.txt")));String data = null;while((data = br.readLine())!=null){System.out.println(data); }
了解了FileReader操作使用FileWriter寫文件就簡單了,這里不贅述。
Eg.我的綜合實例
testFile:
[java] view plain copyimport java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; public class testFile { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub // file(內存)----輸入流---->【程序】----輸出流---->file(內存) File file = new File("d:/temp", "addfile.txt"); try { file.createNewFile(); // 創建文件 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 向文件寫入內容(輸出流) String str = "親愛的小南瓜!"; byte bt[] = new byte[1024]; bt = str.getBytes(); try { FileOutputStream in = new FileOutputStream(file); try { in.write(bt, 0, bt.length); in.close(); // boolean success=true; // System.out.println("寫入文件成功"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { // 讀取文件內容 (輸入流) FileInputStream out = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(out); int ch = 0; while ((ch = isr.read()) != -1) { System.out.print((char) ch); } } catch (Exception e) { // TODO: handle exception } } } java中多種方式讀文件
[java] view plain copy//------------------參考資料--------------------------------- // //1、按字節讀取文件內容 //2、按字符讀取文件內容 //3、按行讀取文件內容 //4、隨機讀取文件內容 import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomaccessFile; import java.io.Reader; public class ReadFromFile { /** * 以字節為單位讀取文件,常用于讀二進制文件,如圖片、聲音、影像等文件。 * * @param fileName * 文件的名 */ public static void readFileByBytes(String fileName) { File file = new File(fileName); InputStream in = null; try { System.out.println("以字節為單位讀取文件內容,一次讀一個字節:"); // 一次讀一個字節 in = new FileInputStream(file); int tempbyte; while ((tempbyte = in.read()) != -1) { System.out.write(tempbyte); } in.close(); } catch (IOException e) { e.printStackTrace(); return; } try { System.out.println("以字節為單位讀取文件內容,一次讀多個字節:"); // 一次讀多個字節 byte[] tempbytes = new byte[100]; int byteread = 0; in = new FileInputStream(fileName); ReadFromFile.showAvailableBytes(in); // 讀入多個字節到字節數組中,byteread為一次讀入的字節數 while ((byteread = in.read(tempbytes)) != -1) { System.out.write(tempbytes, 0, byteread); } } catch (Exception e1) { e1.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e1) { } } } } /** * 以字符為單位讀取文件,常用于讀文本,數字等類型的文件 * * @param fileName * 文件名 */ public static void readFileByChars(String fileName) { File file = new File(fileName); Reader reader = null; try { System.out.println("以字符為單位讀取文件內容,一次讀一個字節:"); // 一次讀一個字符 reader = new InputStreamReader(new FileInputStream(file)); int tempchar; while ((tempchar = reader.read()) != -1) { // 對于windows下,rn這兩個字符在一起時,表示一個換行。 // 但如果這兩個字符分開顯示時,會換兩次行。 // 因此,屏蔽掉r,或者屏蔽n。否則,將會多出很多空行。 if (((char) tempchar) != 'r') { System.out.print((char) tempchar); } } reader.close(); } catch (Exception e) { e.printStackTrace(); } try { System.out.println("以字符為單位讀取文件內容,一次讀多個字節:"); // 一次讀多個字符 char[] tempchars = new char[30]; int charread = 0; reader = new InputStreamReader(new FileInputStream(fileName)); // 讀入多個字符到字符數組中,charread為一次讀取字符數 while ((charread = reader.read(tempchars)) != -1) { // 同樣屏蔽掉r不顯示 if ((charread == tempchars.length) && (tempchars[tempchars.length - 1] != 'r')) { System.out.print(tempchars); } else { for (int i = 0; i < charread; i++) { if (tempchars[i] == 'r') { continue; } else { System.out.print(tempchars[i]); } } } } } catch (Exception e1) { e1.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { } } } } /** * 以行為單位讀取文件,常用于讀面向行的格式化文件 * * @param fileName * 文件名 */ public static void readFileByLines(String fileName) { File file = new File(fileName); BufferedReader reader = null; try { System.out.println("以行為單位讀取文件內容,一次讀一整行:"); reader = new BufferedReader(new FileReader(file)); String tempString = null; int line = 1; // 一次讀入一行,直到讀入null為文件結束 while ((tempString = reader.readLine()) != null) { // 顯示行號 System.out.println("line " + line + ": " + tempString); line++; } reader.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e1) { } } } } /** * 隨機讀取文件內容 * * @param fileName * 文件名 */ public static void readFileByRandomAccess(String fileName) { RandomAccessFile randomFile = null; try { System.out.println("隨機讀取一段文件內容:"); // 打開一個隨機訪問文件流,按只讀方式 randomFile = new RandomAccessFile(fileName, "r"); // 文件長度,字節數 long fileLength = randomFile.length(); // 讀文件的起始位置 int beginIndex = (fileLength > 4) ? 4 : 0; // 將讀文件的開始位置移到beginIndex位置。 randomFile.seek(beginIndex); byte[] bytes = new byte[10]; int byteread = 0; // 一次讀10個字節,如果文件內容不足10個字節,則讀剩下的字節。 // 將一次讀取的字節數賦給byteread while ((byteread = randomFile.read(bytes)) != -1) { System.out.write(bytes, 0, byteread); } } catch (IOException e) { e.printStackTrace(); } finally { if (randomFile != null) { try { randomFile.close(); } catch (IOException e1) { } } } } /** * 顯示輸入流中還剩的字節數 * * @param in */ private static void showAvailableBytes(InputStream in) { try { System.out.println("當前字節輸入流中的字節數為:" + in.available()); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { String fileName = "C:/temp/newTemp.txt"; ReadFromFile.readFileByBytes(fileName); ReadFromFile.readFileByChars(fileName); ReadFromFile.readFileByLines(fileName); ReadFromFile.readFileByRandomAccess(fileName); } } [java] view plain copy//二、將內容追加到文件尾部 import java.io.FileWriter; import java.io.IOException; import java.io.RandomAccessFile; /** * 將內容追加到文件尾部 */ public class AppendToFile { /** * A方法追加文件:使用RandomAccessFile * * @param fileName * 文件名 * @param content * 追加的內容 */ public static void appendMethodA(String fileName, String content) { try { // 打開一個隨機訪問文件流,按讀寫方式 RandomAccessFile randomFile = new RandomAccessFile(fileName, "rw"); // 文件長度,字節數 long fileLength = randomFile.length(); // 將寫文件指針移到文件尾。 randomFile.seek(fileLength); randomFile.writeBytes(content); randomFile.close(); } catch (IOException e) { e.printStackTrace(); } } /** * B方法追加文件:使用FileWriter * * @param fileName * @param content */ public static void appendMethodB(String fileName, String content) { try { // 打開一個寫文件器,構造函數中的第二個參數true表示以追加形式寫文件 FileWriter writer = new FileWriter(fileName, true); writer.write(content); writer.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { String fileName = "C:/temp/newTemp.txt"; String content = "new append!"; // 按方法A追加文件 AppendToFile.appendMethodA(fileName, content); AppendToFile.appendMethodA(fileName, "append end. n"); // 顯示文件內容 ReadFromFile.readFileByLines(fileName); // 按方法B追加文件 AppendToFile.appendMethodB(fileName, content); AppendToFile.appendMethodB(fileName, "append end. n"); // 顯示文件內容 ReadFromFile.readFileByLines(fileName); } } 1、判斷文件是否存在,不存在創建文件
[java] view plain copyFile file=new File(path+filename); if(!file.exists()) { try { file.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } 2、判斷文件夾是否存在,不存在創建文件夾
[java] view plain copyFile file =new File(path+filename); //如果文件夾不存在則創建 if (!file .exists()) { file .mkdir(); } java 寫文件的三種方法比較
[java] view plain copyimport java.io.File; import java.io.FileOutputStream; import java.io.*; public class FileTest { public FileTest() { } public static void main(String[] args) { FileOutputStream out = null; FileOutputStream outSTr = null; BufferedOutputStream Buff=null; FileWriter fw = null; int count=1000;//寫文件行數 try { out = new FileOutputStream(new File(“C:/add.txt”)); long begin = System.currentTimeMillis(); for (int i = 0; i < count; i++) { out.write(“測試java 文件操作/r/n”.getBytes()); } out.close(); long end = System.currentTimeMillis(); System.out.println(“FileOutputStream執行耗時:” + (end - begin) + ” 豪秒”); outSTr = new FileOutputStream(new File(“C:/add0.txt”)); Buff=new BufferedOutputStream(outSTr); long begin0 = System.currentTimeMillis(); for (int i = 0; i < count; i++) { Buff.write(“測試java 文件操作/r/n”.getBytes()); } Buff.flush(); Buff.close(); long end0 = System.currentTimeMillis(); System.out.println(“BufferedOutputStream執行耗時:” + (end0 - begin0) + ” 豪秒”); fw = new FileWriter(“C:/add2.txt”); long begin3 = System.currentTimeMillis(); for (int i = 0; i < count; i++) { fw.write(“測試java 文件操作/r/n”); } fw.close(); long end3 = System.currentTimeMillis(); System.out.println(“FileWriter執行耗時:” + (end3 - begin3) + ” 豪秒”); } catch (Exception e) { e.printStackTrace(); } finally { try { fw.close(); Buff.close(); outSTr.close(); out.close(); } catch (Exception e) { e.printStackTrace(); } } } } java中的getParentFile
String name = "AAAA.txt";String lujing = "1"+"/"+"2";//定義路徑File a = new File(lujing,name);
a.getParentFile().mkdirs(); //這里如果不加getParentFile(),創建的文件夾為"1/2/AAAA.txt/"
那么,a的意義就是“1/2/AAAA.txt”。這里a是File,但是File這個類在Java里表示的不只是文件,雖然File在英語里是文件的意思。Java里,File至少可以表示文件或文件夾(大概還有可以表示系統設備什么的,這里不考慮,只考慮文件和文件夾)。也就是說,在“1/2/AAAA.txt”真正出現在磁盤結構里之前,它既可以表示這個文件,也可以表示這個路徑的文件夾。那么,如果沒有getParentFile(),直接執行a.mkdirs(),就是說,創建“1/2/AAAA.txt”代表的文件夾,也就是“1/2/AAAA.txt/”,在此之后,執行a.createNewFile(),試圖創建a文件,然而以a為名的文件夾已經存在了,所以createNewFile()實際是執行失敗的。你可以用System.out.println(a.createNewFile())這樣來檢查是不是真正創建文件成功。所以,這里,你想要創建的是“1/2/AAAA.txt”這個文件。在創建AAAA.txt之前,必須要1/2這個目錄存在。所以,要得到1/2,就要用a.getParentFile(),然后要創建它,也就是a.getParentFile().mkdirs()。在這之后,a作為文件所需要的文件夾大概會存在了(有特殊情況會無法創建的,這里不考慮),就執行a.createNewFile()創建a文件。
Java RandomAccessFile的使用
Java的RandomAccessFile提供對文件的讀寫功能,與普通的輸入輸出流不一樣的是RamdomAccessFile可以任意的訪問文件的任何地方。這就是“Random”的意義所在。
RandomAccessFile的對象包含一個記錄指針,用于標識當前流的讀寫位置,這個位置可以向前移動,也可以向后移動。RandomAccessFile包含兩個方法來操作文件記錄指針。
long getFilePoint():記錄文件指針的當前位置。
void seek(long pos):將文件記錄指針定位到pos位置。
RandomAccessFile包含InputStream的三個read方法,也包含OutputStream的三個write方法。同時RandomAccessFile還包含一系列的readXxx和writeXxx方法完成輸入輸出。
RandomAccessFile的構造方法如下

mode的值有四個
"r":以只讀文方式打開指定文件。如果你寫的話會有IOException。
"rw":以讀寫方式打開指定文件,不存在就創建新文件。
"rws":不介紹了。
"rwd":也不介紹。
[java] view plain copy/** * 往文件中依次寫入3名員工的信息, * 每位員工有姓名和員工兩個字段 然后按照 * 第二名,第一名,第三名的先后順序讀取員工信息 */ import java.io.File; import java.io.RandomAccessFile; public class RandomAccessFileTest { public static void main(String[] args) throws Exception { Employee e1 = new Employee(23, "張三"); Employee e2 = new Employee(24, "lisi"); Employee e3 = new Employee(25, "王五"); File file = new File("employee.txt"); if (!file.exists()) { file.createNewFile(); } // 一個中文占兩個字節 一個英文字母占一個字節 // 整形 占的字節數目 跟cpu位長有關 32位的占4個字節 RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); randomAccessFile.writeChars(e1.getName()); randomAccessFile.writeInt(e1.getAge()); randomAccessFile.writeChars(e2.getName()); randomAccessFile.writeInt(e2.getAge()); randomAccessFile.writeChars(e3.getName()); randomAccessFile.writeInt(e3.getAge()); randomAccessFile.close(); RandomAccessFile raf2 = new RandomAccessFile(file, "r"); raf2.skipBytes(Employee.LEN * 2 + 4); String strName2 = ""; for (int i = 0; i < Employee.LEN; i++) { strName2 = strName2 + raf2.readChar(); } int age2 = raf2.readInt(); System.out.println("strName2 = " + strName2.trim()); System.out.println("age2 = " + age2); raf2.seek(0); String strName1 = ""; for (int i = 0; i < Employee.LEN; i++) { strName1 = strName1 + raf2.readChar(); } int age1 = raf2.readInt(); System.out.println("strName1 = " + strName1.trim()); System.out.println("age1 = " + age1); raf2.skipBytes(Employee.LEN * 2 + 4); String strName3 = ""; for (int i = 0; i < Employee.LEN; i++) { strName3 = strName3 + raf2.readChar(); } int age3 = raf2.readInt(); System.out.println("strName3 = " + strName3.trim()); System.out.println("age3 = " + age3); } } class Employee { // 年齡 public int age; // 姓名 public String name; // 姓名的長度 public static final int LEN = 8; public Employee(int age, String name) { this.age = age; // 對name字符長度的一個處理 if (name.length() > LEN) { name = name.substring(0, LEN); } else { while (name.length() < LEN) { name = name + "/u0000"; } } this.name = name; } public int getAge() { return age; } public String getName() { return name; } } 高效的RandomAccessFile
http://zhang-xiujiao.iteye.com/blog/1150751
主體:
RandomAccessFile類。其I/O性能較之其它常用開發語言的同類性能差距甚遠,嚴重影響程序的運行效率。
開發人員迫切需要提高效率,下面分析RandomAccessFile等文件類的源代碼,找出其中的癥結所在,并加以改進優化,創建一個"性/價比"俱佳的隨機文件訪問類BufferedRandomAccessFile。
在改進之前先做一個基本測試:逐字節COPY一個12兆的文件(這里牽涉到讀和寫)。
讀 | 寫 | 耗用時間(秒) |
RandomAccessFile | RandomAccessFile | 95.848 |
BufferedInputStream + DataInputStream | BufferedOutputStream + DataOutputStream | 2.935 |
我們可以看到兩者差距約32倍,RandomAccessFile也太慢了。先看看兩者關鍵部分的源代碼,對比分析,找出原因。
1.1.[RandomAccessFile]
Java代碼
public class RandomAccessFile implements DataOutput, DataInput { public final byte readByte() throws IOException { int ch = this.read(); if (ch < 0) throw new EOFException(); return (byte)(ch); } public native int read() throws IOException; public final void writeByte(int v) throws IOException { write(v); } public native void write(int b) throws IOException; }
可見,RandomAccessFile每讀/寫一個字節就需對磁盤進行一次I/O操作。
1.2.[BufferedInputStream]
Java代碼
public class BufferedInputStream extends FilterInputStream { private static int defaultBufferSize = 2048; protected byte buf[]; // 建立讀緩存區 public BufferedInputStream(InputStream in, int size) { super(in); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } public synchronized int read() throws IOException { ensureOpen(); if (pos >= count) { fill(); if (pos >= count) return -1; } return buf[pos++] & 0xff; // 直接從BUF[]中讀取 } private void fill() throws IOException { if (markpos < 0) pos = 0; /* no mark: throw away the buffer */ else if (pos >= buf.length) /* no room left in buffer */ if (markpos > 0) { /* can throw away early part of the buffer */ int sz = pos - markpos; System.arraycopy(buf, markpos, buf, 0, sz); pos = sz; markpos = 0; } else if (buf.length >= marklimit) { markpos = -1; /* buffer got too big, invalidate mark */ pos = 0; /* drop buffer contents */ } else { /* grow buffer */ int nsz = pos * 2; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buf, 0, nbuf, 0, pos); buf = nbuf; } count = pos; int n = in.read(buf, pos, buf.length - pos); if (n > 0) count = n + pos; } } 1.3.[BufferedOutputStream]
Java代碼
public class BufferedOutputStream extends FilterOutputStream { protected byte buf[]; // 建立寫緩存區 public BufferedOutputStream(OutputStream out, int size) { super(out); if (size <= 0) { throw new IllegalArgumentException("Buffer size <= 0"); } buf = new byte[size]; } public synchronized void write(int b) throws IOException { if (count >= buf.length) { flushBuffer(); } buf[count++] = (byte)b; // 直接從BUF[]中讀取 } private void flushBuffer() throws IOException { if (count > 0) { out.write(buf, 0, count); count = 0; } } } 可見,Buffered I/O putStream每讀/寫一個字節,若要操作的數據在BUF中,就直接對內存的buf[]進行讀/寫操作;否則從磁盤相應位置填充buf[],再直接對內存的buf[]進行讀/寫操作,絕大部分的讀/寫操作是對內存buf[]的操作。
1.3.小結
內存存取時間單位是納秒級(10E-9),磁盤存取時間單位是毫秒級(10E-3),同樣操作一次的開銷,內存比磁盤快了百萬倍。理論上可以預見,即使對內存操作上萬次,花費的時間也遠少對于磁盤一次I/O的開銷。顯然后者是通過增加位于內存的BUF存取,減少磁盤I/O的開銷,提高存取效率的,當然這樣也增加了BUF控制部分的開銷。從實際應用來看,存取效率提高了32倍。
根據1.3得出的結論,現試著對RandomAccessFile類也加上緩沖讀寫機制。
隨機訪問類與順序類不同,前者是通過實現DataInput/DataOutput接口創建的,而后者是擴展FilterInputStream/FilterOutputStream創建的,不能直接照搬。
2.1.開辟緩沖區BUF[默認:1024字節],用作讀/寫的共用緩沖區。
2.2.先實現讀緩沖。
讀緩沖邏輯的基本原理:
A 欲讀文件POS位置的一個字節。B 查BUF中是否存在?若有,直接從BUF中讀取,并返回該字符BYTE。C 若沒有,則BUF重新定位到該POS所在的位置并把該位置附近的BUFSIZE的字節的文件內容填充BUFFER,返回B。以下給出關鍵部分代碼及其說明:
Java代碼
public class BufferedRandomAccessFile extends RandomAccessFile { // byte read(long pos):讀取當前文件POS位置所在的字節 // bufstartpos、bufendpos代表BUF映射在當前文件的首/尾偏移地址。 // curpos指當前類文件指針的偏移地址。 public byte read(long pos) throws IOException { if (pos < this.bufstartpos || pos > this.bufendpos ) { this.flushbuf(); this.seek(pos); if ((pos < this.bufstartpos) || (pos > this.bufendpos)) throw new IOException(); } this.curpos = pos; return this.buf[(int)(pos - this.bufstartpos)]; } // void flushbuf():bufdirty為真,把buf[]中尚未寫入磁盤的數據,寫入磁盤。 private void flushbuf() throws IOException { if (this.bufdirty == true) { if (super.getFilePointer() != this.bufstartpos) { super.seek(this.bufstartpos); } super.write(this.buf, 0, this.bufusedsize); this.bufdirty = false; } } // void seek(long pos):移動文件指針到pos位置,并把buf[]映射填充至POS所在的文件塊。 public void seek(long pos) throws IOException { if ((pos < this.bufstartpos) || (pos > this.bufendpos)) { // seek pos not in buf this.flushbuf(); if ((pos >= 0) && (pos <= this.fileendpos) && (this.fileendpos != 0)) { // seek pos in file (file length > 0) this.bufstartpos = pos * bufbitlen / bufbitlen; this.bufusedsize = this.fillbuf(); } else if (((pos == 0) && (this.fileendpos == 0)) || (pos == this.fileendpos + 1)) { // seek pos is append pos this.bufstartpos = pos; this.bufusedsize = 0; } this.bufendpos = this.bufstartpos + this.bufsize - 1; } this.curpos = pos; } // int fillbuf():根據bufstartpos,填充buf[]。 private int fillbuf() throws IOException { super.seek(this.bufstartpos); this.bufdirty = false; return super.read(this.buf); } } 至此緩沖讀基本實現,逐字節COPY一個12兆的文件(這里牽涉到讀和寫,用BufferedRandomAccessFile試一下讀的速度):
讀 | 寫 | 耗用時間(秒) |
RandomAccessFile | RandomAccessFile | 95.848 |
BufferedRandomAccessFile | BufferedOutputStream + DataOutputStream | 2.813 |
BufferedInputStream + DataInputStream | BufferedOutputStream + DataOutputStream | 2.935 |
可見速度顯著提高,與BufferedInputStream+DataInputStream不相上下。
2.3.實現寫緩沖。
寫緩沖邏輯的基本原理:
A欲寫文件POS位置的一個字節。B 查BUF中是否有該映射?若有,直接向BUF中寫入,并返回true。C若沒有,則BUF重新定位到該POS所在的位置,并把該位置附近的 BUFSIZE字節的文件內容填充BUFFER,返回B。下面給出關鍵部分代碼及其說明:
Java代碼
// boolean write(byte bw, long pos):向當前文件POS位置寫入字節BW。 // 根據POS的不同及BUF的位置:存在修改、追加、BUF中、BUF外等情況。在邏輯判斷時,把最可能出現的情況,最先判斷,這樣可提高速度。 // fileendpos:指示當前文件的尾偏移地址,主要考慮到追加因素 public boolean write(byte bw, long pos) throws IOException { if ((pos >= this.bufstartpos) && (pos <= this.bufendpos)) { // write pos in buf this.buf[(int)(pos - this.bufstartpos)] = bw; this.bufdirty = true; if (pos == this.fileendpos + 1) { // write pos is append pos this.fileendpos++; this.bufusedsize++; } } else { // write pos not in buf this.seek(pos); if ((pos >= 0) && (pos <= this.fileendpos) && (this.fileendpos != 0)) { // write pos is modify file this.buf[(int)(pos - this.bufstartpos)] = bw; } else if (((pos == 0) && (this.fileendpos == 0)) || (pos == this.fileendpos + 1)) { // write pos is append pos this.buf[0] = bw; this.fileendpos++; this.bufusedsize = 1; } else { throw new IndexOutOfBoundsException(); } this.bufdirty = true; } this.curpos = pos; return true; } 至此緩沖寫基本實現,逐字節COPY一個12兆的文件,(這里牽涉到讀和寫,結合緩沖讀,用BufferedRandomAccessFile試一下讀/寫的速度):
讀 | 寫 | 耗用時間(秒) |
RandomAccessFile | RandomAccessFile | 95.848 |
BufferedInputStream + DataInputStream | BufferedOutputStream + DataOutputStream | 2.935 |
BufferedRandomAccessFile | BufferedOutputStream + DataOutputStream | 2.813 |
BufferedRandomAccessFile | BufferedRandomAccessFile | 2.453 |
可見綜合讀/寫速度已超越BufferedInput/OutputStream+DataInput/OutputStream。
高效的RandomAccessFile【續】
http://zhang-xiujiao.iteye.com/blog/1150762
優化BufferedRandomAccessFile。
優化原則:
調用頻繁的語句最需要優化,且優化的效果最明顯。 多重嵌套邏輯判斷時,最可能出現的判斷,應放在最外層。 減少不必要的NEW。這里舉一典型的例子:
Java代碼
public void seek(long pos) throws IOException { ... this.bufstartpos = pos * bufbitlen / bufbitlen; // bufbitlen指buf[]的位長,例:若bufsize=1024,則bufbitlen=10。 ... } seek函數使用在各函數中,調用非常頻繁,上面加重的這行語句根據pos和bufsize確定buf[]對應當前文件的映射位置,用"*"、"/"確定,顯然不是一個好方法。
優化一:this.bufstartpos = (pos << bufbitlen) >> bufbitlen;優化二:this.bufstartpos = pos & bufmask; // this.bufmask = ~((long)this.bufsize - 1);兩者效率都比原來好,但后者顯然更好,因為前者需要兩次移位運算、后者只需一次邏輯與運算(bufmask可以預先得出)。
至此優化基本實現,逐字節COPY一個12兆的文件,(這里牽涉到讀和寫,結合緩沖讀,用優化后BufferedRandomAccessFile試一下讀/寫的速度):
讀 | 寫 | 耗用時間(秒) |
RandomAccessFile | RandomAccessFile | 95.848 |
BufferedInputStream + DataInputStream | BufferedOutputStream + DataOutputStream | 2.935 |
BufferedRandomAccessFile | BufferedOutputStream + DataOutputStream | 2.813 |
BufferedRandomAccessFile | BufferedRandomAccessFile | 2.453 |
BufferedRandomAccessFile優 | BufferedRandomAccessFile優 | 2.197 |
可見優化盡管不明顯,還是比未優化前快了一些,也許這種效果在老式機上會更明顯。
以上比較的是順序存取,即使是隨機存取,在絕大多數情況下也不止一個BYTE,所以緩沖機制依然有效。而一般的順序存取類要實現隨機存取就不怎么容易了。
需要完善的地方
提供文件追加功能:
Java代碼
public boolean append(byte bw) throws IOException { return this.write(bw, this.fileendpos + 1); }
提供文件當前位置修改功能:
Java代碼
public boolean write(byte bw) throws IOException { return this.write(bw, this.curpos); }
返回文件長度(由于BUF讀寫的原因,與原來的RandomAccessFile類有所不同):
Java代碼
public long length() throws IOException { return this.max(this.fileendpos + 1, this.initfilelen); }
返回文件當前指針(由于是通過BUF讀寫的原因,與原來的RandomAccessFile類有所不同):
Java代碼
public long getFilePointer() throws IOException { return this.curpos; }
提供對當前位置的多個字節的緩沖寫功能:
Java代碼
public void write(byte b[], int off, int len) throws IOException { long writeendpos = this.curpos + len - 1; if (writeendpos <= this.bufendpos) { // b[] in cur buf System.arraycopy(b, off, this.buf, (int)(this.curpos - this.bufstartpos), len); this.bufdirty = true; this.bufusedsize = (int)(writeendpos - this.bufstartpos + 1); } else { // b[] not in cur buf super.seek(this.curpos); super.write(b, off, len); } if (writeendpos > this.fileendpos) this.fileendpos = writeendpos; this.seek(writeendpos+1); } public void write(byte b[]) throws IOException { this.write(b, 0, b.length); }
提供對當前位置的多個字節的緩沖讀功能:
Java代碼
public int read(byte b[], int off, int len) throws IOException { long readendpos = this.curpos + len - 1; if (readendpos <= this.bufendpos && readendpos <= this.fileendpos ) { // read in buf System.arraycopy(this.buf, (int)(this.curpos - this.bufstartpos), b, off, len); } else { // read b[] size > buf[] if (readendpos > this.fileendpos) { // read b[] part in file len = (int)(this.length() - this.curpos + 1); } super.seek(this.curpos); len = super.read(b, off, len); readendpos = this.curpos + len - 1; } this.seek(readendpos + 1); return len; } public int read(byte b[]) throws IOException { return this.read(b, 0, b.length); } public void setLength(long newLength) throws IOException { if (newLength > 0) { this.fileendpos = newLength - 1; } else { this.fileendpos = 0; } super.setLength(newLength); } public void close() throws IOException { this.flushbuf(); super.close(); } 至此完善工作基本完成,試一下新增的多字節讀/寫功能,通過同時讀/寫1024個字節,來COPY一個12兆的文件,(這里牽涉到讀和寫,用完善后BufferedRandomAccessFile試一下讀/寫的速度):
讀 | 寫 | 耗用時間(秒) |
RandomAccessFile | RandomAccessFile | 95.848 |
BufferedInputStream + DataInputStream | BufferedOutputStream + DataOutputStream | 2.935 |
BufferedRandomAccessFile | BufferedOutputStream + DataOutputStream | 2.813 |
BufferedRandomAccessFile | BufferedRandomAccessFile | 2.453 |
BufferedRandomAccessFile優 | BufferedRandomAccessFile優 | 2.197 |
BufferedRandomAccessFile完 | BufferedRandomAccessFile完 | 0.401 |
與MappedByteBuffer+RandomAccessFile的對比?
JDK1.4+提供了NIO類 ,其中MappedByteBuffer類用于映射緩沖,也可以映射隨機文件訪問,可見JAVA設計者也看到了RandomAccessFile的問題,并加以改進。怎么通過MappedByteBuffer+RandomAccessFile拷貝文件呢?下面就是測試程序的主要部分:
Java代碼
RandomAccessFile rafi = new RandomAccessFile(SrcFile, "r"); RandomAccessFile rafo = new RandomAccessFile(DesFile, "rw"); FileChannel fci = rafi.getChannel(); FileChannel fco = rafo.getChannel(); long size = fci.size(); MappedByteBuffer mbbi = fci.map(FileChannel.MapMode.READ_ONLY, 0, size); MappedByteBuffer mbbo = fco.map(FileChannel.MapMode.READ_WRITE, 0, size); long start = System.currentTimeMillis(); for (int i = 0; i < size; i++) { byte b = mbbi.get(i); mbbo.put(i, b); } fcin.close(); fcout.close(); rafi.close(); rafo.close(); System.out.println("Spend: "+(double)(System.currentTimeMillis()-start) / 1000 + "s"); 試一下JDK1.4的映射緩沖讀/寫功能,逐字節COPY一個12兆的文件,(這里牽涉到讀和寫):
讀 | 寫 | 耗用時間(秒) |
RandomAccessFile | RandomAccessFile | 95.848 |
BufferedInputStream + DataInputStream | BufferedOutputStream + DataOutputStream | 2.935 |
BufferedRandomAccessFile | BufferedOutputStream + DataOutputStream | 2.813 |
BufferedRandomAccessFile | BufferedRandomAccessFile | 2.453 |
BufferedRandomAccessFile優 | BufferedRandomAccessFile優 | 2.197 |
BufferedRandomAccessFile完 | BufferedRandomAccessFile完 | 0.401 |
MappedByteBuffer+ RandomAccessFile | MappedByteBuffer+ RandomAccessFile | 1.209 |
確實不錯,看來NIO有了極大的進步。建議采用 MappedByteBuffer+RandomAccessFile的方式。