NIO概述
- 同步与异步(synchronous/asynchronous) : 同步是⼀种可靠的有序运⾏机制,当我们进⾏同步操作时,后续的任务是等待当前调⽤返回,才会进⾏下⼀步;⽽异步则相反,其他任务不需要等待当前调⽤返回,通常依靠事件、回调等机制来实现任务间次序关系
-
同步 : 调⽤⽅法之后 , 必须要得到⼀个返回值 例如 : 买⽕⻋票, ⼀定要买到票 , 才能继续下⼀步
-
异步 : 调⽤⽅法之后 , 不需要有返回值 , 但是会有回调函数 , 回调函数指的是满⾜条件之后会⾃动执⾏的⽅法 例如:买⽕⻋票, 不⼀定要买到票 , 我可以交代售票员 , 当有票的话, 你就帮我出张票
-
-
阻塞与⾮阻塞:在进⾏ 阻塞 操作时,当前线程会处于阻塞状态,⽆法从事其他任务,只有当条件就绪才能继续,⽐如ServerSocket 新连接建⽴完毕,或者数据读取、写⼊操作完成;⽽⾮阻塞 则是不管 IO 操作是否结束,直接返回,相应操作在后台继续处理
-
阻塞 : 如果没有达到⽅法的⽬的 , 就会⼀直停在那⾥ ( 等待 ) , 例如 : ServerSocket 的 accept() ⽅法
-
⾮阻塞 : 不管⽅法有没有达到⽬的 , 都直接往下执⾏ ( 不等待)
-
IO: 同步阻塞
- NIO: 同步⾮阻塞
-
AIO: 异步⾮阻塞
Buffer类
概述:Buffer是⼀个对象,它是对某种基本类型的数组进⾏了封装
- Buffer主要有如下几种:
- ByteBuffer
-
CharBuffer
-
DoubleBuffer
-
FloatBuffer
-
IntBuffer
-
LongBuffer
-
ShortBuffer
- 创建ByteBuffer
- ⽅式⼀:在堆中创建缓冲区:allocate(int capacity)
-
⽅式⼆ : 在系统内存创建缓冲区: allocatDirect(int capacity)
- ⽅式三:通过数组创建缓冲区:wrap(byte[] arr)
代码演示:
⽅式⼀:在堆中创建缓冲区:
allocate(int capacity)
public static void main ( String [] args ) {// 创建堆缓冲区ByteBuffer byteBuffer = ByteBuffer . allocate ( 10 );}
⽅式⼆
:
在系统内存创建缓冲区:
allocatDirect(int capacity)
public static void main ( String [] args ) {// 创建直接缓冲区ByteBuffer byteBuffer = ByteBuffer . allocateDirect ( 10 );}
- 在堆中创建缓冲区称为:间接缓冲区
- 在系统内存创建缓冲区称为:直接缓冲区
- 间接缓冲区的创建和销毁效率要⾼于直接缓冲区
- 间接缓冲区的⼯作效率要低于直接缓冲区
⽅式三:通过数组创建缓冲区:wrap(byte[] arr)
public static void main ( String [] args ) {byte [] byteArray = new byte [ 10 ];ByteBuffer byteBuffer = ByteBuffer . wrap ( byteArray );}
此种⽅式创建的缓冲区为:间接缓冲区
添加数据-put
讲解
- public ByteBuffer put(byte b):向当前可⽤位置添加数据。
-
public ByteBuffer put(byte[] byteArray) :向当前可⽤位置添加⼀个byte[] 数组
-
public ByteBuffer put(byte[] byteArray,int offset,int len):添加⼀个 byte[] 数组的⼀部分
public class Bufferlei {
public static void main(String[] args) {
//Buff主要有如下几种:
/*ByteBUffer
CharBuffer
DoubleBUffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
*/
//ByteBuffer堆中缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
//添加数据
byteBuffer.put((byte) 1);
byteBuffer.put((byte) 2);
byteBuffer.put((byte) 3);
//ByteBUffer转换为普通字节数组
byte [] bytes = byteBuffer.array();
System.out.println(Arrays.toString(bytes));
//创建系统内存创建缓冲区
ByteBuffer b1 = ByteBuffer.allocateDirect(10);
b1.put((byte) 3);
b1.put((byte) 2);
b1.put((byte) 1);
byte[] b = byteBuffer.array();
System.out.println(Arrays.toString(b));
//创建数组缓冲区
byte[] b2 = {5,6,7};
ByteBuffer b3 = ByteBuffer.wrap(b2);
byte []b4 = b3.array();
System.out.println(Arrays.toString(b4));
}
}
容量-capacity
讲解
Buffer
的容量
(capacity)
是指:
Buffer
所能够包含的元素的最⼤数量。定义了Buffer
后,容量是不可变的。
public class Test02 {
public static void main(String[] args) {
/*
* 容量-capacity
* Buffer所能包含的元素的最大数量,定义了BUffer后,容量是不可变的
*
* */
ByteBuffer b1 = ByteBuffer.allocate(10);
System.out.println("容量"+b1.capacity());//10 之后不可改变
byte [] bytes = {97,98,99,100};
ByteBuffer b2 = ByteBuffer.wrap(bytes);
System.out.println("容量:"+b2.capacity());//容量4,之后不可改变
}
}
限制-limit
讲解:
- 限制limit是指:第⼀个不应该读取或写⼊元素的index索引。缓冲区的限制(limit)不能为负,并且不能⼤于容量。
-
有两个相关⽅法:
-
public int limit() :获取此缓冲区的限制。
-
public Buffer limit(int newLimit) :设置此缓冲区的限制
-
public class Test03 {
public static void main(String[] args) {
/*
* 限制-limit
*
* */
ByteBuffer b1 = ByteBuffer.allocate(10);
//添加数据
b1.put((byte) 10);
//获取限制
int limit1 = b1.limit();
System.out.println("limitl:"+limit1);//10
//设置限制
b1.limit(3);
b1.put((byte) 20);
b1.put((byte) 30);
//b1.put((byte) 40);
// b1.put((byte)40);// 报异常,因为限制
//位置索引为3,所以再存14就会报异常:BufferOverflowException
System.out.println(b1);
}
}
位置-position
讲解
- 位置position是指:当前可写⼊的索引。位置不能⼩于0,并且不能⼤于"限制"。
-
有两个相关⽅法:
-
public int position() :获取当前可写⼊位置索引。
-
public Buffer position(int p) :更改当前可写⼊位置索引。
-
public class Test04 {
public static void main(String[] args) {
//位置-position
//他是指当前可写入的索引 位置不能小于0;并且不能大于限制
//int position()获取当前写入位置索引
//Buffer position(int p ) 更改当前写入位置的索引
ByteBuffer b1 = ByteBuffer.allocate(10);
//添加数据
b1.put((byte) 10);
//获取当前位置索引
int position = b1.position();
System.out.println("position"+position);//1
//设置当前位置索引
b1.position(5);
b1.put((byte) 22);
b1.put((byte) 33);
System.out.println("position"+b1.position());//7
System.out.println(Arrays.toString(b1.array()));
}
}
标记 -mark
讲解
- 标记mark是指:当调⽤缓冲区的reset()⽅法时,会将缓冲区的position位置重置为该索引。
- 相关方法:
-
public Buffer mark() :设置此缓冲区的标记为当前的position位置。
-
public Buffer reset() : 将此缓冲区的位置重置为以前标
-
public class Test05 {
public static void main(String[] args) {
//标记-mark
//Buffer mark()设置此缓冲区的标记当前的position位置
//Buffer reset()将此缓冲区的位置重置为以前标记的位置
ByteBuffer b1 = ByteBuffer.allocate(10);
//添加元素
b1.put((byte) 11);
//获取当前位置索引
int position = b1.position();
System.out.println("position:"+position);
//标记当前位置索引
b1.mark();
//添加元素
b1.put((byte) 22);
b1.put((byte) 33);
//获取当前位置索引
System.out.println("position:"+b1.position());
System.out.println(Arrays.toString(b1.array()));
//重置当前位置索引
b1.reset();
//获取当前位置索引
System.out.println("position:"+b1.position());
//添加元素
b1.put((byte) 44);
System.out.println(Arrays.toString(b1.array()));
}
}
其他方法
//其他方法
/*
* int remaining()获取position与limit之间的元素数
* boolean isReadOnly()获取当前缓冲区是否只读
* boolean isDirect()获取当前缓冲区是否为直接缓冲区
* Buffer rewind() 重绕缓冲区
* 将position位置设置为:0
* 限制limit不变
* 丢弃标记
*
* Buffer clear()还原缓冲区的状态
* 将limit设置为当前position位置;
- 将当前position位置设置为0;
- 丢弃标记。
*
* */
public static void main(String[] args) {
//创建对象
ByteBuffer buffer =ByteBuffer.allocate(10);
//添加元素
buffer.put((byte)11);
buffer.put((byte)22);
buffer.put((byte)33);
//限制
buffer.limit(6);
//容量是10 位置是3 限制是6
System.out.println("容量是" +buffer.capacity() + " 位置是" +buffer.position() + " 限制是" +buffer.limit());
//还原
buffer.clear();
//容量是10 位置是0 限制是10
System.out.println("容量是" +buffer.capacity() + " 位置是" +buffer.position() + " 限制是" +buffer.limit());
}
public static void main(String[] args) {
//创建对象
ByteBuffer buffer =
ByteBuffer.allocate(10);
//添加元素
buffer.put((byte)11);
buffer.put((byte)22);
buffer.put((byte)33);
//限制
buffer.limit(6);
//容量是10 位置是3 限制是6
System.out.println("容量是" +
buffer.capacity() + " 位置是" +
buffer.position() + " 限制是" + buffer.limit());
//切换
buffer.flip();
//容量是10 位置是0 限制是3
System.out.println("容量是" +
buffer.capacity() + " 位置是" +
buffer.position() + " 限制是" + buffer.limit());
}
Channel(通道)
Channel的概述
Channel
(通道):
Channel
是⼀个对象,可以通过它读取和写⼊数据,
可以把它看做是
IO
中的流,不同的是:
Channel
是双向的, Channel
对象既可以调⽤读取的⽅法
,
也可以调⽤写出的⽅法 。
Channel
的分类
- FileChannel:从⽂件读取数据的 输⼊流和输出流
- DatagramChannel:读写UDP⽹络协议数据 DatagramPackge
- SocketChannel:读写TCP⽹络协议数据 Socket
- ServerSocketChannel:可以监听TCP连接 ServerSocket
FileChannel类的基本使用
获取FileChannel类的对象
- java.nio.channels.FileChannel (抽象类):⽤于读、写⽂件的通道
-
FileChannel 是抽象类,我们可以通过 FileInputStream 和FileOutputStream的 getChannel() ⽅法⽅便的获取⼀个它的⼦类对象
FileInputStream fi = new FileInputStream ( new File ( src ));FileOutputStream fo = new FileOutputStream ( new File ( dst ));// 获得传输通道 channelFileChannel inChannel = fi . getChannel ();FileChannel outChannel = fo . getChannel ();
使⽤
FileChannel
类完成⽂件的复制
public class Test06 {
public static void main(String[] args) throws IOException {
//Channel概述
/*
* FileCh annel类的基本使用
* */
//使用FileChannel类完成文件的复制
FileInputStream fis = new FileInputStream("C:\\Users\\25120\\IdeaProjects\\Test\\Hello.txt");
FileOutputStream fos = new FileOutputStream("C:\\Users\\25120\\IdeaProjects\\Test\\Hello1.txt");
//获得FileChannel管道对象
FileChannel c1 = fis.getChannel();
FileChannel c2 = fis.getChannel();
//创建ByteBuffer数组
ByteBuffer b = ByteBuffer.allocate(1000);
//循环读取数据
while ((c1.read(b))!=-1){
//重置postion为0,limit为postion的位置
b.flip();
//写出数据
c2.write(b);
//还原
b.clear();
}
//释放资源
c2.close();
c1.close();
fos.close();
fis.close();
}
}
FileChannel 结合 MappedByteBuffer 实现⾼效读 写
MappedByteBuffer
类的概述
-
上例直接使⽤ FileChannel 结合 ByteBuffer 实现的管道读写,但并不能提⾼⽂件的读写效率。
-
ByteBuffer 有个抽象⼦类: MappedByteBuffer ,它可以将⽂件直接映射⾄内存,把硬盘中的读写变成内存中的读 写 , 所以可以提⾼⼤⽂件的读写效率。
- 可以调⽤FileChannel的map()⽅法获取⼀个MappedByteBuffer,map()⽅法的原型MappedByteBuffer map(MapMode mode, longposition, long size);
说明:将节点中从
position
开始的
size
个字节映射到返回
的
MappedByteBuffer
中。
ServerSocketChannel和SocketChannel创建连接
1.客户端:SocketChannel类⽤于连接的客户端,它相当于:Socket
SocketChannel socket = SocketChannel.open()
2.调⽤SocketChannel的实例⽅法 connect(SocketAddress add)连接服务器:
socket . connect ( new InetSocketAddress ( "localhost" , 8888 ));
客户端代码:
public class Test07 {
public static void main(String[] args) throws IOException {
//ServerSocketChannel和SocketChannel创建连接
//1.先调用SocketChannel的open()方法打开通道
//SocketChannel soket = SocketChannel.open()
//2.调用SocketChannel的实例方法connect(SocketAddress add)连接服务器
//socket.connect(new InetSocketAddress("localhost",8888))
//示例:客户端连接服务器
SocketChannel socket = SocketChannel.open();
socket.connect(new InetSocketAddress("127.0.0.1",8888));
}
}
服务器端:ServerSocketChannel类⽤于连接的服务器端,它相当于:ServerSocket。
调⽤
ServerSocketChannel
的静态⽅法
open()
就可以获得ServerSocketChannel对象
,
但并没有指定端⼝号
,
必须通过其套接字的bind
⽅法将其绑定到特定地址,才能接受连接。
ServerSocketChannel serverChannel = ServerSocketChannel . open ()
调⽤
ServerSocketChannel
的实例⽅法bind(SocketAddress add):绑定本机监听端⼝,准备接受连接。
构造⽅法:
InetSocketAddress(int port)
:指定本机监听 端⼝
serverChannel . bind ( new InetSocketAddress ( 8888 ));
调⽤
ServerSocketChannel
的实例⽅法
accept()
:等待连接。
SocketChannel accept = serverChannel . accept ();System . out . println ( " 后续代码 ..." );
服务器代码(默认-阻塞默认):
我们可以通过ServerSocketChannel的 configureBlocking(boolean b)⽅法设置accept()是否阻 塞
public class Test08 {
public static void main(String[] args) throws IOException, InterruptedException {
//ServerSocketChanne创建连接
//服务器端:ServerSocketChannel类用于连接的服务器
//绑定本机监听端口,准备接受连接
ServerSocketChannel socketChan1 = ServerSocketChannel.open();
//socketChan1.bind(new InetSocketAddress(8888));
//调用ServerSocketChannel的实例方法accept();等待链接
//System.out.println("服务器等待客户端连接");
//SocketChannel accept = socketChan1.accept();
//System.out.println("后续代码.....");
socketChan1.bind(new InetSocketAddress(8888));
//设置非阻塞连接
socketChan1.configureBlocking(false);//写成false叫非阻塞,写成true叫阻塞
while (true) {
//获取客户端连接
SocketChannel sc = socketChan1.accept();
if (sc != null) {
//不等于null说明连接上了客户端
System.out.println("连接上了,,,");
break;
}//读取数据操作
else {
//没连接上客户端
System.out.println("打会游戏");
Thread.sleep(2000);
}
}
}
}
NIO网络编程收发信息
使用ServerSocketChannel代替之前的ServerSocket,来完成网络编程的收发数据
书写服务器代码
public class Test09 {
public static void main(String[] args) throws IOException {
//NIO网络编程收发信息
//书写服务器代码
//创建对象
ServerSocketChannel ssc =ServerSocketChannel.open();
//服务器绑定端口
ssc.bind(new InetSocketAddress(6666));
//连接上客户端
SocketChannel sc = ssc.accept();
//服务器端接受数据
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//接收数据
int len = buffer.remaining();
//打印结构
System.out.println(new String(buffer.array(),0,len));
//关闭资源
sc.close();
}
}
书写客户端代码:
public class Test10 {
public static void main(String[] args) throws IOException {
//书写客户端
//创建
SocketChannel sc = SocketChannel.open();
//连接服务器
sc.connect(new InetSocketAddress("127.0.0.1",6666));
//客户端发数据
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//数组中添加数据
buffer.put("你好呀数据库".getBytes());
//切换
buffer.flip();
//发出数据
sc.write(buffer);
//关流
sc.close();
}
}