Java NIO教程 MappedByteBuffer


之前跟大家說過,要講MappedByteBuffer,現在我來履行承諾了。

首先從大體上講一下MappedByteBuffer究竟是什么。從繼承結構上來講,MappedByteBuffer繼承自ByteBuffer,所以ByteBuffer有的能力它全有;像變動position和limit指針啦、包裝一個其他種類Buffer的視圖啦,都可以。“MappedByteBuffer”為何而來?吾輩心中亦有惑(熊貓人之謎的梗)用一個字來概括就是

為什么?因為它使用direct buffer的方式讀寫文件內容,這種方式的學名叫做內存映射。這種方式直接調用系統底層的緩存,沒有JVM和系統之間的復制操作,所以效率大大的提高了。而且由於它這么快,還可以用它來在進程(或線程)間傳遞消息,基本上能達到和“共享內存頁”相同的作用,只不過它是依托實體文件來運行的。

而且它還有另一種能力。就是它可以讓我們讀寫那些因為太大而不能放進內存中的文件。有了它,我們就可以假定整個文件都放在內存中(實際上,大文件放在內存和虛擬內存中),基本上都可以將它當作一個特別大的數組來訪問,這樣極大的簡化了對於大文件的修改等操作。

下面我們開始介紹它的用法了

FileChannel提供了map方法來把文件映射為MappedByteBuffer: MappedByteBuffer map(int mode,long position,long size); 可以把文件的從position開始的size大小的區域映射為MappedByteBuffer,mode指出了可訪問該內存映像文件的方式,共有三種,分別為:
MapMode.READ_ONLY(只讀): 試圖修改得到的緩沖區將導致拋出 ReadOnlyBufferException。
MapMode.READ_WRITE(讀/寫): 對得到的緩沖區的更改最終將寫入文件;但該更改對映射到同一文件的其他程序不一定是可見的(無處不在的“一致性問題”又出現了)。
MapMode.PRIVATE(專用): 可讀可寫,但是修改的內容不會寫入文件,只是buffer自身的改變,這種能力稱之為”copy on write”

再簡單的說一下,MappedByteBuffer較之ByteBuffer新增的三個方法

  • fore()緩沖區是READ_WRITE模式下,此方法對緩沖區內容的修改強行寫入文件
  • load()將緩沖區的內容載入內存,並返回該緩沖區的引用
  • isLoaded()如果緩沖區的內容在物理內存中,則返回真,否則返回假

下面代碼終於出場了

int length = 0x8FFFFFF;//一個byte占1B,所以共向文件中存128M的數據
try (FileChannel channel = FileChannel.open(Paths.get("src/c.txt"),
		StandardOpenOption.READ, StandardOpenOption.WRITE);) {
	MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
	for(int i=0;i<length;i++) {
		mapBuffer.put((byte)0);
	}
	for(int i = length/2;i<length/2+4;i++) {
		//像數組一樣訪問
		System.out.println(mapBuffer.get(i));
	}
}

上面是MappedByteBuffer最基本的應用,而下面這段代碼主要是測試它到底有多快,

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class TestMappedByteBuffer {
	private static int length = 0x2FFFFFFF;//1G
	private abstract static class Tester {
		private String name;
		public Tester(String name) {
			this.name = name;
		}
		public void runTest() {
			System.out.print(name + ": ");
			long start = System.currentTimeMillis();
			test();
			System.out.println(System.currentTimeMillis()-start+" ms");
		}
		public abstract void test();
	}
	private static Tester[] testers = {
		new Tester("Stream RW") {
			public void test() {
				try (FileInputStream fis = new FileInputStream(
						"src/a.txt");
						DataInputStream dis = new DataInputStream(fis);
						FileOutputStream fos = new FileOutputStream(
								"src/a.txt");
						DataOutputStream dos = new DataOutputStream(fos);) {
					
					byte b = (byte)0;
					for(int i=0;i<length;i++) {
						dos.writeByte(b);
						dos.flush();
					}					
					while (dis.read()!= -1) {
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		},
		new Tester("Mapped RW") {
			public void test() {
				try (FileChannel channel = FileChannel.open(Paths.get("src/b.txt"),
						StandardOpenOption.READ, StandardOpenOption.WRITE);) {
					MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, length);
					for(int i=0;i<length;i++) {
						mapBuffer.put((byte)0);
					}
					mapBuffer.flip();
					while(mapBuffer.hasRemaining()) {
						mapBuffer.get();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		},
		new Tester("Mapped PRIVATE") {
			public void test() {
				try (FileChannel channel = FileChannel.open(Paths.get("src/c.txt"),
						StandardOpenOption.READ, StandardOpenOption.WRITE);) {
					MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.PRIVATE, 0, length);
					for(int i=0;i<length;i++) {
						mapBuffer.put((byte)0);
					}
					mapBuffer.flip();
					while(mapBuffer.hasRemaining()) {
						mapBuffer.get();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	};
	public static void main(String[] args) {
		for(Tester tester:testers) {
			tester.runTest();
		}
	}
}

先從整體上提一句上面的代碼,runTest()是一個模板方法,並且引用了一個未實現的test()方法;通過匿名內部類的實現,填充了測試內容。

再來說上面代碼的測試結果。用傳統流的方式,當然是最慢的,但應該是由於用的數據量是1G,無法全部讀入內存,所以它根本無法完成測試。剩下兩種MapMode.READ_WRITEMapMode.PRIVATE各有特點,首先說MapMode.READ_WRITE,它的速度每次差別較大,在0.6s和8s之間波動,而且很不穩定。但MapMode.PRIVATE就穩得出奇,一直是1.1s到1.2s之間。但無論是哪個速度都是十分驚人的。但是MappedByteBuffer也有不足,就是在數據量很小的時候,表現比較糟糕,那是因為direct buffer的初始化時間較長,所以建議大家只有在數據量較大的時候,在用MappedByteBuffer。

還要強調的一點是,MappedByteBuffer存在內存占用和文件關閉等不確定問題。被MappedByteBuffer打開的文件只有在垃圾收集時才會被關閉,而這個點是不確定的。javadoc里是這么說的:

A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected. ——JavaDoc

關於MappedByteBuffer就告訴你這么多了,有什么問題盡管提、有什么想法隨時找我交流。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM