nio简介
Java NIO(New IO)是从Java 1.4版本开始引入的 一个新的IO API,可以替代标准的Java IO API。
NIO与原来的IO有同样的作用和目的,但是使用 的方式完全不同,NIO支持面向缓冲区的、基于 通道的IO操
作。NIO将以更加高效的方式进行文件的读写操作。
javaNIO与IO的区别:
传统方式的IO:
通道和缓冲区
javaNIO系统的核心在于:
通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用 NIO 系统,需要获取 用于连接 IO 设备的通道以及用于容纳数据的缓冲 区。然后操作缓冲区,对数据进行处理。 Channel 负责传输, Buffer 负责存储。
缓冲区:
一个用于特定基本数据类型的容器。由java.nio包定义,所有缓冲区都是buffer抽象类的子类
java NIO中的buffer主要用于与NIO通道进行交互,数据是同通道读入缓冲区,从缓冲区写入通道中去。
Buffer的重要概念:
容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创
建后不能更改。
限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据
不可读写。缓冲区的限制不能为负,并且不能大于其容量。
位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为
负,并且不能大于其限制
标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法
指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这
个 position。
标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity
缓冲区的基本属性
Buffer的常用方法
缓冲区的数据操作
Buffer 所有子类提供了两个用于数据操作的方法:get()
与 put() 方法
/**
* 缓冲区 在java NIO中负责数据的存取。 缓冲区就是数组,用于存储不同数据类型的数据
* 根据数据类型不同(Boolean除外),提供相应的缓冲区
* ByteBuffer CharBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer
*
* 上述缓冲区的管理方式几乎一致,allocate()
* 缓冲区存取数据的两个核心方法 put():放入数据到缓冲区 get():获取缓冲区的数据
* 缓冲区的四个核心属性
* capacity 容量 一旦声明,不能改变。
* limit 界限:表示缓冲区可以操作数据的大小 limit后面的数据不能进行读写
* position 位置,表示缓冲区中正在操作数据的位置
* position <=limit<=capacity
*
* 直接缓冲区 非直接缓冲区 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm的内存中
* 缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
*/
public class testBuffer {
@Test
public void Test2(){
ByteBuffer buf = ByteBuffer.allocate(1024);
String str="abcde";
buf.put(str.getBytes());
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst,0,2);
System.out.println(new String(dst,0,2));
System.out.println(buf.position());
//mark() 标记一下
buf.mark();
buf.get(dst,2,2);
System.out.println(new String(dst,2,2));
System.out.println(buf.position());
buf.reset();//回到mark的位置
System.out.println(buf.position());
if(buf.hasRemaining()){
//剩余的可以操作的数据个数 刚才position回到了2,所以还剩下3个
System.out.println(buf.remaining());
}
}
@Test
public void test1(){
ByteBuffer buf = ByteBuffer.allocate(1024);
String str="abcde";
System.out.println("`````````````````allocate");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
//利用put()存入数据到缓冲区
buf.put(str.getBytes());
System.out.println("`````````````````put");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
//切换为读取数据模式
buf.flip();
System.out.println("````````````````flip");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
//利用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());//获取的时候position的位置往后移动
System.out.println(buf.capacity());
System.out.println(buf.limit());
//rewind():可重复读数据
buf.rewind();
System.out.println("``````````````rewind");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
//清空缓冲区 clear方法 回到最初状态 但是缓冲区的数据还在,只是处于被"遗忘状态"
System.out.println("`````````````clear");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
}
}
/**
* 缓冲区 在java NIO中负责数据的存取。 缓冲区就是数组,用于存储不同数据类型的数据
*
* 根据数据类型不同(Boolean除外),提供相应的缓冲区
* ByteBuffer CharBuffer ShortBuffer IntBuffer LongBuffer FloatBuffer DoubleBuffer
*
* 上述缓冲区的管理方式几乎一致,allocate()
* 缓冲区存取数据的两个核心方法 put():放入数据到缓冲区 get():获取缓冲区的数据
* 缓冲区的四个核心属性
* capacity 容量 一旦声明,不能改变。
* limit 界限:表示缓冲区可以操作数据的大小 limit后面的数据不能进行读写
* position 位置,表示缓冲区中正在操作数据的位置
* position <=limit<=capacity
*
* 直接缓冲区 非直接缓冲区 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm的内存中
* 缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
*
*
*/
public class testBuffer {
@Test
public void Test2(){
ByteBuffer buf = ByteBuffer.allocate(1024);
String str="abcde";
buf.put(str.getBytes());
buf.flip();
byte[] dst = new byte[buf.limit()];
buf.get(dst,0,2);
System.out.println(new String(dst,0,2));
System.out.println(buf.position());
//mark() 标记一下
buf.mark();
buf.get(dst,2,2);
System.out.println(new String(dst,2,2));
System.out.println(buf.position());
buf.reset();//回到mark的位置
System.out.println(buf.position());
if(buf.hasRemaining()){
//剩余的可以操作的数据个数 刚才position回到了2,所以还剩下3个
System.out.println(buf.remaining());
}
}
@Test
public void test1(){
ByteBuffer buf = ByteBuffer.allocate(1024);
String str="abcde";
System.out.println("`````````````````allocate");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
//利用put()存入数据到缓冲区
buf.put(str.getBytes());
System.out.println("`````````````````put");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
//切换为读取数据模式
buf.flip();
System.out.println("````````````````flip");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
//利用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());//获取的时候position的位置往后移动
System.out.println(buf.capacity());
System.out.println(buf.limit());
//rewind():可重复读数据
buf.rewind();
System.out.println("``````````````rewind");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
//清空缓冲区 clear方法 回到最初状态 但是缓冲区的数据还在,只是处于被"遗忘状态"
System.out.println("`````````````clear");
System.out.println(buf.position());
System.out.println(buf.capacity());
System.out.println(buf.limit());
}
@Test
public void Test3(){
//分配直接缓冲区
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
System.out.println(buf.isDirect());
}
}
/**
* 通道:用于源节点与目标节点的连接,在java nio负责缓冲区的数据的传输。channel本身是不存储数据的,因此需要配合缓冲区进行传输。
* 通道的一些主要实现类 java.nio,channel接口
* fileChannel socketChannel serverSocketChannel 网络io DatagramChannel UDP
*
* 1.如何获取通道
* java针对支持通道的类提供了getChannel()方法
* 本地IO
* FileInputStream/FileOutPutStream/RandomAccessStream
*
* 网络IO
* socket
* ServerAccessFile
* DatagramSocket
*
* 获取通道的方法
* 2.在JDK1.7中的NIO.2针对各个通道提供了静态方法 open()
*
*3.在JDK1.7中的NIO.2的Files工具类的newByteChannel()
*
*/
public class testChannel {
//利用通道完成文件的复制
@Test
public void test1() {
FileInputStream fis= null;
FileOutputStream fos= null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("src\\1.jpg");
fos = new FileOutputStream("src\\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 (IOException e) {
e.printStackTrace();
} finally {
try {
if(outChannel!=null) outChannel.close();
if(inChannel!=null) inChannel.close();
if(fos!=null) fos.close();
if(fis!=null) fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
//使用内存映射文件 完成复制
public void test2() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);//读的模式
FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
//内存映射文件
MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
//直接对缓冲区的数据进行读写操作
byte[] dst = new byte[inMappedBuf.limit()];
inMappedBuf.get(dst);
outMappedBuf.put(dst);
inChannel.close();
outChannel.close();
}
//四、通道之间的数据传输 transferFrom() transformTo()
@Test
public void test3() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);//读的模式
FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
// inChannel.transferTo(0,inChannel.size(),outChannel);
outChannel.transferFrom(inChannel,0,inChannel.size());
inChannel.close();
outChannel.close();
}
}
//五、分散与聚集
// 分散读取:将通道中的数据读取的数据分散到多个通道buffer中
//聚集写入:将多个buffer缓冲区聚集到channel
@Test
public void test4() throws Exception{
RandomAccessFile file=new RandomAccessFile("src\\1.txt","rw");//设置为读写模式
//1、获取通道
FileChannel channel = file.getChannel();
//2.获取指定大小的缓冲区
ByteBuffer btf1 = ByteBuffer.allocate(100);//缓冲区1
ByteBuffer btf2 = ByteBuffer.allocate(200);//缓冲区2
//3.分散读取
ByteBuffer[] buf={btf1,btf2};
channel.read(buf);
for (ByteBuffer temp:buf){
temp.flip();
}
System.out.println(new String(buf[0].array(),0,buf[0].limit()));//String方法的三个参数 数组 起始坐标 读取的长度
System.out.println(new String(buf[1].array(),0,buf[1].limit()));//String方法的三个参数 数组 起始坐标 读取的长度
//聚集写入 写入到2.txt
RandomAccessFile file2=new RandomAccessFile("src\\3.txt","rw");
FileChannel channel1 = file2.getChannel();
channel1.write(buf);
channel1.close();
}
//字符集 CharSet
//编码:字符串--->字节数组
//解码:字节数组--->字符集
@Test
public 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());
}
}
//字符集
@Test
public void test6() throws Exception{
Charset gbk = Charset.forName("GBK");
//获取编码器
CharsetEncoder encoder = gbk.newEncoder();
//获取解码器
CharsetDecoder decoder = gbk.newDecoder();
CharBuffer buf = CharBuffer.allocate(1024);
buf.put("xxx");
buf.flip();
//编码
ByteBuffer encode1 = encoder.encode(buf);
for (int i = 0; i < encode1.limit(); i++) {
System.out.println(encode1.get());
}
//对编码的内容进行解码
encode1.flip();
CharBuffer decode = decoder.decode(encode1);
System.out.println(decode.toString());
System.out.println("-----------");//换utf-8进行解码
Charset charset = Charset.forName("utf-8");
encode1.flip();
CharBuffer decode1 = charset.decode(encode1);
System.out.println(decode1.toString());
}
NIO的非阻塞网络通信
阻塞与非阻塞
缺点:即使加入多线程,让其去访问服务器,但是CPU的利用率还是不够高。
NIO的形式 非阻塞式
阻塞式示例代码
/**
* 使用nio完成完成网络通信的三个核心
* 1.通道:负责连接
*
* --java.nio.channels.channel接口
* --SelectableChannel
* --SocketChannel TCP
* --serverSocketChannel TCP
* --DatagramChannel UDP
*
* --Pipe.SinkChannel
* --Pipe.SourceChannel
*
* 2.缓冲区:负责数据的存取
* 3.选择器:是selectableChannel的多路复用器。用于监控selectableChannel的IO状况
*/
//阻塞示例
public class TestBlockNIO {
//客户端
@Test
public void client() throws Exception{
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));//服务端通道
FileChannel inChannel=FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);
//2.分配指定大小的缓冲区域
ByteBuffer buf = ByteBuffer.allocate(1024);
//3.读取本地文件,并发送到服务器
while(inChannel.read(buf)!=-1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
//关闭通道
inChannel.close();
sChannel.close();
}
//服务端
@Test
public void server()throws Exception{
//1、获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();//ssChannel serverSocketChannel
FileChannel outChannel=FileChannel.open(Paths.get("src\\2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
//2.绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//3.客户端连接的通道
SocketChannel sChannel = ssChannel.accept();
//4.接受客户端的数据,并保存到本地
//缓冲区
ByteBuffer dst = ByteBuffer.allocate(1024);
while (sChannel.read(dst)!=-1){
dst.flip();
outChannel.write(dst);
dst.clear();//必须放在while循环的内部,为什么呢?我想应该是终止while,也就是读取完数据跳出循环的条件
}
outChannel.close();
sChannel.close();
ssChannel.close();
}
}
思路:如果出现错误,可以使用try//catch方法看看哪里出现了问题
注意:先启动服务器,之后再其中客户端。如果先启动服务器,那和谁连呢吗,所以,需要先让服务器处于就绪状态。
非阻塞示例代码
public class testBlockNIO {
//客户端
@Test
public void client() throws Exception{
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
FileChannel file=FileChannel.open(Paths.get("src\\1.png"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(1024);
while(file.read(buf)!=-1){
buf.flip();
sChannel.write(buf);
buf.clear();
}
sChannel.shutdownOutput();//告诉服务器已经传输完毕
//接受服务端的反馈
int len=0;
while((len=sChannel.read(buf))!=-1){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
file.close();
sChannel.close();
}
//服务端
@Test
public void server()throws Exception{
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.bind(new InetSocketAddress(9898));
FileChannel outChannel = FileChannel.open(Paths.get("src\\2.png"), StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
SocketChannel sChannel = ssChannel.accept();
ByteBuffer buf = ByteBuffer.allocate(1024);
while (sChannel.read(buf)!=-1){
buf.flip();
outChannel.write(buf);
buf.clear();
}
//发送返回给客户端
buf.put("服务端接受数据成功".getBytes());
buf.flip();
sChannel.write(buf);
sChannel.close();
outChannel.close();
ssChannel.close();
}
}
非阻塞式NIO
public class testNotBlockingNIO {
//客户端
@Test
public void client()throws Exception{
//1.获取通道
SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//2.切换为非阻塞模式
sChannel.configureBlocking(false);
//3.分配指定大小的缓冲区
ByteBuffer buf=ByteBuffer.allocate(1024);
//4.发送数据给服务端
buf.put(new Date().toString().getBytes());
buf.flip();
buf.clear();
//关闭通道
sChannel.close();
}
//服务端
@Test
public void server() throws Exception{
//获取通道
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);//切换为非阻塞模式
FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
//绑定连接
ssChannel.bind(new InetSocketAddress(9898));
//获取选择器
Selector selector = Selector.open();
//将通道注册到选择器上,指定“监听接受事件”
SelectionKey register = ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//轮询的获取选择器上已经准备就绪的事件
while(selector.select()>0){
//获取当前选择器中所有注册的选择键(已就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//迭代器迭代
while (iterator.hasNext()){
//获取准备就绪的事件
SelectionKey sk = iterator.next();
//判断是什么事件准备就绪
if(sk.isAcceptable()){
//若接受就绪,获取客户端连接
SocketChannel sChannel = ssChannel.accept();
//切换非阻塞模式
sChannel.configureBlocking(false);
//将通道注册到选择器上
sChannel.register(selector,SelectionKey.OP_READ);
}else if(sk.isReadable()){
//获取当前选择器上 读就绪 状态的通道
SocketChannel sChannel = (SocketChannel) sk.channel();
//读取数据
ByteBuffer buf = ByteBuffer.allocate(1024);
int len=0;
while ((len=sChannel.read(buf))>0){
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
//取消SelectionKey
iterator.remove();
}
}
}
}
Java NIO中的DatagramChannel是一个能收发UDP包的通道。
操作步骤:
打开DatagramChannel---------------------------接受/发送数据
Datagram示例代码:
public class testNonblockingNIO {
@Test
public void send() throws IOException {
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while(scanner.hasNext()){
String str = scanner.next();
buf.put((new Date().toString()+":\n"+str).getBytes());
buf.flip();
dc.send(buf,new InetSocketAddress("127.0.0.1",9898));
buf.clear();
}
dc.close();
}
@Test
public void receive() throws IOException{
DatagramChannel dc = DatagramChannel.open();
dc.configureBlocking(false);
dc.bind(new InetSocketAddress(9898));
Selector selector=Selector.open();
dc.register(selector, SelectionKey.OP_READ);
while (selector.select()>0){
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext()){
SelectionKey sk = it.next();
if(sk.isReadable()){
ByteBuffer buf = ByteBuffer.allocate(1024);
dc.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
}
it.remove();
}
}
}
idea出现控制台无法输入的问题:
help---edit custom vm options...
粘贴下面这句话就可以解决。
-Deditable.java.test.console=true
管道:Java NIO 管道是2个线程之间的单向数据连接。 Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
示例代码:
public class TestPipe {
@Test
public void test1()throws Exception{
//1.获取管道
Pipe pipe = Pipe.open();
//2.将缓冲区的数据写入管道
ByteBuffer buf = ByteBuffer.allocate(1024);
//向管道写输入
Pipe.SinkChannel sinkChannel = pipe.sink();
buf.put("通过单向管道发送数据".getBytes());
buf.flip();
//向管道中写入数据
sinkChannel.write(buf);
//读取缓冲区的数据,访问source通道
Pipe.SourceChannel sourceChannel = pipe.source();
//切换为读取数据的模式
buf.flip();
int len = sourceChannel.read(buf);
System.out.println(new String(buf.array(),0,len));
//关闭资源
sourceChannel.close();
sinkChannel.close();
}
}