翻譯+轉:java.io和java.nio效能簡單對比

來源:互聯網
上載者:User

我從java1.3開始學習java,後來主要用1.4,再後來1.5和1.6中的很多新特性,都停留在“知道”的狀態,比如nio,雖然據說可以提升效能,但並沒有真正深入使用和測試過,工作操作檔案的情況不多,所以關注也不多,即便用到,也還是習慣性的用java.io。今天看到的這篇文章,雖然測試手段非常簡單,所得結論也難免有些片面 ,但依然說明,在順序訪問的時候,NIO的效能相對java.io有很大的提升。

也許應該update一下自己的知識了,否則就要OUT,或者早已經OUT了。
下次操作檔案或者寫socket要用NIO了。

---
轉自:http://links.techwebnewsletters.com/ctt?kn=28&m=34038811&r=MzI1Mjc3MDAzOAS2&b=0&j=NTc5NjM4MTAS1&mt=1&rt=0
以下為翻譯的內容:

最近我在工作中用到了java i/o相關功能。因為對java.io的瞭解更多(畢竟面世較早),所以一開始我使用的是java.io包下的類,後來為了測試一下是不是能夠通過NIO提高檔案操作效能,於是轉向了java.nio。我得到的結論讓我感到有些震驚,下面是對比測試的一些細節:

   1、在java.io的測試代碼中,我使用RandomAccessFile直接向檔案寫資料,並搜尋到特定的位置執行記錄的插入、讀取和刪除。
   2、在java.nio的初步測試代碼中,使用FileChannel對象。NIO之所以比java.io更加高效,是因為NIO面向的是data chunks,而java.io基本上是面向byte的。
   3、為了進一步挖掘NIO的能力,我又改用MappedByteBuffer執行測試,這個類是構建在作業系統的虛擬記憶體機制上的。根據java文檔所說,這個類在效能方面是最好的。

 

為了進行測試,我寫了一個類比員工資料庫的小程式,員工資料的結構如下:

    class Employee {<br /> String last; // the key<br /> String first;<br /> int id;<br /> int zip;<br /> boolean employed;<br /> String comments;<br /> }

員工資料寫入檔案,並將last name作為索引key,日後可以通過這個key從檔案中載入該員工對應的資料。無論使用IO、NIO還是MappedByteBuffers,首先都需要開啟一個RandomAccessFile。以下代碼在使用者的home目錄下建立一個名為employee.ejb的檔案,設定為可讀可寫,並初始化對應的Channel和MappedByteBuffer:
    String userHome = System.getProperty("user.home");<br /> StringBuffer pathname = new StringBuffer(userHome);<br /> pathname.append(File.separator);<br /> pathname.append("employees.ejb");<br /> java.io.RandomAccessFile journal =<br /> new RandomAccessFile(pathname.toString(), "rw");</p><p> //下面這一句是為了NIO<br /> java.nio.channels.FileChannel channel = journal.getChannel();</p><p> //下面這兩句是為了使用MappedByteBuffer<br /> journal.setLength(PAGE_SIZE);<br /> MappedByteBuffer mbb =<br /> channel.map(FileChannel.MapMode.READ_WRITE, 0, journal.length() );

 
使用channel.map進行映射後,當該檔案被追加了新的資料時,之前的MappedByteBuffer是看不到這些資料的。因為我們想測試讀和寫,所以當檔案中追加寫入新的記錄後,需要重新做映射才能使得MappedByteBuffer讀取新資料。為了提高效率,降低重新對應的次數,每次空間不夠的時候,我們將檔案擴張特定的大小(比如說1K)以防止每次追加新記錄都要重新對應。
 

下面是寫入員工記錄的對比測試:

使用java.io的代碼:
    public boolean addRecord_IO(Employee emp) {<br /> try {<br /> byte[] last = emp.last.getBytes();<br /> byte[] first = emp.first.getBytes();<br /> byte[] comments = emp.comments.getBytes();</p><p> // Just hard-code the sizes for perfomance<br /> int size = 0;<br /> size += emp.last.length();<br /> size += 4; // strlen - Integer<br /> size += emp.first.length();<br /> size += 4; // strlen - Integer<br /> size += 4; // emp.id - Integer<br /> size += 4; // emp.zip - Integer<br /> size += 1; // emp.employed - byte<br /> size += emp.comments.length();<br /> size += 4; // strlen - Integer<br /> long offset = getStorageLocation(size);<br /> //<br /> // Store the record by key and save the offset<br /> //<br /> if ( offset == -1 ) {<br /> // We need to add to the end of the journal. Seek there<br /> // now only if we're not already there<br /> long currentPos = journal.getFilePointer();<br /> long jounralLen = journal.length();<br /> if ( jounralLen != currentPos )<br /> journal.seek(jounralLen);</p><p> offset = jounralLen;<br /> }else {<br /> // Seek to the returned insertion point<br /> journal.seek(offset);<br /> }<br /> // Fist write the header<br /> journal.writeByte(1);<br /> journal.writeInt(size);<br /> // Next write the data<br /> journal.writeInt(last.length);<br /> journal.write(last);<br /> journal.writeInt(first.length);<br /> journal.write(first);<br /> journal.writeInt(emp.id);<br /> journal.writeInt(emp.zip);<br /> if ( emp.employed )<br /> journal.writeByte(1);<br /> else<br /> journal.writeByte(0);<br /> journal.writeInt(comments.length);<br /> journal.write(comments);<br /> // Next, see if we need to append an empty record if we inserted<br /> // this new record at an empty location<br /> if ( newEmptyRecordSize != -1 ) {<br /> // Simply write a header<br /> journal.writeByte(0); //inactive record<br /> journal.writeLong(newEmptyRecordSize);<br /> }<br /> employeeIdx.put(emp.last, offset);<br /> return true;<br /> }<br /> catch ( Exception e ) {<br /> e.printStackTrace();<br /> }<br /> return false;<br /> }
 

使用java.nio的代碼:

    public boolean addRecord_NIO(Employee emp) {<br /> try {<br /> data.clear();<br /> byte[] last = emp.last.getBytes();<br /> byte[] first = emp.first.getBytes();<br /> byte[] comments = emp.comments.getBytes();<br /> data.putInt(last.length);<br /> data.put(last);<br /> data.putInt(first.length);<br /> data.put(first);<br /> data.putInt(emp.id);<br /> data.putInt(emp.zip);<br /> byte employed = 0;<br /> if ( emp.employed )<br /> employed = 1;<br /> data.put(employed);<br /> data.putInt(comments.length);<br /> data.put(comments);<br /> data.flip();<br /> int dataLen = data.limit();<br /> header.clear();<br /> header.put((byte)1); // 1=active record<br /> header.putInt(dataLen);<br /> header.flip();<br /> long headerLen = header.limit();<br /> int length = (int)(headerLen + dataLen);<br /> long offset = getStorageLocation((int)dataLen);<br /> //<br /> // Store the record by key and save the offset<br /> //<br /> if ( offset == -1 ) {<br /> // We need to add to the end of the journal. Seek there<br /> // now only if we're not already there<br /> long currentPos = channel.position();<br /> long jounralLen = channel.size();<br /> if ( jounralLen != currentPos )<br /> channel.position(jounralLen);<br /> offset = jounralLen;<br /> }<br /> else {<br /> // Seek to the returned insertion point<br /> channel.position(offset);<br /> }<br /> // Fist write the header<br /> long written = channel.write(srcs);<br /> // Next, see if we need to append an empty record if we inserted<br /> // this new record at an empty location<br /> if ( newEmptyRecordSize != -1 ) {<br /> // Simply write a header<br /> data.clear();<br /> data.put((byte)0);<br /> data.putInt(newEmptyRecordSize);<br /> data.flip();<br /> channel.write(data);<br /> }<br /> employeeIdx.put(emp.last, offset);<br /> return true;<br /> }<br /> catch ( Exception e ) {<br /> e.printStackTrace();<br /> }<br /> return false;<br /> }
 
使用MappedByteBuffer的代碼如下:
 
   public boolean addRecord_MBB(Employee emp) {<br /> try {<br /> byte[] last = emp.last.getBytes();<br /> byte[] first = emp.first.getBytes();<br /> byte[] comments = emp.comments.getBytes();<br /> int datalen = last.length + first.length + comments.length + 12 + 9;<br /> int headerlen = 5;<br /> int length = headerlen + datalen;<br /> //<br /> // Store the record by key and save the offset<br /> //<br /> long offset = getStorageLocation(datalen);<br /> if ( offset == -1 ) {<br /> // We need to add to the end of the journal. Seek there<br /> // now only if we're not already there<br /> long currentPos = mbb.position();<br /> long journalLen = channel.size();<br /> if ( (currentPos+length) >= journalLen ) {<br /> //log("GROWING FILE BY ANOTHER PAGE");<br /> mbb.force();<br /> journal.setLength(journalLen + PAGE_SIZE);<br /> channel = journal.getChannel();<br /> journalLen = channel.size();<br /> mbb = channel.map(FileChannel.MapMode.READ_WRITE, 0, journalLen);<br /> currentPos = mbb.position();<br /> }<br /> if ( currentEnd != currentPos )<br /> mbb.position(currentEnd);<br /> offset = currentEnd;//journalLen;<br /> }<br /> else {<br /> // Seek to the returned insertion point<br /> mbb.position((int)offset);<br /> }<br /> // write header<br /> mbb.put((byte)1); // 1=active record<br /> mbb.putInt(datalen);<br /> // write data<br /> mbb.putInt(last.length);<br /> mbb.put(last);<br /> mbb.putInt(first.length);<br /> mbb.put(first);<br /> mbb.putInt(emp.id);<br /> mbb.putInt(emp.zip);<br /> byte employed = 0;<br /> if ( emp.employed )<br /> employed = 1;<br /> mbb.put(employed);<br /> mbb.putInt(comments.length);<br /> mbb.put(comments);<br /> currentEnd += length;<br /> // Next, see if we need to append an empty record if we inserted<br /> // this new record at an empty location<br /> if ( newEmptyRecordSize != -1 ) {<br /> // Simply write a header<br /> mbb.put((byte)0);<br /> mbb.putInt(newEmptyRecordSize);<br /> currentEnd += 5;<br /> }<br /> employeeIdx.put(emp.last, offset);<br /> return true;<br /> }<br /> catch ( Exception e ) {<br /> e.printStackTrace();<br /> }<br /> return false;<br /> }
 

接下來,調用每種方法插入100,000條記錄, 耗時對比如下:
    * With java.io: ~10,000 milliseconds
    * With java.nio: ~2,000 milliseconds
    * With MappedByteBuffer: ~970 milliseconds

 
使用NIO的效能改善效果非常明顯,使用MappedByteBuffer的效能,更是讓人吃驚。

使用三種方式讀取資料的效能對比如下:
    * With java.io: ~6,900 milliseconds
    * With java.nio: ~1,400 milliseconds
    * With MappedByteBuffer: ~355 milliseconds

和寫入的時候情況差不多,NIO有很明顯的效能提升,而MappedByteBuffer則有驚人的高效率。從java.io遷移到nio並使用MappedByteBuffer,通常可以獲得10倍以上的效能提升。
 

需要原始的測試代碼,請訪問:
http://www.ericbruno.com/nio.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.