Java NIO 学习总结(一)
主要内容:
1.IO与NIO的区别
2.NIO的缓冲区数据存取操作
3.通道(Channel)的原理与获取
4.通道的数据传输与内存映射文件
5.分散读取与聚集写入
6.字符集Charset
一、IO与NIO的区别
1.NIO与原IO有同样的作用和目的,但使用方式完全不同,NIO支持面向缓冲区,基于通道的IO操作,NIO读写更加高效。其主要区别如下:
带着问题:非阻塞如何理解? 非阻塞与选择器 主要是针对 网络IO通信的
2.小结:
IO:是单向传输数据管道,既负责连接,也负责数据传输;
NIO:通道->是双向连接(铁路),但不负责数据的存取;缓冲区->书传输数据的载体(铁路上的火车),负责数据的存取,分工更细;
二、NIO的缓冲区数据存取操作
1.缓冲区,在JAVA NIO中负责数据的存取,其底层实现就是数组,不同的数据类型,提供了不同的缓冲区,如:ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DubbleBuffer、CharBuffer,而没有boolean类型的Buffer
package com.jupiter.test;
import java.lang.String;
import org.junit.Test;
import java.nio.Buffer;
import java.nio.ByteBuffer;
/**
* @author Jupiter
* @date 2019/3/2-11:15
* @description 简单的buffer操作
*/
public class TestBuffer {
/*
* 1.缓冲区的两个核心方法:
* put() : 存入数据到缓冲区中
* get() : 从缓冲区中读数据
* 2.缓冲区中的四个核心属性
* capacity : 表示缓冲区的大小,最大存储数据量,一旦申明不能改变
* limit : 缓冲区中可以操作数据的大小
* position : 表示正在操作缓冲区中数据的位置
* mark : 标记当前position的位置,可以通过reset方法恢复到mark的位置
* 大小 : 0 <= mark <= position <= limit <=capacity
*/
@Test
public void test1(){
String src = "abcde";
//1. 创建一个指定大小的buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("---------------allocate()-------------");
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//2. 利用put将数据存入到缓冲区中
buffer.put(src.getBytes());//capacity=1024,limit=1024,position=5
System.out.println("---------------put()-------------");
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//3. 切换buffer为读模式
buffer.flip();//capacity=1024,limit=5,position=0
System.out.println("---------------flip()-------------");
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//4. 利用get读取缓冲区数据
//新建目标字节数组
byte[] target = new byte[buffer.limit()];
//取数据放到目标字节数组中
buffer.get(target, 0, buffer.limit());//capacity=1024,limit=5,position=5
System.out.println("---------------get()-------------");
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//5. 可重复读
buffer.rewind();
System.out.println("---------------rewind()-------------");
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//6. 清空缓冲区,但是缓冲区的数据依然存在!只不过出于“被遗忘状态”
buffer.clear();
System.out.println("---------------clear()-------------");
System.out.println(buffer.position());
System.out.println(buffer.limit());
System.out.println(buffer.capacity());
//检验数据是否还存在
System.out.println("---------------取出被“清空”后的buffer中的第一个char字符-------------");
System.out.println((char)buffer.get());
}
@Test
public void test2(){
String src = "abcde";
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(src.getBytes());
byteBuffer.flip();//切换到读模式
byte[] target = new byte[byteBuffer.limit()];
byteBuffer.get(target, 0, 2);//读取buffer中的前两位
System.out.println(byteBuffer.position());//当前position
byteBuffer.mark();//标记
byteBuffer.get(target, 2, 2);//读取buffer中的三四位
System.out.println(byteBuffer.position());//当前position=4
byteBuffer.reset();//跳回到标记的地方
System.out.println(byteBuffer.position());//当前position=mark=2
}
}
2.NIO中的直接缓冲区与非直接缓冲区
非直接缓冲区:通过allocate()方法,在JVM的堆内存中分配缓冲区
直接缓冲区:通过allocate()方法,直接在操作系统物理内存中开辟缓冲区
使用直接缓冲区,有些时候可以提高效率,原理:
1)非直接缓冲区操作数据时,操作系统OS有一个缓冲区,JVM中也有一个缓冲区,这样程序与磁盘之间交互数据时,会有缓冲区与缓冲区的copy操作,而直接缓冲区是直接应用程序在物理内存中开辟一个缓冲区,省去了copy数据的操作,从而提高了效率。
2)但是直接缓冲区有弊端:应用程序写数据到直接缓冲区后,物理内存中的直接缓冲区将又操作系统OS控制;物理内存中开辟缓冲区开销大,如果物理内存中的数据不能及时处理,操作系统会停机。
三、通道的原理与获取、通道的数据传输与内存映射文件、分散读取与聚集写入、字符集Charset
package com.jupiter.test;
import org.junit.Test;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
/**
* @author Jupiter
* @date 2019/3/4-22:20
* @description 简单的Channel操作
*/
public class TestChannel {
/*
* 一.通道是连接。通道本身不负责传输,而是配合缓冲区传输数据
* 二.通道的主要实现类:
* FileChannel
* SocketChannel
* ServerSocketChannel
* DatagramChannel
* 三.获取通道
* 1.Java针对支持通道的类,提供了getChannel()方法
* 本地IO:
* FileInputStream/FileOutputStream
* RandomAccessFile
* 网络IO:
* Socket
* ServerSocket
* DatagramSocket
* 2.Java1.7之后的各通道实现类的open()静态方法
* 3.Java1.7之后的Files工具类的newByteChannel()方法
*
* 四.通道之间的数据传输
* transferFrom()
* transferTo()
* 五.分散(scatter)与聚集(Gather)
* 1.分散读取:与通道读取多个加载数据的buffer
* 2.聚集写入:将多个缓冲区的数据聚集到通道中
* 六.字符集:Charset
* 1.编码:字符串 -> 字节数组
* 2.解码:字节数组 -> 字符串
*/
/**
* @description 利用通道+非直接缓冲区完成文件复制
*/
@Test
public void test1() throws Exception {
//文件流对象相当于只指定了文件传输的起始与终点
FileInputStream fis = new FileInputStream("o:/1.flv");
FileOutputStream fos = new FileOutputStream("o:/2.flv");
//获取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//将通道中的数据写入缓冲区,数据装车
while ((inChannel.read(buffer) != -1)) {
//切换buffer为读数据模式
buffer.flip();
//数据下车
outChannel.write(buffer);
buffer.clear();//清空buffer
}
fis.close();
inChannel.close();
fos.close();
outChannel.close();
}
/**
* @description 利用 FileChannel+内存映射文件 复制文件
*/
@Test
public void test2() throws IOException {
//创建两条通道:读数据通道、写数据通道
FileChannel inChannel = FileChannel.open(Paths.get("o:/1.flv"), StandardOpenOption.READ);
//CREATE_NEW:当对象存在就会报错;CREATE:当对象存在就不操作
FileChannel outChannel = FileChannel.open(Paths.get("o:/2.flv"), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
//内存映射文件:建立内存映射冲区 读数据的一个缓冲区、写数据的一个缓冲区
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, outChannel.size());
//直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];//在JVM堆内存中创建与内存映射相等大小的字节数组对象:大对象开销很大
inMappedBuf.get(dst);//将缓冲区的数据读到堆内存
outMappedBuf.put(dst);//将堆内存的数据写到缓冲区
inChannel.close();
outChannel.close();
}
/**
* @description 利用直接缓冲区+通道完成文件复制
*/
@Test
public void test3() throws IOException {
FileChannel inChannel = FileChannel.open(Paths.get("o:/1.flv"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("o:/2.flv"), StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE);
//调用channel的transferFrom/To方法直接操作物理内存
outChannel.transferFrom(inChannel, 0, inChannel.size());
inChannel.close();
outChannel.close();
}
/**
* @description 分散读取与聚集写入
*/
@Test
public void test4() throws IOException {
//1.txt内容:abcdefg
RandomAccessFile file = new RandomAccessFile("o:/1.txt","rw" );
//获取通道
FileChannel channel = file.getChannel();
//分配两个缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(5);
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
//分散读取
ByteBuffer[] bufs = {buffer1, buffer2};
channel.read(bufs);
for (ByteBuffer buffer: bufs)
buffer.flip(); // 每个buffer都切换到读模式
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()));
//聚集写入
RandomAccessFile outFile = new RandomAccessFile("o:/2.txt", "rw");
//获取写出数据的channel
FileChannel outChannel = outFile.getChannel();
outChannel.write(bufs);
file.close();
channel.close();
outFile.close();
outChannel.close();
}
/**
* @description 测试编码
*/
@Test
public void test5() throws CharacterCodingException {
Charset cs = Charset.forName("UTF-8");
//获取编码器
CharsetEncoder charsetEncoder = cs.newEncoder();
//获取解码器
CharsetDecoder charsetDecoder = cs.newDecoder();
CharBuffer cBuffer = CharBuffer.allocate(1024);
cBuffer.put("你好");
cBuffer.flip();
//编码
ByteBuffer buffer = charsetEncoder.encode(cBuffer);
for (int i=0; i<"你好".getBytes().length; i++){
System.out.println(buffer.get());
}
//解码
buffer.flip();
CharBuffer result = charsetDecoder.decode(buffer);
System.out.println(result);
buffer.rewind();
Charset cs2 = Charset.forName("UTF-8");
CharsetDecoder charsetDecoder1 = cs2.newDecoder();
CharBuffer result2 = charsetDecoder1.decode(buffer);
System.out.println(result2);//正常输出
buffer.rewind();
Charset cs3 = Charset.forName("GBK");
CharsetDecoder charsetDecoder2 = cs3.newDecoder();
CharBuffer result3 = charsetDecoder2.decode(buffer);
System.out.println(result3);//乱码输出
}
}