一直有著對(duì)nio的高效莫名的喜歡鹤树,今天我就來說說nio的那些事兒赴背。
非阻塞:阻塞一直以來都是計(jì)算機(jī)解決復(fù)雜問題比較簡(jiǎn)單的思路椰拒,同樣它也是低效的代名詞,當(dāng)程序被阻塞凰荚,似乎他已經(jīng)停下來并且執(zhí)著于當(dāng)前的事情燃观,而對(duì)于新的任務(wù),它需要一直等著當(dāng)前程序結(jié)束阻塞便瑟,很多時(shí)候這種行為是沒有意義的缆毁,但又不得不那樣做。
對(duì)于IO操作更是如此到涂,所以Java1.4出來了新的IO脊框。
概念介紹
- Channel
可以翻譯為通道,和IO中的流對(duì)應(yīng)践啄,不同的是浇雹,Channel是雙向的,而Stream只能從一端到另一端(單向) - Buffer
緩沖區(qū)屿讽,用來存放數(shù)據(jù)的區(qū)域昭灵,它有四個(gè)索引用來進(jìn)行高效的操作- capacity:buffer的容量
- limit:索引能到達(dá)的地方,通常情況和容量的大小一致
- mark:標(biāo)記伐谈,像書簽一樣烂完,保存你剛剛索引,下次很容易技能找到
- position:位置衩婚,當(dāng)前正在操作數(shù)據(jù)的位置
- Selector(暫不涉及)
單線程下可以管理多個(gè)Channel的分發(fā)器
代碼實(shí)戰(zhàn)
以下借文件拷貝這一功能說明nio操作流程
@SuppressWarnings("resource")
public static void copy(File src, File dest) {
if(src == null || dest == null)
throw new NullPointerException("源文件或目標(biāo)文件為空窜护!");
if(src.isDirectory() || dest.isDirectory())
throw new IllegalArgumentException("我不能對(duì)目錄進(jìn)行復(fù)制");
if(!src.exists())
throw new RuntimeException("源文件不存在");
if(src.length() == 0)
throw new RuntimeException("文件內(nèi)容為空效斑!");
if( !dest.exists())
try {
dest.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
FileChannel srcChannel = null;
FileChannel destChannel = null;
try {
/*
* 創(chuàng)建FileChannel需要區(qū)分FileOutputStream和FileInputStream,
* 如果是用FileOutputStream創(chuàng)建的Channel只能進(jìn)行寫操作而不能驚醒讀操作非春,
* 否則會(huì)報(bào)NonReadableChannelException,反之用FileInputStream
* 創(chuàng)建的Channel只能進(jìn)行讀操作,而不能進(jìn)行寫操作,否則會(huì)報(bào)NonWritableChannelException
* 當(dāng)然除此之外奇昙,可以使用RandomAccessFile來替換护侮,但必須指定操作方式,就像
* mode = "r" / "rw" 但不可以是 "w"
* RandomAccessFile r = new RandomAccessFile(file, mode)
*/
srcChannel = new FileInputStream(src).getChannel();
destChannel = new FileOutputStream(dest).getChannel();
/*
* 創(chuàng)建創(chuàng)建Buffer储耐,這里有兩個(gè)方法allocate(int) 和 allocateDirect(int)
* 這兩個(gè)方法表現(xiàn)在申請(qǐng)內(nèi)存的方式不一樣羊初,allocateDirect是直接向操作系統(tǒng)申請(qǐng)內(nèi)存,
* 而allocate()是在Jvm堆中申請(qǐng)內(nèi)存什湘,當(dāng)Java從外界獲取到數(shù)據(jù)時(shí)长赞,首先經(jīng)過系統(tǒng)內(nèi)存,
* 然后再將這部分內(nèi)存復(fù)制到Jvm內(nèi)存中闽撤,所以使用allocateDirect()會(huì)省去一步操作得哆,
* 但是想要從操作系統(tǒng)獲取內(nèi)存是很復(fù)雜且耗時(shí)的,所以allocateDirect()不一定就快,
* 通常分配的內(nèi)存小于1M就用allocate哟旗,更大的話就用allocateDirect比較快贩据。
* ByteBuffer:
* 這是最基本的byte的buffer,當(dāng)然還有其他基本類型的buffer闸餐,當(dāng)然除了Boolean
* 不過你需要注意的是除ByteBuffer其他的buffer實(shí)際上只是ByteBuffer的不同表現(xiàn)形式饱亮,
* 他們還是依據(jù)ByteBuffer作為真正的數(shù)據(jù)緩沖區(qū),你可以使用byteBuffer的asCharBuffer
* 等等其它類似的方法可以將ByteBuffer轉(zhuǎn)化為其它的buffer
*/
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//read方法的意思是將channel中的數(shù)據(jù)放到buffer中舍沙,如果返回結(jié)果為-1近上,表示channel中的數(shù)據(jù)已被讀完
while(srcChannel.read(byteBuffer) != -1) {
/*
* 翻轉(zhuǎn)操作 將limit索引設(shè)置為 position, 設(shè)置position為0拂铡,清空mark
* 其實(shí)過程是這樣的戈锻,buffer的get操作和put操作都會(huì)使得position加1
* 在channel的read操作實(shí)際上使用buffer的put操作往里面塞東西,不管有沒有塞滿
* 都會(huì)設(shè)置limit索引和媳,設(shè)置了limit很有必要格遭,在通過buffer.get()操作或者channel.write(buffer)
* 操作的時(shí)候會(huì)將position與limit進(jìn)行比較,如果position大于limit就會(huì)拋出BufferUnderflowException
* 同樣如果不設(shè)置position為0留瞳,現(xiàn)在的position就等于limit拒迅,那么你通過get()將會(huì)拋出BufferUnderflowException
* 而如果是channel.write()就不會(huì)有任何數(shù)據(jù),你可以用以下的替換flip()操作的代碼進(jìn)行測(cè)試,
* 屆時(shí)你講明白position置于0是多么重要她倘。
*/
//byteBuffer.limit(byteBuffer.position()); //設(shè)置limit為position
byteBuffer.flip();
//和讀操作相反璧微,它是將byteBuffer中的內(nèi)容寫入到channel中
destChannel.write(byteBuffer);
//清空操作(實(shí)際上它并沒有清空數(shù)據(jù),而僅僅是把那四個(gè)索引設(shè)為初始值)硬梁,因?yàn)橄乱淮蝐hannel.read()或者byteBuffer.put()操作
//將會(huì)重新覆蓋上一次的數(shù)據(jù)
byteBuffer.clear();
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if(srcChannel != null)
srcChannel.close();
if(destChannel != null)
destChannel.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
以上就是nio的魅力前硫,可能有人會(huì)覺得有點(diǎn)麻煩比如說flip()和clear()操作,他們其實(shí)只進(jìn)行了索引之間賦值操作荧止,只有通過索引進(jìn)行操作屹电,效率才會(huì)如此高阶剑。所以不得不使用他們,總之需要記住這些索引的功能危号,才能更好的使用它牧愁。
文件映射到內(nèi)存
出于性能考慮,在修改和創(chuàng)建大型文件的時(shí)候外莲,內(nèi)存往往承載不了太多的文件資源猪半,但是用文件映射后,可以假定整個(gè)文件都在內(nèi)存中偷线,這時(shí)候磨确,操作文件的性能可想而知。
@SuppressWarnings("resource")
private static void appendNameToFile(File file, String name) {
if(file == null)
throw new NullPointerException();
if(!file.exists())
throw new RuntimeException("文件不存在");
try {
FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
try{
/*
* 通過channel獲取到MappedByteBuffer声邦,它是ByteBuffer的子類俐填,并且內(nèi)存是通過操作系統(tǒng)獲取的
* 在被gc回收之前一直有限,但你并不知道什么時(shí)候gc會(huì)進(jìn)行回收操作
* map方法有三個(gè)參數(shù)翔忽,第一個(gè)是指定讀寫模式英融,第二個(gè)參數(shù)指定從文件中的起始位置開始映射,第三個(gè)參數(shù)是從第二個(gè)
* 參數(shù)的位置起需要映射的長(zhǎng)度為多少
* 需要注意的是以下操作會(huì)在文件末尾 添加一個(gè)空格歇式,如果不想添加空格那么第三個(gè)參數(shù)需要設(shè)置為file.length() - 1
* 后面的1代表第二個(gè)參數(shù)的值
*/
//MappedByteBuffer buffer =channel.map(MapMode.READ_WRITE, 1, file.length());
MappedByteBuffer buffer =channel.map(MapMode.READ_WRITE, file.length(), name.length());
buffer.put(name.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
channel.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
文件鎖
Java1.4提供了給文件加鎖的功能(同步共享的文件)驶悟,用于防止不同線程的同時(shí)訪問或修改同一個(gè)文件,這包括Jvm線程和操作系統(tǒng)的本地線程材失,而且Java提供的文件鎖對(duì)象操作系統(tǒng)是可見的痕鳍,因?yàn)樗苯佑成涞搅瞬僮飨到y(tǒng)的本地鎖。
public static void addFileLock(File file) {
if(file == null)
throw new NullPointerException();
if(!file.exists())
throw new RuntimeException("文件不存在");
try {
@SuppressWarnings("resource")
/*
* 你不能使用FileInputStream龙巨,因?yàn)樗鼊?shì)必會(huì)往channel里面寫點(diǎn)東西笼呆,很自然的FileInputStream不會(huì)讓你寫,而是拋異常
* 當(dāng)然除了tryLock還有l(wèi)ock他們的區(qū)別僅僅是非阻塞與阻塞的問題旨别,tryLock如果不能獲取鎖的話诗赌,方法直接會(huì)返回,而lock只會(huì)死等
* 其實(shí)如果你再細(xì)心點(diǎn)秸弛,他們分別還重載了一個(gè)三參數(shù)的方法铭若,類似 tryLock(long position, long size, boolean shared)
* 通過這個(gè)方法聲明可以看出來他是一個(gè)鎖文件局部的方法,就像鎖數(shù)據(jù)庫(kù)和單獨(dú)鎖表的區(qū)別一樣递览,你可以嘗試一下它叼屠。
*/
FileLock fileLock = new FileOutputStream(file).getChannel().tryLock();
if(fileLock != null) {
System.out.println("文件已被鎖住");
try {
TimeUnit.SECONDS.sleep(20);
} catch (InterruptedException e) {
}
fileLock.release();
System.out.println("文件已被解鎖");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}