一、爲原始類提供緩存支持;html
2、字符集編碼解碼解決方案;java
3、Channel :一個新的原始 I/O 抽象;linux
4、支持鎖和內存映射文件的文件訪問接口;api
5、提供多路非阻塞式(non-bloking)的高伸縮性網絡 I/O。數組
在基本I/O操做中全部的操做都是直接以流的形式完成的,而在NIO中全部的操做都要使用到緩衝區處理,且全部的讀寫操做都是經過緩衝區完成的。緩衝區(Buffer)是一個線性的、有序的數據集,只能容納某種特定的數據類型。緩存
一個Buffer有如下幾個屬性:安全
容量(capacity):緩衝區能包含的元素的最大數目。網絡
限制(limit):第一個沒法被寫入或讀取的元素座標。oracle
座標(position):下一個要寫入或讀取的元素的座標。app
更多Buffer的屬性和方法參考http://download.oracle.com/javase/6/docs/api/ 。
另外,
http://www.linuxtopia.org/online_books/programming_books/thinking_in_java/TIJ314_027.htm 上面有一個各類類型的Buffer和byte[]之間相互轉換的模型圖,挺有幫助的。
圖 1 Buffer內部結構
看一下一個簡單的示例,演示緩衝區的操做流程,觀察position、limit、capacity。
Java代碼:
import java.nio.IntBuffer;
public class IntBufferDemo {
public static void main(String[] args) {
// 開闢10個大小的緩衝區
IntBuffer buf = IntBuffer.allocate(10);
System.out.print("一、寫入數據以前的position、limit、capacity:");
System.out.println("position= " + buf.position() + ",limit="
+ buf.limit() + ",capacity=" + buf.position());
// 定義整形數組
int temp[] = { 5, 3, 2 };
// 向緩衝區寫入數據
buf.put(3);
// 向緩衝區寫入一組數據
buf.put(temp);
System.out.print("二、寫入數據以後的position、limit、capacity:");
System.out.println("position= " + buf.position() + ",limit="
+ buf.limit() + ",capacity=" + buf.position());
// 重設緩衝區
buf.flip();
System.out.print("三、重設緩衝區後的position、limit、capacity:");
System.out.println("position= " + buf.position() + ",limit="
+ buf.limit() + ",capacity=" + buf.position());
System.out.print("緩衝區中的內容");
// 只要緩衝區還有內容則輸出
while (buf.hasRemaining()) {
System.out.print(buf.get() + " ");
}
}
}
字節緩衝區要麼是直接的,要麼是非直接的。若是爲直接字節緩衝區,則 Java 虛擬機會盡最大努力直接在此緩衝區上執行本機 I/O 操做。也就是說,在每次調用基礎操做系統的一個本機 I/O 操做以前(或以後),虛擬機都會盡可能避免將緩衝區的內容複製到中間緩衝區中(或從中間緩衝區中複製內容)。
使用allocateDirect方法完成這個操做。此方法返回的緩衝區進行分配和取消分配所需成本一般高於非直接緩衝區。直接緩衝區的內容能夠駐留在常規的垃圾回收堆以外,所以,它們對應用程序的內存需求量形成的影響可能並不明顯。因此,建議將直接緩衝區主要分配給那些易受基礎系統的本機 I/O 操做影響的大型、持久的緩衝區。
Java中的信息是用Unicode進行編碼的,可是現實世界的編碼有不少種,這就有了編碼、解碼的問題。Java使用Charset來表示一個字符集,但願用一種統一的方法來解決編碼解碼問題。下面看一個實際的例子。
Java代碼:
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
public class CharsetDemo {
public static void main(String[] args) throws Exception {
Charset latin1 = Charset.forName("GBK");
CharsetEncoder encoder = latin1.newEncoder();
CharsetDecoder decoder = latin1.newDecoder();
CharBuffer cb = CharBuffer.wrap("我是實習生。");
ByteBuffer buf = null;
buf = encoder.encode(cb);
System.out.println(decoder.decode(buf));
}
}
使用通道(Channel)來讀取和寫入數據,全部的內容都是先讀到或寫入到緩衝區中,再經過緩衝區操做的。通道與傳統的流操做不一樣,傳統的流操做分爲輸入和輸出,而通道支持雙向操做。java.nio.channels.Channel是一個接口,只定義了close()和isOpen()方法。
通道中比較經常使用的就是FileChannel了,注意,FileChannel是一個抽象類,因此經過別的對象產生。下面給出一個FileChannel的示例。
Java代碼:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelDemo {
public static void main(String[] args) throws Exception {
File file1 = new File("in.txt");
File file2 = new File("out.txt");
FileInputStream input = new FileInputStream(file1);
FileOutputStream output = new FileOutputStream(file2);
FileChannel fin = input.getChannel();
FileChannel fout = output.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
while (fin.read(buf) != -1) {
buf.flip();
fout.write(buf);
buf.clear();
}
fin.close();
fout.close();
input.close();
output.close();
}
}
內存映射能夠把文件映射到內存(MappedByteBuffer)中,這樣咱們能夠假設整個文件在內存中,咱們能夠簡單地將文件當成一個Buffer來處理。下面是一個很是簡單的例子。
Java代碼:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class LargeMappedFiles {
// The file created with the preceding program is 128 MB long, which is
// probably larger than the space your OS will allow. The file appears to be
// accessible all at once because only portions of it are brought into
// memory, and other parts are swapped out. This way a very large file (up
// to 2 GB) can easily be modified. Note that the file-mapping facilities of
// the underlying operating system are used to maximize performance.
static int length = 0x8FFFFFF; // 128 Mb
public static void main(String[] args) throws Exception {
// To do both writing and reading, we start with a RandomAccessFile.
// Note that you must specify the starting point and the length of the
// region that you want to map in the file; this means that you have the
// option to map smaller regions of a large file.
MappedByteBuffer out = new RandomAccessFile("test.dat", "rw")
.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
for (int i = 0; i < length; i++)
out.put((byte) 'x');
System.out.println("Finished writing");
for (int i = length / 2; i < length / 2 + 6; i++)
System.out.print((char) out.get(i));
}
}
下面給出一個內存映射和傳統流讀寫速度比較的例子。
Java代碼:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class MappedIO {
private static int numOfInts = 4000000;
private static int numOfUbuffInts = 200000;
private abstract static class Tester {
private String name;
public Tester(String name) { this.name = name; }
public long runTest() {
System.out.print(name + ": ");
try {
long startTime = System.currentTimeMillis();
test();
long endTime = System.currentTimeMillis();
return (endTime - startTime);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public abstract void test() throws IOException;
}
private static Tester[] tests = {
new Tester("Stream Write") {
public void test() throws IOException {
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(new File("temp.tmp"))));
for(int i = 0; i < numOfInts; i++)
dos.writeInt(i);
dos.close();
}
},
new Tester("Mapped Write") {
public void test() throws IOException {
FileChannel fc =
new RandomAccessFile("temp.tmp", "rw")
.getChannel();
IntBuffer ib = fc.map(
FileChannel.MapMode.READ_WRITE, 0, fc.size())
.asIntBuffer();
for(int i = 0; i < numOfInts; i++)
ib.put(i);
fc.close();
}
},
new Tester("Stream Read") {
public void test() throws IOException {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("temp.tmp")));
for(int i = 0; i < numOfInts; i++)
dis.readInt();
dis.close();
}
},
new Tester("Mapped Read") {
public void test() throws IOException {
FileChannel fc = new FileInputStream(
new File("temp.tmp")).getChannel();
IntBuffer ib = fc.map(
FileChannel.MapMode.READ_ONLY, 0, fc.size())
.asIntBuffer();
while(ib.hasRemaining())
ib.get();
fc.close();
}
},
new Tester("Stream Read/Write") {
public void test() throws IOException {
RandomAccessFile raf = new RandomAccessFile(
new File("temp.tmp"), "rw");
raf.writeInt(1);
for(int i = 0; i < numOfUbuffInts; i++) {
raf.seek(raf.length() - 4);
raf.writeInt(raf.readInt());
}
raf.close();
}
},
new Tester("Mapped Read/Write") {
public void test() throws IOException {
FileChannel fc = new RandomAccessFile(
new File("temp.tmp"), "rw").getChannel();
IntBuffer ib = fc.map(
FileChannel.MapMode.READ_WRITE, 0, fc.size())
.asIntBuffer();
ib.put(0);
for(int i = 1; i < numOfUbuffInts; i++)
ib.put(ib.get(i - 1));
fc.close();
}
}
};
public static void main(String[] args) {
for(int i = 0; i < tests.length; i++)
System.out.println(tests[i].runTest());
}
}
要注意的是,MappedByteBuffer中的內容隨時都有可能發生變化,例如當本程序或其它程序改變了相應的文件時,當這種變化發生時,產生的後果是操做系統相關的,所以很難說明。而且MappedByteBuffer的所有或者一部分可能在任意時刻變成不可達的,好比文件被刪除了一部分。當訪問一個不可達的區域時,有可能會在當時或以後會拋出未知錯誤。因此,安全地使用MappedByteBuffer,能夠使用下面介紹的FileLock來將映射文件的一件部分鎖住, 這樣別的線程就沒法修改這部分文件了。
若是一個線程操做一個文件時不但願其餘線程進行訪問,則能夠經過FileLock鎖定一個文件。此類的對象須要依靠FileChannel進行實例化操做。鎖定文件有兩種方式:共享鎖和獨佔鎖。共享鎖容許多個線程進行文件的讀取操做,獨佔鎖只容許一個線程進行文件的讀寫操做。下面的是FileLock的示例用法。
Java代碼:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
public class FileLockDemo {
public static void main(String[] args) throws Exception {
// Write something to a file for test,make sure e://in.txt is not
// exist,or change the filename to another.
// To simplify the demo,I don't deal the possible error.
File file = new File("in.txt");
FileOutputStream output = new FileOutputStream(file, true);
FileChannel fout = output.getChannel();
ByteBuffer srcs = ByteBuffer.allocate(1024);
srcs.put("Something.".getBytes());
srcs.flip();
fout.write(srcs);
srcs.clear();
// Now try to lock the file.
FileLock lock = fout.tryLock();
if (lock != null) {
System.out.println("File " + file.getName() + " is locked.");
// New a thread to access the file.
new Thread() {
@Override
public void run() {
File file = new File("in.txt");
try {
FileInputStream fis = new FileInputStream(file);
// This may throw a IOException.
fis.read();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
Thread.sleep(10000);
// Release the file's lock.
lock.release();
System.out.println("File " + file.getName()
+ "'s lock is released.");
}
fout.close();
output.close();
}
}
咱們能夠使用FileLock來更安全地使用文件內存映射。下面的例子使用了兩個線程,每一個線程鎖定了文件的一部分。
Java代碼:
// Locking portions of a mapped file.
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class LockingMappedFiles {
static final int LENGTH = 0x8FFFFFF; // 128 Mb
static FileChannel fc;
public static void main(String[] args) throws Exception {
fc = new RandomAccessFile("test.dat", "rw").getChannel();
MappedByteBuffer out = fc
.map(FileChannel.MapMode.READ_WRITE, 0, LENGTH);
for (int i = 0; i < LENGTH; i++)
out.put((byte) 'x');
new LockAndModify(out, 0, 0 + LENGTH / 3);
new LockAndModify(out, LENGTH / 2, LENGTH / 2 + LENGTH / 4);
}
private static class LockAndModify extends Thread {
private ByteBuffer buff;
private int start, end;
LockAndModify(ByteBuffer mbb, int start, int end) {
this.start = start;
this.end = end;
mbb.limit(end);
mbb.position(start);
buff = mbb.slice();
start();
}
public void run() {
try {
// Exclusive lock with no overlap:
FileLock fl = fc.lock(start, end, false);
System.out.println("Locked: " + start + " to " + end);
// Perform modification:
while (buff.position() < buff.limit() - 1)
buff.put((byte) (buff.get() + 1));
fl.release();
System.out.println("Released: " + start + " to " + end);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}