NIO

写在前面:

第一次看到NIO。还是在一道面试题上。问 BIO、NIO、AIO有什么区别。当时百度了一下得到一个简单大概的回答。BIO:同步阻塞IO NIO:同步非阻塞IO AIO:异步非阻塞IO。

里面具体三者的对应关系这篇文章讲的还可以,推荐看看。https://juejin.im/entry/598da7d16fb9a03c42431ed3

BIO

同步阻塞I/O

不适合高并发

NIO

同步非阻塞

多路复用器通过不断轮询各个连接的状态。有流可读或者可写时,应用程序才需要去处理它。

AIO

异步非阻塞IO

NIO需要使用者线程不停的轮询IO对象,来确定是否有数据准备好可以读了,而AIO则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了。

自己稍微总结了一下。至少面试的时候能接的上。不至于太尴尬。

当我学完了NIO后,再回过头来看什么是BIO、AIO就显得轻松了许多。废话不多,开始吧。老规矩,尚硅谷,看视频,做笔记,作总结。

百度网盘资源:

链接https://pan.baidu.com/s/1Erfz3MwrQrdBS-QYBrOUMw 
提取码cy4w
  1. Java NIO 简介

  2. Java NIO 与 IO 的主要区别

  3. 缓冲区(Buffer)和通道(Channel)

  4. NIO 的非阻塞式网络通信

  5. 选择器(Selector)

  6. 管道(Pipe)

  7. Java NIO2 (Path、Paths 与 Files )

  8. UDP TCP

一、Java NIO 简介

简介:Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。

二、Java NIO 与 IO 的主要区别

IO

NIO

面向流

面向缓冲区

阻塞IO

非阻塞IO

/无

选择器

有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。

NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。

三、缓冲区(Buffer)和通道(Channel)

Java NIO系统的核心在于:通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用 NIO 系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。

简而言之:Channel负责传输, Buffer负责存储。

缓冲区

含义

一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是Buffer抽象类的子类。Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写 入通道中的。

缓冲区的基本属性

容量(capacity):表示 Buffer最大数据容量

限制(limit):表示缓冲区中可以操作的数据大小

位置(position):表示缓冲区中正在操作的数据位置

标记(mark)与重置 (reset):mark表示记录当前postion位置,可以通过reset恢复到mark位置.

标记、位置、限制、容量遵守以下不变式:0 <= mark <=position <= limit <= capacity

和缓冲区有关的几个方法

  • allocate创建一个容量为 capacity 的XxxBuffer 对象

  • put()存入数据到缓冲区。

  • get()获取缓冲区中的数据。

  • flip()切换到读数据模式。

  • rewind()可重复读数据

  • clear()清空缓冲区并返回对缓冲区的引用

直接与非直接缓冲区


方法

位置

非直接缓冲区

allocate()

JVM中

直接缓冲区

(只有ByteBuffer支持)

allocateDirect()

FileChannel  的 map() 方法

物理内存中(效率高,消耗大)

通道

含义

由java.nio.channels 包定义的。Channel 表示 IO源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel只能与 Buffer进行交互

主要实现类

FileChannel:用于读取、写入、映射和操作文件的通道。

------------SelectableChannel

DatagramChannel:通过UDP读写网络中的数据通道。

SocketChannel:通过TCP读写网络中的数据。

ServerSocketChannel:可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel。

获取通道

①对支持通道的对象调用getChannel()方法

本地IO

网络

FileInputStream/FileOutputStream

Socket

RandomAccessFile

ServerSocket/DatagramSocket

//利用通道完成文件的复制(非直接缓冲区)
FileInputStream fis = null;
FileOutputStream fos = null;
FileChannel fisChannel = null;
FileChannel fosChannel = null;
try {
    fis = new FileInputStream("C:\\Desktop\\简历.rar");
    fos = new FileOutputStream("C:\\Desktop\\简历2.rar");


    //1.获取通道
    fisChannel = fis.getChannel();
    fosChannel = fos.getChannel();


    //2.分配指定大小的缓冲区
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);


    //3.将通道中的数据存入缓冲区
    while (fisChannel.read(byteBuffer)!=-1){
        byteBuffer.flip();//切换到读模式
        //4.将缓冲区中的数据写入通道
        fosChannel.write(byteBuffer);
        byteBuffer.clear();
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if(fisChannel!=null){
        fisChannel.close();
    }
    if(fosChannel!=null){
        fosChannel.close();
    }
    if(fis!=null){
        fis.close();
    }
    if(fos!=null){
        fos.close();
    }
}

②在JDK1.7后的NIO.2中针对各个通道提供了静态方法open()

FileChannel inChannel = FileChannel.open(Paths.get("C:\\Users\\简历.rar"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("C:\\Users\\简历3.rar"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
//inChannel.transferTo(0,inChannel.size(),outChannel);
//创建了两个 直接缓冲区 可以相互进行读写
MappedByteBuffer inMap = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMap = outChannel.map(FileChannel.MapMode.READ_WRITE,0, inChannel.size());
//ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
//直接对缓冲区进行数据的读写操作
byte[] bytes = new byte[inMap.limit()];
inMap.get(bytes);
outMap.put(bytes);

③在JDK1.7后的NIO.2的Files工具类的newByteChannel()

通道的数据传输

  • 将 Buffer 中数据写入 Channel

  • 从 Channel 读取数据到 Buffer

  • transferTo将数据从源通道传输到其他 Channel 中

  • transferFrom将数据从源通道传输到其他 Channel 中

FileChannel inChannel = FileChannel.open(Paths.get("C:\\Users\\Desktop\\简历.rar"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("C:\\Users\\Desktop\\简历2.rar"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);


inChannel.transferTo(0,inChannel.size(),outChannel);
outChannel.transferFrom(inChannel,0,inChannel.size());

分散(Scatter)和聚集(Gather)

分散读取(ScatteringReads)是指从Channel中读取的数据“分散”到多个Buffer中

聚集写入(GatheringWrites)是指将多个Buffer中的数据“聚集”到Channel

RandomAccessFile raf = new RandomAccessFile("templates/1.txt","rw");
//1.获取通道
FileChannel channel = raf.getChannel();
//2.分配指定大小的缓冲区
ByteBuffer allocate1 = ByteBuffer.allocate(100);
ByteBuffer allocate2 = ByteBuffer.allocate(1024);


ByteBuffer[] byteBuffers = {allocate1,allocate2};
//将通道中的数据分散到多个缓冲区中
channel.read(byteBuffers);


for (ByteBuffer byteBuffer : byteBuffers) {
    //每个缓冲区进行flip读模式
    byteBuffer.flip();
}
System.out.println(new String(byteBuffers[0].array(),0,byteBuffers[0].limit()));
System.out.println("====================================================================");
System.out.println(new String(byteBuffers[1].array(),0,byteBuffers[1].limit()));


RandomAccessFile randomAccessFile = new RandomAccessFile("/templates/2.txt","rw");
FileChannel channel1 = randomAccessFile.getChannel();
//将多个缓冲区中的数据聚集到通道
channel1.write(byteBuffers);

四、NIO 的非阻塞式网络通信


阻塞式网络通信

//客户端
@Test
public void client() throws IOException {
    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",10086));
    FileChannel fileChannel = FileChannel.open(Paths.get("/templates/1.jpg"), StandardOpenOption.READ);
    ByteBuffer allocate = ByteBuffer.allocate(1024);
    while (fileChannel.read(allocate)!=-1){
        allocate.flip();
        socketChannel.write(allocate);
        allocate.clear();
    }
    //socketChannel.shutdownOutput();
    //接收服务端的反馈
    int len = 0;
    while ((len=socketChannel.read(allocate))!=-1){
        allocate.flip();
        System.out.println(new String(allocate.array(),0,len));
        allocate.clear();
    }
    socketChannel.close();
    fileChannel.close();
}
//服务端
@Test
public void server() throws IOException {
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(10086));
    SocketChannel accept = serverSocketChannel.accept();
    FileChannel channel = FileChannel.open(Paths.get("/templates/111.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
    ByteBuffer allocate = ByteBuffer.allocate(1024);
    while (accept.read(allocate)!=-1){
        allocate.flip();
        channel.write(allocate);
        allocate.clear();
    }
    //发送给客户端
    allocate.put("服务端接收成功".getBytes());
    allocate.flip();
    accept.write(allocate);
    serverSocketChannel.close();
    accept.close();
    channel.close();
}
先启动服务端,再启动客户端。发现客户端没有接收到服务端返回的“服务端接收成功”

很显然 发生了线程阻塞。

解决办法:socketChannel.shutdownOutput();


非阻塞式网络通信TCP

public static void main(String[] args) throws IOException {
    //1.创建客户端通道
    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
    //2.切换成 非阻塞模式
    socketChannel.configureBlocking(false);
    //socketChannel.shutdownOutput();
    //3.创建缓冲区
    ByteBuffer allocate = ByteBuffer.allocate(1024);
    Scanner scanner = new Scanner(System.in);
    while (scanner.hasNext()){
        String next = scanner.next();
        //4.发送数据给服务端
        allocate.put((LocalDateTime.now().toString()+"\n"+next).getBytes());
        allocate.flip();
        socketChannel.write(allocate);
        allocate.clear();
    }
    socketChannel.close();
}
//服务端
@Test
public void server() throws IOException {
    //1.创建通道 并绑定端口号
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(9999));
    //2.切换 非阻塞模式
    serverSocketChannel.configureBlocking(false);
    //3.创建 选择器
    Selector selector = Selector.open();
    //4.设置对通道的监听类型(读、写、连接、接收)------Ps:将通道注册到了选择器上
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    //5.轮询式的 获取选择器上已经“准备就绪”的事件---------监控所有注册的Channel
    while (selector.select()>0){
        //6.获取当前选择器中所有注册的"选择键"(已就绪的监听事件)
        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        while (iterator.hasNext()){
            SelectionKey selectionKey = iterator.next();


            if(selectionKey.isAcceptable()){
                SocketChannel socketChannel = serverSocketChannel.accept();
                //也要把这里客户端的通道切换到非阻塞式
                socketChannel.configureBlocking(false);
                //把这个通道也注册到选择器上
                socketChannel.register(selector,SelectionKey.OP_READ);
            }else if(selectionKey.isReadable()){
                //获取注册通道
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);


                int len = 0;
                while ((len=socketChannel.read(byteBuffer))>0){
                    byteBuffer.flip();
                    System.out.println(new String(byteBuffer.array(),0,len));
                    byteBuffer.clear();
                }
            }
            //取消选择键 SelectionKey
            iterator.remove();
        }
    }
}

解决办法:

1.客户端给设置了非阻塞模式

2.服务端也设置了非阻赛模式 还加了Selector选择器


非阻塞式网络通信UDP

//客户端
public static void main(String[] args) throws IOException {
    DatagramChannel datagramChannel = DatagramChannel.open();
    datagramChannel.configureBlocking(false);


    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);


    Scanner scanner = new Scanner(System.in);


    while (scanner.hasNext()){
        String string = scanner.next();
        byteBuffer.put(string.getBytes());
        byteBuffer.flip();
        datagramChannel.send(byteBuffer,new InetSocketAddress("127.0.0.1",8888));
        byteBuffer.clear();
    }
    datagramChannel.close();
}
//服务端
@Test
public void receive() throws IOException {
    DatagramChannel datagramChannel = DatagramChannel.open();
    datagramChannel.configureBlocking(false);
    datagramChannel.bind(new InetSocketAddress(8888));


    Selector selector = Selector.open();


    datagramChannel.register(selector, SelectionKey.OP_READ);


    while (selector.select()>0){
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> iterator = selectionKeys.iterator();
        while (iterator.hasNext()){
            SelectionKey selectionKey = iterator.next();
            if(selectionKey.isReadable()){
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                datagramChannel.receive(byteBuffer);
                byteBuffer.flip();
                System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit()));
                byteBuffer.clear();
            }
            iterator.remove();
        }
    }
}

解决办法:

1.客户端设置了非阻塞模式

2.服务端设置了非阻塞模式 还加了Selector选择器

五、选择器(Selector) 

含义选择器(Selector)是SelectableChannle对象的多路复用器,Selector可以同时监控多个Se
lectableChannel 的 IO状况,也就是说,利用 Selector 可使一个单独的线程管理多个
Channel。Selector 是非阻塞 IO 的核心。

创建SelectorSelector selector = Selector.open();
向选择器注册通道serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。
可以监听的事件类型(可使用 SelectionKey 的四个常量表示):

若注册时不止监听一个事件,则可以使用“位或”操作符连接。

Selector 的常用方法
SelectionKey表示 SelectableChannel和Selector之间的注册关系。每次向选择器注册通道时就会选
择一个事件(选择键)。

六、管道(Pipe)含义Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。
数据会被写到sink通道,从source通道读取。

//1.创建管道Pipe pipe = Pipe.open();//2.向管道中写数据Pipe.SinkChannel sinkChannel = pipe.sink();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);byteBuffer.put("我".getBytes());byteBuffer.flip();sinkChannel.write(byteBuffer);byteBuffer.clear();
//3.从管道中获取数据Pipe.SourceChannel sourceChannel = pipe.source();sourceChannel.read(byteBuffer);byteBuffer.flip();System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit()));
七、Java NIO2 (Path、Paths 与 Files)

随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理文件系统特性的支持,以至于我们称他们为 NIO.2。

Paths

java.nio.file.Path 接口代表一个平台无关的平台路径,描述了目录结构中文件的位置

Path

java.nio.file.Path 接口代表一个平台无关的平台路径,描述了目录结构中文件的位置

Files

java.nio.file.Files 用于操作文件或目录的工具类


八、UDP TCP

InetAddress

该类封装了一个IP地址 表示互联网中的ip地址

InetAddress

getLocalHost()

返回本地主机

InetAddress

getByName(String hostName)

传递主机名,获取ip地址

UDP协议

无连通协议。数据发送方和数据接收方不建立逻辑链接。不能保证数据完整性

//客户端
public static void main(String[] args) {
    InetAddress inetAddress = null;
    try {
        inetAddress = InetAddress.getByName("127.0.0.1");
        DatagramSocket datagramSocket = null;
        try {
            datagramSocket = new DatagramSocket();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        Scanner scanner = new Scanner(System.in);
        while (true){
            String string = scanner.next();
            byte[] bytes = string.getBytes();
            DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,inetAddress,6666);
            datagramSocket.send(datagramPacket);
        }
    } catch (UnknownHostException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

①创建DatagramPackage对象,封装数据,“接收的IP与端口“

②创建DatagramSocket

③调用DatagramSocket类方法send,发送数据包

④关闭数据包

//服务端
@Test
public void server() throws IOException {
    DatagramSocket datagramSocket = new DatagramSocket(6666);
    byte[] bytes = new byte[1024];
    while (true){
        DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
        datagramSocket.receive(datagramPacket);
        System.out.println(new String(bytes,0,datagramPacket.getLength()));
    }
}

TCP协议

面向连接协议。数据发送方和数据接收方要建立逻辑链接。可靠无差错数据传输。

TCP严格区分客户端和服务端。通信时,必须由客户端去连接服务器端才能通信,服务器端不可以主动连接客户端。且服务器端必须事先先启动。

TCP客户端Client java.net.Socket

构造方法 Socket(Stringhost服务器IP,int port端口号)

客户端与服务端数据交换,必须使用套接字对象。Socket中获取的IO流。

try (Socket socket = new Socket("127.0.0.1", 7777)) {
    Scanner scanner = new Scanner(System.in);
    //向客户端发送数据
    OutputStream outputStream = socket.getOutputStream();
    while (true){
        String s = scanner.next();
        outputStream.write(s.getBytes());
    }
} catch (UnknownHostException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

TCP服务端 java.net.ServerSocket

构造方法ServerSocket(intport端口号)

监听端口 ServerSocketserverSocket = new ServerSocket(8888);

Socker s =serverSocket.accept();

try {
    //监听端口
    ServerSocket serverSocket = new ServerSocket(7777);
    //必须要获得客户端的套接字对象
    Socket socket = serverSocket.accept();
    //接收客户端数据
    InputStream inputStream = socket.getInputStream();
    byte[] bytes = new byte[1024];
    while (true){
        int i = inputStream.read(bytes);
        System.out.println(new String(bytes,0,i));
    }
} catch (IOException e) {
    e.printStackTrace();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值