Java NIO 是從Java 1.4版本開(kāi)始引入的一個(gè)新的 IO API,可以替代標(biāo)準(zhǔn)的 Java IO API。NIO與原來(lái)的 IO 有同樣的作用和目的,但是使用的方式完全不同,NIO 支持面向 緩沖區(qū) 的,基于 通道 的IO 操作,至于什么是緩沖區(qū),什么是通道,接下來(lái)我將會(huì)用大白話一一說(shuō)明??傊?,NIO 就是以更高效的方式進(jìn)行文件的讀寫(xiě)操作。
在學(xué)習(xí)本篇之前,首先你要對(duì) IO 有一定的了解。當(dāng)然不了解的話,也可以看得哈哈,我會(huì)說(shuō)的很通俗易懂。
我們先看看 Java NIO 與 IO的主要區(qū)別:
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向緩沖區(qū)(Buffer Oriented) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
無(wú) | 選擇器(Selectors) |
上面的什么 面向緩沖區(qū),又什么非阻塞IO,又是選擇器的,這些到底都啥啊,拍桌子。。。
下面我會(huì)在本篇中對(duì)上面出現(xiàn)的概念及盲點(diǎn)進(jìn)行解析。
首先我們看看他們的區(qū)別
為什么說(shuō)IO是面向流,那流又是什么呢?
我們先看上面的圖片。
我們?cè)趯W(xué)IO的時(shí)候,肯定都聽(tīng)過(guò)這樣的例子,IO流就相當(dāng)于一條管道,它里面所有的操作都是單向的。如果要把文件中的數(shù)據(jù)拿到程序中,需要建立一條通道。想把程序中的數(shù)據(jù)存到文件中也需要建立一條通道。所以我們成io流是單向的。因?yàn)閕o管道里實(shí)際面對(duì)的是字節(jié)的流動(dòng),所以我們稱io流為面向流。
那什么說(shuō)NIO是面向緩沖區(qū)呢?
你可以這樣想象,通道就相當(dāng)于與一條道路,緩沖區(qū)相當(dāng)于出租車,出租車上拉的是乘客,出租車可以上乘客也可以下乘客。回到NIO 上面,通道就是一條道路,他負(fù)責(zé)提供行駛的絕對(duì)條件,即就是有路啊,這樣出租車才能基本出行,而緩沖區(qū)在這里是出租車,出租車?yán)锩孀氖侨?,出租車?fù)責(zé)將乘客送到它要去的地方,當(dāng)然,出租車不受限制,他可以在任意地方。所以簡(jiǎn)而言之,通道(Channel)負(fù)責(zé)傳輸,Buffer 負(fù)責(zé)存儲(chǔ)。
在 NIO 里面,有兩個(gè)特別重要的東西,那就是 通道(Channel) 與 緩沖區(qū)(Buffer)
Java NIO系統(tǒng)的核心在于:通道 和緩沖區(qū)。通道表示 打開(kāi)IO 設(shè)備(例如:文件,套接字)的鏈接。若需要使用 NIO 系統(tǒng),需要獲取用于鏈接 IO 的設(shè)備的通道以及用于容納數(shù)據(jù)的緩沖區(qū)。然后操作緩沖區(qū),對(duì)數(shù)據(jù)進(jìn)行處理。
/*緩沖區(qū)(Buffer):在Java Nio中負(fù)責(zé)數(shù)據(jù)的存取。緩沖區(qū)就是數(shù)組。用于存儲(chǔ)不同數(shù)據(jù)類型的數(shù)據(jù)** 根據(jù)數(shù)據(jù)類型不同(boolean 除外),提供了相應(yīng)類型的緩沖區(qū)* ByteBuffer* CharBuffer* ...** 上述緩沖區(qū)的管理方式幾乎一致,都是通過(guò)allocate() 獲取緩沖區(qū)** 2/緩沖區(qū)存取數(shù)據(jù)的兩個(gè)核心方法:* put(): 存入數(shù)據(jù)到緩沖區(qū)中* get():獲取緩沖區(qū)中的數(shù)據(jù)** 4.緩沖區(qū)中的4個(gè)核心屬性:* capacity: 容量,表示緩沖區(qū)中最大存儲(chǔ)數(shù)據(jù)的容量。一旦聲明不能改變,(底層就是數(shù)組)* limit:界限,表示緩沖區(qū)中可以操作數(shù)據(jù)的大小。(limit 后面的數(shù)據(jù)不能進(jìn)行讀寫(xiě))* position:位置,表示緩沖區(qū)中正在操作數(shù)據(jù)的位置。** 5.直接緩沖區(qū)與非直接緩沖區(qū)* 非直接緩沖區(qū):通過(guò) allocate()方法分配緩沖區(qū),將緩沖區(qū)建立在 JVM的內(nèi)存中* 直接緩沖區(qū):通過(guò) allocateDirect() 方法分配直接緩沖區(qū),將緩沖區(qū)建立物理內(nèi)存中。** mark: 標(biāo)記,表示記錄當(dāng)前 postion 的位置,可以通過(guò) reset() 恢復(fù)到mark 位置* position<=limit<=capacity* */public class Test { public static void main(String[] args) throws IOException { test2(); test3(); } private static void test1() { String str="Petterp"; //1.分配一個(gè)指定大小的緩沖區(qū) ByteBuffer buf = ByteBuffer.allocate(1024); System.out.println("____________allocate_________"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //2.利用 put() 存入數(shù)據(jù)到緩沖區(qū)中 buf.put(str.getBytes()); System.out.println("____________put_________"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //3.切換讀取數(shù)據(jù)模式 buf.flip(); System.out.println("____________flip_________"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //4.利用get() 讀取緩沖區(qū)的數(shù)據(jù) byte[] dst=new byte[buf.limit()]; buf.get(dst); System.out.println(new String(dst,0,dst.length)); System.out.println("____________get_________"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //5.rewind()_可重復(fù)讀數(shù)據(jù) buf.rewind(); System.out.println("____________rewind_________"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); //6.clear(): 清空緩沖區(qū).(緩沖區(qū)數(shù)據(jù)還在,但是處于"被遺忘"狀態(tài) // 因?yàn)閘imit這些值全回到了初始狀態(tài),所以無(wú)法正確讀取數(shù)據(jù)。) buf.clear(); System.out.println("____________clear_________"); System.out.println(buf.position()); System.out.println(buf.limit()); System.out.println(buf.capacity()); } private static void test2(){ ByteBuffer buf =ByteBuffer.allocate(1024); String res="Petterp"; buf.put(res.getBytes()); buf.flip(); //記錄指針位置為0 buf.mark(); System.out.println("____________mark記錄position位置_________"); System.out.println(buf.position()); byte[] bytes = new byte[buf.limit()]; buf.get(bytes,0,3); System.out.println("打印get到的數(shù)據(jù)" new String(bytes,0,bytes.length)); System.out.println("____________get之后position_________"); System.out.println(buf.position()); //會(huì)到記錄的指針位置 buf.reset(); System.out.println("____________reset之后position_________"); System.out.println(buf.position()); System.out.println("____________remaining判斷可操作數(shù)據(jù)長(zhǎng)度_________"); //判斷緩沖區(qū)是否還有剩余數(shù)據(jù) if (buf.hasRemaining()){ //獲取緩沖區(qū)中可以操作的數(shù)據(jù)長(zhǎng)度 System.out.println(buf.remaining()); } } private static void test3(){ ByteBuffer buf=ByteBuffer.allocateDirect(1024); //判斷是否是直接緩存區(qū) System.out.println(buf.isDirect()); }}
非直接緩沖區(qū)在,建立在JVM內(nèi)存中,實(shí)際讀寫(xiě)數(shù)據(jù)時(shí),需要在 OS 和JVM之間進(jìn)行數(shù)據(jù)拷貝。
為什么不直接讓磁盤(pán)控制器把數(shù)據(jù)送到用戶控件的緩沖區(qū)呢?
因?yàn)槲覀兊挠布ǔ2荒苤苯釉L問(wèn)用戶內(nèi)存空間。如果有一個(gè)程序需要讀寫(xiě)磁盤(pán)空間,出于系統(tǒng)安全考慮,磁盤(pán)中的文件無(wú)法直接傳輸?shù)轿覀兂绦蛑校仨毥?jīng)過(guò)系統(tǒng)的內(nèi)核地址空間的緩存中,然后將內(nèi)核地址空間數(shù)據(jù)復(fù)制到用戶地址空間,這樣數(shù)據(jù)才可以傳輸?shù)轿覀兊膽?yīng)用程序。
內(nèi)存映射空間
直接緩沖區(qū),緩沖區(qū)建立在受操作系統(tǒng)管理的物理內(nèi)存中,OS和JVM直接通過(guò)這塊物理內(nèi)存進(jìn)行交互,沒(méi)有了中間的拷貝環(huán)節(jié)
但是直接緩沖區(qū)也有很多弊端:
內(nèi)存消耗大(分配與銷毀不易控制)
如果當(dāng)Java 程序?qū)?shù)據(jù)寫(xiě)到物理內(nèi)存中后,這個(gè)時(shí)候我們就無(wú)法管理這塊內(nèi)存,只能由系統(tǒng)進(jìn)行控制。
//1.利用通道完成文件的復(fù)制(非直接緩沖區(qū))public static void test1(){ try{ long l = System.currentTimeMillis(); fis = new FileInputStream("D:1.zip"); fos = new FileOutputStream("D:2.zip"); //1.獲取通道 inChannel = fis.getChannel(); outChanel1 = fos.getChannel(); //2.分配指定大小的緩沖區(qū) ByteBuffer buf = ByteBuffer.allocate(1024); //3.將通道中的數(shù)據(jù)存入緩沖中 while (inChannel.read(buf)!=-1){ buf.flip();//切換讀取數(shù)據(jù)模式 //將緩沖區(qū)中的數(shù)據(jù)寫(xiě)入通道中 outChanel1.write(buf); buf.clear(); //清空緩沖區(qū) } long l2 = System.currentTimeMillis(); System.out.println("時(shí)間" (l2-l)); }catch (IOException e){ e.printStackTrace(); }finally { if (outChanel1 != null) { try { outChanel1.close(); } catch (IOException e) { e.printStackTrace(); } } if (inChannel != null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } }}//使用直接緩沖區(qū)完成文件的復(fù)制(內(nèi)存映射文件)@RequiresApi(api = Build.VERSION_CODES.O)public static void test2(){ try { long l = System.currentTimeMillis(); //第一個(gè)參數(shù)是路徑,第二個(gè)參數(shù)是模式 FileChannel inchannel=FileChannel.open(Paths.get("D:demo.txt"),StandardOpenOption.READ); FileChannel outChannel=FileChannel.open(Paths.get("D:demo2.txt"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE); //內(nèi)存映射文件 MappedByteBuffer inMapBuf = inchannel.map(FileChannel.MapMode.READ_ONLY, 0, inchannel.size()); MappedByteBuffer outMapBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inchannel.size()); //直接對(duì)緩沖區(qū)進(jìn)行數(shù)據(jù)的讀寫(xiě)操作 byte[] bytes = new byte[inMapBuf.limit()]; inMapBuf.get(bytes); outMapBuf.put(bytes); inchannel.close(); outChannel.close(); long l2 = System.currentTimeMillis(); System.out.println("時(shí)間" (l2-l)); } catch (IOException e) { e.printStackTrace(); }}
通道(Channel) 由java.nio.channels 包定義的。Channel 表示 IO 源于目標(biāo)打開(kāi)的鏈接。Channel 類似于傳統(tǒng)的流,只不過(guò) Channel 本身不能直接訪問(wèn)數(shù)據(jù),Channel 只能與 Buffer進(jìn)行交互。
通道的主要實(shí)現(xiàn)類:* Java.nio/channels.Channel 接口* FileChannel 本地文件傳輸* SocketChannel 網(wǎng)絡(luò)傳輸* ServerSocketChannel* DatagramChannel
獲取通道* -1. Java 鎮(zhèn)對(duì)支持通道的類提供了 getChannel() 方法* 本地IO* FiledInputStream/FileOutputStream* RandomAccessFile** 網(wǎng)絡(luò)IO* Socket* ServerSocket* DatagramSocket* -2. 在 JDK 1.7中的 NIO.2 針對(duì)各個(gè)通道提供了靜態(tài)方法 open()** -3. 在 jdk 1.7中的 NIO.2 的 Files 工具類 newByteChannel()
//1.利用通道完成文件的復(fù)制(非直接緩沖區(qū))public static void test1(){ try{ long l = System.currentTimeMillis(); fis = new FileInputStream("D:1.zip"); fos = new FileOutputStream("D:2.zip"); //1.獲取通道 inChannel = fis.getChannel(); outChanel1 = fos.getChannel(); //2.分配指定大小的緩沖區(qū) ByteBuffer buf = ByteBuffer.allocate(1024); //3.將通道中的數(shù)據(jù)存入緩沖中 while (inChannel.read(buf)!=-1){ buf.flip();//切換讀取數(shù)據(jù)模式 //將緩沖區(qū)中的數(shù)據(jù)寫(xiě)入通道中 outChanel1.write(buf); buf.clear(); //清空緩沖區(qū) } long l2 = System.currentTimeMillis(); System.out.println("時(shí)間" (l2-l)); }catch (IOException e){ e.printStackTrace(); }finally { if (outChanel1 != null) { try { outChanel1.close(); } catch (IOException e) { e.printStackTrace(); } } if (inChannel != null) { try { inChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } }}
transferFrom()
transferTo()
public static void test3(){ try { FileChannel inchannel=FileChannel.open(Paths.get("D:demo.txt"),StandardOpenOption.READ); FileChannel outChannel=FileChannel.open(Paths.get("D:Demop.txt"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE); if (outChannel != null) {// inchannel.transferTo(0,inchannel.size(),outChannel); outChannel.transferFrom(inchannel,0,inchannel.size()); inchannel.close(); outChannel.close(); } } catch (IOException e) { e.printStackTrace(); } }
[外鏈圖片轉(zhuǎn)存失敗(img-FzYapv7x-1567945086087)(C:\Users\Pettepr\AppData\Roaming\Typora\typora-user-images\1554968002880.png)]
分散讀取(Scattering Reads):將通道中的數(shù)據(jù)分散到多個(gè)緩沖區(qū)中
聚集寫(xiě)入(Gathering Writes):將多個(gè)緩沖區(qū)的數(shù)據(jù)聚集到通道中
//分散和聚集
public static void test4() throws IOException {
RandomAccessFile rafi=new RandomAccessFile(“D:demo1.txt”,“rw”);
//1.獲取通道 FileChannel channel=rafi.getChannel(); //2.分配指定大小的緩沖區(qū) ByteBuffer buf1 =ByteBuffer.allocate(100); ByteBuffer buf2 = ByteBuffer.allocate(1024); //3.分散讀取 ByteBuffer[] bufs={buf1,buf2}; channel.read(bufs); for (ByteBuffer byteBuffer:bufs){ byteBuffer.flip(); } System.out.println(new String(bufs[0].array(),0,bufs[0].limit())); System.out.println("----------"); System.out.println(new String(bufs[1].array(),0,bufs[1].limit())); //4.聚集寫(xiě)入 RandomAccessFile raf2=new RandomAccessFile("D:demo2.txt","rw"); FileChannel channel2=raf2.getChannel(); channel2.write(bufs);
}
計(jì)算機(jī)里的文件,數(shù)據(jù),圖片文件只是一種表面現(xiàn)象,所有文件在底層都是二進(jìn)制文件,即全部都是字節(jié)碼。
對(duì)于文本文件而言,之所以可以看到一個(gè)個(gè)的字符,這完全是因?yàn)橄到y(tǒng)將底層的二進(jìn)制序列轉(zhuǎn)換成字符的緣故。在這個(gè)過(guò)程中涉及兩個(gè)概念:編碼(Encode) 和解碼 (Decode),通常而言,把明文的字符序列轉(zhuǎn)換成計(jì)算機(jī)理解的二進(jìn)制序列稱為編碼,把二進(jìn)制序列轉(zhuǎn)換成普通人能看懂的明文字符串稱為解碼。
Java 默認(rèn)視同 Uniocde 字符集,但很多操作系統(tǒng)并不適用Unicode 字符集,那么當(dāng)從系統(tǒng)中讀取數(shù)據(jù)到 Java程序中時(shí),就可能出現(xiàn)亂碼等問(wèn)題。
JDK1.4 提供了 Charset來(lái)處理字節(jié)序列和字符序列(字符串)之間的轉(zhuǎn)換關(guān)系,該類包含了用于創(chuàng)建解碼器和編碼器的方法。還提供了獲取 Charset所支持字符集的方法,Charset類是不可變的。
編碼:字符串 -> 字節(jié)數(shù)組
解碼:字節(jié)數(shù)組 -> 字符串
public static void test6() throws CharacterCodingException {
//字符集
Charset cs1 = Charset.forName(“GBK”);
//查看Java支持的字符集格式 private static void test5(){ SortedMap<String, Charset> map = Charset.availableCharsets(); Set<Map.Entry<String, Charset>> set = map.entrySet(); for (Map.Entry<String,Charset> entry: set){ System.out.println(entry.getKey() "=" entry.getValue()); } } //獲取編碼器 CharsetEncoder ce = cs1.newEncoder(); //獲取解碼器 CharsetDecoder cd=cs1.newDecoder(); CharBuffer cBuf = CharBuffer.allocate(1024); cBuf.put("我是Petterp"); cBuf.flip(); //編碼 System.out.println(cBuf.limit()); ByteBuffer bBuf = ce.encode(cBuf); for (int i=0;i<11;i ){ System.out.println(bBuf.get()); } bBuf.flip(); CharBuffer cBuf2 = cd.decode(bBuf); System.out.println(cBuf2.toString()); System.out.println("__________"); //獲得解碼器 Charset cs2=Charset.forName("GBK"); bBuf.flip(); CharBuffer cBuf3 = cs2.decode(bBuf); System.out.println(cBuf3.toString());}
來(lái)源:https://www.icode9.com/content-1-443851.html為了解決二進(jìn)制序列與字符之間的對(duì)應(yīng)關(guān)系,這就需要字符集了。所謂字符集,就是為每個(gè)字符編個(gè)號(hào)碼而已。任何人都可以制定自己獨(dú)有的字符集明知要為每個(gè)字符編個(gè)號(hào)碼即可。當(dāng)然,如果每個(gè)人都制定自己獨(dú)有的字符集,那程序就沒(méi)法交流了。
聯(lián)系客服