Java NIO 是從Java 1.4版本開始引入的一個(gè)新的 IO API,可以替代標(biāo)準(zhǔn)的 Java IO API。NIO與原來的 IO 有同樣的作用和目的,但是使用的方式完全不同,NIO 支持面向 緩沖區(qū) 的,基于 通道 的IO 操作,至于什么是緩沖區(qū),什么是通道,接下來我將會(huì)用大白話一一說明??傊?strong>NIO 就是以更高效的方式進(jìn)行文件的讀寫操作。
在學(xué)習(xí)本篇之前,首先你要對(duì) IO 有一定的了解。當(dāng)然不了解的話,也可以看得哈哈,我會(huì)說的很通俗易懂。 我們先看看 Java NIO 與 IO的主要區(qū)別: IO | NIO |
---|
面向流(Stream Oriented) | 面向緩沖區(qū)(Buffer Oriented) | 阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) | 無 | 選擇器(Selectors) |
上面的什么 面向緩沖區(qū),又什么非阻塞IO,又是選擇器的,這些到底都啥啊,拍桌子。。。 下面我會(huì)在本篇中對(duì)上面出現(xiàn)的概念及盲點(diǎn)進(jìn)行解析。 IO與NIO的區(qū)別首先我們看看他們的區(qū)別 為什么說IO是面向流,那流又是什么呢? 
我們先看上面的圖片。 我們?cè)趯W(xué)IO的時(shí)候,肯定都聽過這樣的例子,IO流就相當(dāng)于一條管道,它里面所有的操作都是單向的。如果要把文件中的數(shù)據(jù)拿到程序中,需要建立一條通道。想把程序中的數(shù)據(jù)存到文件中也需要建立一條通道。所以我們成io流是單向的。因?yàn)閕o管道里實(shí)際面對(duì)的是字節(jié)的流動(dòng),所以我們稱io流為面向流。 那什么說NIO是面向緩沖區(qū)呢? 
你可以這樣想象,通道就相當(dāng)于與一條道路,緩沖區(qū)相當(dāng)于出租車,出租車上拉的是乘客,出租車可以上乘客也可以下乘客?;氐絅IO 上面,通道就是一條道路,他負(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ū)。通道表示 打開IO 設(shè)備(例如:文件,套接字)的鏈接。若需要使用 NIO 系統(tǒng),需要獲取用于鏈接 IO 的設(shè)備的通道以及用于容納數(shù)據(jù)的緩沖區(qū)。然后操作緩沖區(qū),對(duì)數(shù)據(jù)進(jìn)行處理。 緩沖區(qū)(Buffer)/*緩沖區(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ū)的管理方式幾乎一致,都是通過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)行讀寫)
* position:位置,表示緩沖區(qū)中正在操作數(shù)據(jù)的位置。
*
* 5.直接緩沖區(qū)與非直接緩沖區(qū)
* 非直接緩沖區(qū):通過 allocate()方法分配緩沖區(qū),將緩沖區(qū)建立在 JVM的內(nèi)存中
* 直接緩沖區(qū):通過 allocateDirect() 方法分配直接緩沖區(qū),將緩沖區(qū)建立物理內(nèi)存中。
*
* mark: 標(biāo)記,表示記錄當(dāng)前 postion 的位置,可以通過 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),所以無法正確讀取數(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ū)與緩沖區(qū)的區(qū)別非直接緩沖區(qū)在,建立在JVM內(nèi)存中,實(shí)際讀寫數(shù)據(jù)時(shí),需要在 OS 和JVM之間進(jìn)行數(shù)據(jù)拷貝。 
為什么不直接讓磁盤控制器把數(shù)據(jù)送到用戶控件的緩沖區(qū)呢? 因?yàn)槲覀兊挠布ǔ2荒苤苯釉L問用戶內(nèi)存空間。如果有一個(gè)程序需要讀寫磁盤空間,出于系統(tǒng)安全考慮,磁盤中的文件無法直接傳輸?shù)轿覀兂绦蛑?,它必須?jīng)過系統(tǒng)的內(nèi)核地址空間的緩存中,然后將內(nèi)核地址空間數(shù)據(jù)復(fù)制到用戶地址空間,這樣數(shù)據(jù)才可以傳輸?shù)轿覀兊膽?yīng)用程序。
內(nèi)存映射空間 直接緩沖區(qū),緩沖區(qū)建立在受操作系統(tǒng)管理的物理內(nèi)存中,OS和JVM直接通過這塊物理內(nèi)存進(jìn)行交互,沒有了中間的拷貝環(huán)節(jié) 
但是直接緩沖區(qū)也有很多弊端:
//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ù)寫入通道中
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ù)的讀寫操作
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)通道(Channel) 由java.nio.channels 包定義的。Channel 表示 IO 源于目標(biāo)打開的鏈接。Channel 類似于傳統(tǒng)的流,只不過 Channel 本身不能直接訪問數(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ù)寫入通道中
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();
}
}
}
}
通道之間的數(shù)據(jù)傳輸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();
}
}
分散(Scatter)與聚集(Gather)[外鏈圖片轉(zhuǎn)存失敗(img-FzYapv7x-1567945086087)(C:\Users\Pettepr\AppData\Roaming\Typora\typora-user-images\1554968002880.png)] 分散讀取(Scattering Reads):將通道中的數(shù)據(jù)分散到多個(gè)緩沖區(qū)中 聚集寫入(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.聚集寫入
RandomAccessFile raf2=new RandomAccessFile("D:demo2.txt","rw");
FileChannel channel2=raf2.getChannel();
channel2.write(bufs);
}
字符集:Charset計(jì)算機(jī)里的文件,數(shù)據(jù),圖片文件只是一種表面現(xiàn)象,所有文件在底層都是二進(jìn)制文件,即全部都是字節(jié)碼。 對(duì)于文本文件而言,之所以可以看到一個(gè)個(gè)的字符,這完全是因?yàn)橄到y(tǒng)將底層的二進(jìn)制序列轉(zhuǎn)換成字符的緣故。在這個(gè)過程中涉及兩個(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)亂碼等問題。 JDK1.4 提供了 Charset來處理字節(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());
}
二進(jìn)制序列和字符之間如何對(duì)應(yīng)呢?為了解決二進(jìn)制序列與字符之間的對(duì)應(yīng)關(guān)系,這就需要字符集了。所謂字符集,就是為每個(gè)字符編個(gè)號(hào)碼而已。任何人都可以制定自己獨(dú)有的字符集明知要為每個(gè)字符編個(gè)號(hào)碼即可。當(dāng)然,如果每個(gè)人都制定自己獨(dú)有的字符集,那程序就沒法交流了。 來源:https://www./content-1-443851.html
|