BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
BIO是面向流的

  1. ServerSocket server = new ServerSocket(8888);
  2. //这个方法会阻塞
  3. Socket socket = server.accept();

NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
NIO是面向缓冲区的

NIO的三个核心:**
1)buffer缓冲区
2)Channel管道
3)Selector选择器

buffer缓冲区和Channel管道

NIO是通过buffer缓冲区和channel管道配合使用来处理数据的

Channel管道比作铁路,buffer缓冲区比作成火车(运载着货物)
而我们的NIO就是通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理

也就是说:Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区
Channel—>运输
Buffer—>数据

buffer缓冲区的要点

缓冲区就是数组,用于存储不同类型的数据
Buffer是缓冲区的抽象类,根据数据类型的不同,提供了相对应的缓冲区
ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、CharBuffer、FloatBuffer、DoubleBuffer
其中最常用的就是ByteBuffer
缓冲区如何使用:读取缓冲区的数据get(),写数据到缓冲区put()
可以通过flip()方法切换为读模式
几个重要的属性:
1)Capacity容量
缓冲区能够容纳的数据元素的最大数量
容量在缓冲区创建时被设定,并且永远不能被改变(因为底层是数组)
2)Limit界限
写模式情况下,值与capacity一样
读模式下,表示缓冲区里的数据的总数,代表了当前缓冲区中一共有多少数据
3)Position位置
表示将要操作数据的位置
Position会自动由相应的 get( )和 put( )函数更新
4)Mark标记
一个备忘位置,用于记录上一次读写的位置
实例:

  1. public static void main(String[] args) {
  2. // 创建一个缓冲区
  3. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  4. // 看一下初始时4个核心变量的值
  5. System.out.println("初始时-->limit--->"+byteBuffer.limit());
  6. System.out.println("初始时-->position--->"+byteBuffer.position());
  7. System.out.println("初始时-->capacity--->"+byteBuffer.capacity());
  8. System.out.println("初始时-->mark--->" + byteBuffer.mark());
  9. System.out.println("--------------------------------------");
  10. // 添加一些数据到缓冲区中
  11. String s = "testNIO";
  12. byteBuffer.put(s.getBytes());
  13. // 看一下初始时4个核心变量的值
  14. System.out.println("put完之后-->limit--->"+byteBuffer.limit());
  15. System.out.println("put完之后-->position--->"+byteBuffer.position());
  16. System.out.println("put完之后-->capacity--->"+byteBuffer.capacity());
  17. System.out.println("put完之后-->mark--->" + byteBuffer.mark());
  18. }

结果:
FVTRDF{)CFX}T}$9%ML{VMQ.png

通过flip()方法切换为读模式

  1. //切换为读模式
  2. byteBuffer.flip();
  3. // 创建一个limit()大小的字节数组(因为就只有limit这么多个数据可读)
  4. byte[] bytes = new byte[byteBuffer.limit()];
  5. // 将读取的数据装进我们的字节数组中
  6. byteBuffer.get(bytes);
  7. // 输出数据
  8. System.out.println(new String(bytes, 0, bytes.length));

结果即为:testNIO
`HT4AV_%0_5`QO~@URDAR[8.png](https://cdn.nlark.com/yuque/0/2021/png/12773302/1625452820003-915b26a5-f286-41f3-bc7d-b6a907fa8cb0.png#align=left&display=inline&height=322&margin=%5Bobject%20Object%5D&name=%60HT4AV_%250_5%60QO~%40URDAR%5B8.png&originHeight=322&originWidth=847&size=48795&status=done&style=none&width=847)<br />也就是说<br />![M5}3H(58}UOWXULO]G2FSJ3.jpg

Channel管道要点

channel只负责数据的运输,不直接操作数据
java.nio.channel.Channel接口实现类:
1)本地IO
FileChannel
2)网络IO
SocketChannel、ServerSocketChannel、DategramChannel
获取管道:
通过getChannel()方法

实例:通过FileChannel配合缓冲区实现文件复制

  1. public static void main(String[] args) throws IOException {
  2. FileInputStream fis = null;
  3. FileOutputStream fos = null;
  4. try {
  5. fis = new FileInputStream("E:/test/a.txt");
  6. fos = new FileOutputStream("E:/test/b.txt");
  7. //获取管道
  8. FileChannel fisChannel = fis.getChannel();
  9. FileChannel fosChannel = fos.getChannel();
  10. //创建缓冲区
  11. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  12. //将管道中的数据存入缓冲区中
  13. while (fisChannel.read(byteBuffer)!=-1){
  14. //切换为读模式
  15. byteBuffer.flip();
  16. //将缓冲区的数据写入管道
  17. fosChannel.write(byteBuffer);
  18. //清空缓冲区
  19. byteBuffer.clear();
  20. }
  21. //关闭管道
  22. fisChannel.close();
  23. fosChannel.close();
  24. } catch (Exception e) {
  25. e.printStackTrace();
  26. }finally {
  27. if (fis!=null){
  28. fis.close();
  29. }
  30. if (fos!=null){
  31. fos.close();
  32. }
  33. }
  34. }