NIO简介
javaNIO是有java1.4之后引入的一个新的IO API,可以提完标准的IO API,NIO与IO有相同的作用和目的,但使用方式完全不一样,NIO面向缓冲区、基于通道的IO操作,将以更加高效的方式进行文件读写操作。
IO NIO
面向流 面向缓冲区
阻塞 非阻塞
无 选择器
NIO:核心在于通道(channel)和缓冲区(buffer)。
通道表示打开到io设备(例如:文件、套接字)的连接若需要使用NIO系统,需要获取用于连接设备的通道以及用于容纳数据的缓存区,然后操作缓存区,对数据进行处理。
简单说,channel负责传输,buffer负责存储
一、缓冲区(Buffer):在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据
1.根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
上述七种缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
2.缓冲区存取数据的两个核心方法:
put() : 存入数据到缓冲区中
get() : 获取缓冲区中的数据
@Test
public void test2(){
ByteBuffer bf = ByteBuffer.allocate(1024);//创建一个缓冲区
bf.put("abc".getBytes());//往缓冲区放入数据
bf.flip(); //切换操作模式
byte[] dst = new byte[bf.limit()];//创建一个以缓冲区中存放数据大小相同的字节数组
bf.get(dst,0,dst.length);//从0的位置获取的数组长度的位置数据到dst数组中
System.out.println(new String(dst));//字节数据转换为字符窜打印
//abc
}
3.缓冲区中的四个核心属性:
capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后数据不能进行读写)
position : 位置,表示缓冲区中正在操作数据的位置。
mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置
0 <= mark <= position <= limit <= capacity
public abstract class Buffer {
private int mark = -1;//标记
private int position = 0;//当前位置
private int limit; //可操作空间大小
private int capacity; //容量
}
操作演示:
@Test
public void test3(){
ByteBuffer bf = ByteBuffer.allocate(1024);
System.out.println("---------未放入数据时---------");
System.out.println("bf.position()="+bf.position());
System.out.println("bf.limit()="+bf.limit());
System.out.println("bf.capacity()="+bf.capacity());
System.out.println("---------放入数据abcd---------");
bf.put("abcd".getBytes());
System.out.println("bf.position()="+bf.position());
System.out.println("bf.limit()="+bf.limit());
System.out.println("bf.capacity()="+bf.capacity());
System.out.println("---------flip()切换操作模式---------");
bf.flip();
System.out.println("bf.position()="+bf.position());
System.out.println("bf.limit()="+bf.limit());
System.out.println("bf.capacity()="+bf.capacity());
System.out.println("---------mark()的同时读取两个字节的数据---------");
bf.mark();
bf.get(new byte[2],0,2);
System.out.println("bf.position()="+bf.position());
System.out.println("bf.limit()="+bf.limit());
System.out.println("bf.capacity()="+bf.capacity());
System.out.println("---------reset()到上一次mark()位置---------");
bf.reset();
System.out.println("bf.position()="+bf.position());
System.out.println("bf.limit()="+bf.limit());
System.out.println("bf.capacity()="+bf.capacity());
}
/**
---------未放入数据时---------
bf.position()=0
bf.limit()=1024
bf.capacity()=1024
---------放入数据abcd---------
bf.position()=4
bf.limit()=1024
bf.capacity()=1024
---------flip()切换操作模式---------
bf.position()=0
bf.limit()=4
bf.capacity()=1024
---------mark()的同时读取两个字节的数据---------
bf.position()=2
bf.limit()=4
bf.capacity()=1024
---------reset()到上一次mark()位置---------
bf.position()=0
bf.limit()=4
bf.capacity()=1024
*/
4.直接缓冲区与非直接缓冲区:
非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中
直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
@Test//判断缓冲区类型
public void test1(){
ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteBuffer buffer2 = ByteBuffer.allocateDirect(1024);
}
5.其它方法
isDirect();//判断是否是直接缓冲区
flip();切换操作模式
reset();恢复到mark的位置
rewind() : 可重复读
clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态
@Test//判断缓冲区类型
public void test1(){
ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteBuffer buffer2 = ByteBuffer.allocateDirect(1024);
System.out.println(buffer.isDirect());//false
System.out.println(buffer2.isDirect());//true
}
@Test
public void test1(){
String str = "abcde";
//1. 分配一个指定大小的缓冲区
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() 存入数据到缓冲区中
buf.put(str.getBytes());
System.out.println("-----------------put()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//3. 切换读取数据模式
buf.flip();
System.out.println("-----------------flip()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//4. 利用 get() 读取缓冲区中的数据
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() : 可重复读
buf.rewind();
System.out.println("-----------------rewind()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
//6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态
buf.clear();
System.out.println("-----------------clear()----------------");
System.out.println(buf.position());
System.out.println(buf.limit());
System.out.println(buf.capacity());
System.out.println((char)buf.get());
}
二、通道Channel
1.通道channel介绍
有java.nio.channels包定义的。channel表示IO源于目标打开的连接,Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。
2.通道的主要实现类
java.nio.channels.Channel 接口:
|--FileChannel:用于读取、写入、映射和操作文件的通道,用于本地文件操作。
|--SocketChannel:通过TCP读写网络中的数据。
|--ServerSocketChannel:可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel。
|--DatagramChannel:通过UDP读写网络中的数据通道。
3.获取通道
3.1 Java 针对支持通道的类提供了 getChannel() 方法
本地 IO:用于本地数据操作
FileInputStream/FileOutputStream
RandomAccessFile
//以FileInputStream为例
FileInputStream fisChannel = new FileInputStream("需要读取的文件");
FileChannel fisChannel = fis.getChannel();//获取通道
网络IO:用于网络传输数据
Socket
ServerSocket
DatagramSocket
3.2使用JDK 1.7中的NIO.2 针对各个通道提供的静态方法open();
调用示例
FileChannel fis = FileChannel.open(Paths.get("d:/tomcat.zip"), StandardOpenOption.READ);
//源码:OpenOption... options--可变形参
public static FileChannel open(Path path, OpenOption... options)
throws IOException
{
Set<OpenOption> set = new HashSet<OpenOption>(options.length);
Collections.addAll(set, options);
return open(path, set, NO_ATTRIBUTES);
}
3.3在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel()
//源码
public static SeekableByteChannel newByteChannel(Path path, OpenOption... options)
throws IOException
{
Set<OpenOption> set = new HashSet<OpenOption>(options.length);
Collections.addAll(set, options);
return newByteChannel(path, set);
}
//FileChannel关系
public abstract class FileChannel
extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel
//示例:可知返回是SeekableByteChannel类型,SeekableByteChannel是FileChannel的父接口
SeekableByteChannel newByteChannel = Files.newByteChannel(Paths.get("d:/tomcat.zip"), StandardOpenOption.READ);
4.通道之间的数据传输
transferFrom():从哪里来,输出管道中的数据从哪里来
transferTo():到哪里去,读取管道的数据要输出到哪里去
示例代码一会会有专门一篇记录
5.分散(Scatter)与聚集(Gather)
分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中
聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
@SuppressWarnings("resource")
@Test
public void test5() throws IOException{
//1.创建操作对象,指定操作类型
RandomAccessFile ra1 = new RandomAccessFile("1.txt", "rw");
//2.获取通道
FileChannel channel = ra1.getChannel();
//3.创建缓冲区
ByteBuffer by1 = ByteBuffer.allocate(100);
ByteBuffer by2 = ByteBuffer.allocate(100);
ByteBuffer by3 = ByteBuffer.allocate(1024);
ByteBuffer[] buf = {by1,by2,by3};
//4.将通道中的数据读入缓冲区
channel.read(buf);
//5.操作指令转换,准备从0开始写数据模式
for (ByteBuffer byteBuffer : buf) {
byteBuffer.flip();
}
//打印每个分散数据值
System.out.println("-- "+new String(buf[0].array(),0,buf[0].limit()));
System.out.println("-- "+new String(buf[1].array(),0,buf[1].limit()));
System.out.println("-- "+new String(buf[2].array(),0,buf[2].limit()));
//6.创建输出对象,指定输出数据位置,操作指令
RandomAccessFile ra2 = new RandomAccessFile("2.txt", "rw");
//7.获取通道
FileChannel channel2 = ra2.getChannel();
//8.写出数据
channel2.write(buf);
//9.关闭通道
channel.close();
channel2.close();
}
6.字符集:Charset
编码:字符串 -> 字节数组
解码:字节数组 -> 字符串
查看NIO中支持的字符集
@Test
public void test5(){
//查看NIO中支持的字符集
Map<String, Charset> map = Charset.availableCharsets();
Set<Entry<String, Charset>> set = map.entrySet();
for (Entry<String, Charset> entry : set) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
编码与解码演示
@Test
public void test6() throws CharacterCodingException{
String str = "java从入门到放弃";
Charset charset = Charset.forName("GBK");
CharsetEncoder en = charset.newEncoder();
CharsetDecoder de = charset.newDecoder();
CharBuffer buffer = CharBuffer.allocate(1024);
buffer.put(str);
buffer.flip();
ByteBuffer encode = en.encode(buffer);
for(int i = 0;i<encode.limit();i++){
//encode.get():调用一次position位置向后移动一次
System.out.println(encode.get());
}
//切换指令准备解码,由此时get到末尾的position指向首位
encode.flip();
//2.同一对象,相同编码格式
CharBuffer decode = de.decode(encode);
// System.out.println("同一对象解码器="+new String(decode.array(),0,decode.limit()));
System.out.println("同一对象解码器="+decode.toString());
//2.不同对象,相同编码格式
Charset charset2 = Charset.forName("GBK");
CharsetDecoder de2 = charset2.newDecoder();
encode.flip();
CharBuffer decode2 = de2.decode(encode);
System.out.println("不同对象,同一格式解码器="+decode2.toString());
//3.不同对象,不同编码格式
//切换position位置
encode.flip();
/* Charset charset3= Charset.forName("utf-8");
CharsetDecoder de3 = charset3.newDecoder();
CharBuffer decode3 = de3.decode(encode);
System.out.println("不同象,不同格式解码器="+decode3.toString());*/
Charset charset3= Charset.forName("utf-8");
CharBuffer decode3 = charset3.decode(encode);
System.out.println("不同对象,不同格式解码器="+decode3.toString());
/**
同一对象解码器=java从入门到放弃
不同对象,同一格式解码器=java从入门到放弃
不同对象,不同格式解码器=java�����ŵ�����
*/
}
来源于尚硅谷官网NIO视频