一、
1. NIO 和 IO 的区别
Java NIO 的核心:通道(channel 负责传输) 和缓存区(buffer负责存储)
a) 缓冲区i.缓存区(buffer) :在Java nio 中负责数据的存取,缓冲区就是数组,用于存储不同数据类型的数据
根据数据类型不同提供了相应类型的缓冲区(Boolean类型除外)
ByteBuffer
CharBuffer
ShorBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBufferii.缓冲区存取数据的两个方法
put():存入数据到缓冲区
get():获取缓冲区中的数据iii.缓冲区中的四个核心属性
mark:标记,表示记录当前 position 的位置,可以通过reset 恢复到mark 记录的位置
position:位置,表示缓冲区中正在操作数据的位置
limit:界限,表示缓冲区可以操作数据的大小(limit 后面的数据不能进行读写)
capacity:容量,标书缓冲区中最大存储数据的容量,一旦申明不能改变
(0 ≤ mark ≤ position ≤ limit ≤ capacity)iv.其他方法
flip() :切换读取数据的模式
rewind ():可重复读数据
clear (): 清空缓冲区但是缓冲区的数据依然存在,但是处于被遗忘状态,不能正确的读取数据v.直接缓冲区和非直接缓冲区
1.非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中(堆内存开辟工作区间,数组)
2.直接缓冲区:通过allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率
缺点:物理内存映射文件中的内容不归我们管,要开辟物理内存,gc 回收
isDirect() 方法判断是否是直接缓冲区 isDirect()
b) 通道(channel):专门用IO操作
i. 通道与流的区别
通道(channel):用于源节点和目标节点的链接,在Java NIO 中负责缓冲区中数据的传输,通道本身不存储任何数据,需要配合缓冲区进行传输
ii. 通道的主要实现类:
java.nio.channels.Channel 接口
|–FileChannel(本地数据的传输)
|–SocketChannel(网络数据的传输)
|–ServerSocketChannel(网络数据的传输)
|–DatagramChannel(网络数据的传输)
iii. 获取通道的三种方式:
1. Java 针对支持通道的类提供了getChannel() 方法
本地IO:
FileInputStream/FileOutputStream
RandomAccessFile
网络IO:
Socket
ServerSocket (TCP)
DatagramSocket(UDP)
- JDK1.7 中的 NIO.2针对各个通道提供了一个静态方法 open()
- 在JDK1.7 中的NIO.2的 Files 工具类的newByteChannel()
iv. 通道之间的数据传输(直接缓冲区的方式)
transferFrom()
transferTo ()
v. 示例
1. 利用通道完成文件的复制
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("1.jpg");
fos = new FileOutputStream("2.jpg");
// ①获取通道
inChannel = fis.getChannel();
outChannel = fos.getChannel();
// ②分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// ③将通道中的数据存入缓冲区
while(inChannel.read(buf) != -1){
buf.flip(); // 切换成读取数据模式
outChannel.write(buf); // ④ 将缓冲区的数据写入通道
buf.clear(); // 清空缓冲区
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(outChannel !=null){
try {
outChannel.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();
}
}
}
- 只用直接缓冲区完成文件的复制(内存映射文件方式)(直接缓冲区的模式只有 ByteBuffer 支持)
@Test
public void test2() throws IOException{
Instant start = Instant.now();
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
// 内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
// 直接对缓冲区进行数据的读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
- 通道之间的数据传输
@Test
public void test3() throws IOException{
FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
// inChannel.transferTo(0, inChannel.size(), outChannel);
outChannel.transferFrom(inChannel, 0, inChannel.size());
}
vi. 分散(scatter)与聚集(gather)
分散读取(scattering reads): 将通道中的数据分散到多个缓冲区中
聚集写入(gathering writes): 将多个缓冲区的数据聚集到通道中
//分散聚集
@Test
public void test4() throws IOException{
RandomAccessFile raf1 = new RandomAccessFile("test.txt", "rw");
// 获取通道
FileChannel channel = raf1.getChannel();
// 分配指定大小的缓冲区
ByteBuffer buf1 = ByteBuffer.allocate(1024);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
// 分散读取
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()) );
// 具体写入
RandomAccessFile raf2 = new RandomAccessFile("test2.txt", "rw");
FileChannel channel2 = raf2.getChannel();
channel2.write(bufs);
}
vvi. 字符集 charset
编码:字符串—》字节数组
解码:字节数组—》字符串
@Test
public void test6() throws IOException{
Charset cs1 = Charset.forName("GBK");
// 获取编码器
CharsetEncoder encoder = cs1.newEncoder();
// 获取解码器
CharsetDecoder decoder = cs1.newDecoder();
CharBuffer cbuf = CharBuffer.allocate(1024);
cbuf.put("my test!");
cbuf.flip();
//编码
ByteBuffer bBuf = encoder.encode(cbuf);
for (int i = 0; i <12; i++) {
System.out.println(bBuf.get());
}
//解码
bBuf.flip();
CharBuffer ccbuf = decoder.decode(bBuf);
System.out.println(ccbuf.toString());
bBuf.flip();
// GBK 编码 utf-8 解码出现乱码
Charset cs2 = Charset.forName("UTF-8");
CharBuffer cbbuf = cs2.decode(bBuf);
System.out.println(cbbuf.toString());
}
- NIO 的非阻塞式网络通信
a) 使用NIO 完成网络通信的三个核心
// 客户端
@Test
public void client() throws IOException{
// 1、获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9803));
// 2、切换成非阻塞模式
socketChannel.configureBlocking(false);
// 3、分配指定大小的缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
// 4、读取本地文件并发送到服务器
buf.put(new Date().toString().getBytes());
buf.flip();
socketChannel.write(buf);
buf.clear();
// 4、关闭通道
socketChannel.close();
}
// 服务端
@Test
public void server() throws IOException{
// 1、获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
// 2、切换非阻塞模式
ssChannel.configureBlocking(false);
// 3、连接绑定
ssChannel.bind(new InetSocketAddress(9803));
// 4、获取选择器
Selector selector = Selector.open();
// 5、将通道注册到选择器上,并且指定监听事件
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
// 6、轮询式的获取选择器上已经“准备就绪”的时间
while(selector.select() > 0){
// 7、获取当前选择器中多有注册的选择键(已注册的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
// 8、迭代获取准备就绪的事件
SelectionKey sk = iterator.next();
// 9、判断具体是什么事件准备就绪
if(sk.isAcceptable()){
// 10、若"接收就绪" ,获取客户端链接
SocketChannel socketChannel = ssChannel.accept();
// 11、切换非阻塞模式
socketChannel.configureBlocking(false);
// 12、将该通道注册到选择器
socketChannel.register(selector, SelectionKey.OP_ACCEPT);
}else if(sk.isReadable()){
// 13、获取当前选择器上读就绪状态的通道
SocketChannel channel = (SocketChannel) sk.channel();
// 14、读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len = 0;
while((len = channel.read(buf)) > 0){
buf.flip();
System.out.println(new String(buf.array(), 0, len));
buf.clear();
}
}
// 15、取消选择键
iterator.remove();
}
}
}