Java NIO系列教程三

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 Grafana 版,10个用户账号 1个月
简介: ​ 今天主要给大家介绍的是Buffer的基本使用这个也是NIO里面最总要的概率之一,里面的操作也是有一些复杂的同时也是需要大家必须要重点掌握的知识点,同时也介绍了一下Selector的用法下一篇文章我们将为大家介绍Pipe管道以及FileLock文件锁这也是NIO里面最后的一分部内容了。

NIO Buffer缓冲和Selector

一、Buffer

  • 通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中
  • 在 NIO 库中,所有数据都是用缓冲区处理的

1.1 基本用法

使用 Buffer 读写数据,四个步骤

​ (1)写入数据到 Buffer

​ (2)调用 flip()方法

​ (3)从 Buffer 中读取数据

​ (4)调用 clear()方法或者 compact()方法

读数据的完整例子

@Test
public void buffer01() throws Exception {
   
    //FileChannel
    RandomAccessFile aFile =
            new RandomAccessFile("d://opencoder.txt","rw");
    FileChannel channel = aFile.getChannel();
    //创建buffer大小
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    //读
    int bytesRead = channel.read(buffer);
    while(bytesRead != -1) {
   
        //read模式
        buffer.flip();

        while(buffer.hasRemaining()) {
   
            System.out.println((char)buffer.get());
        }
        buffer.clear();
        bytesRead = channel.read(buffer);
    }
    aFile.close();
}

写数据的完整例子

//创建buffer
IntBuffer buffer = IntBuffer.allocate(8);

//buffer放
for (int i = 0; i < buffer.capacity(); i++) {
   
    int j = 2*(i+1);
    buffer.put(j);
}
//重置缓冲区
buffer.flip();
//获取
while(buffer.hasRemaining()) {
   
    int value = buffer.get();
    System.out.println(value+" ");
}

1.2 三个重要属性

Buffer还有三个属性:Capacity、Position、limit

  1. capacity 内存块固定大小值

    一旦 Buffer 满了,需要将其清空,才能写入

  2. position

    写入数据的时候,初始值为0,慢慢会往下移动,最大值为-1表示满了
    读入数据的时候, position=2 时表示已开始读入了 3 个 byte。ByteBuffer.flip()切换到读模式时 position 会被重置为 0

  3. limit

    limit 表示可对 Buffer 最多写入或者读取多少个数据

1.3 核心方法的使用

分配字节数据
​ 要想获得一个 Buffer 对象首先要进行分配。 每一个 Buffer 类都有一个 allocate 方法 比如:

ByteBuffer buf = ByteBuffer.allocate(48);

写数据的两种方式

​ (1)从 Channel 写到 Buffer。

​ (2)通过 Buffer 的 put()方法写到 Buffer 里。

int bytesRead = inChannel.read(buf); //read into buffer

buf.put(127);

读写模式转换 flip()

​ flip 方法将 Buffer 从写模式切换到读模式。调用 flip()方法会将 position 设回 0,并将 limit 设置成之前 position 的值。

从 Buffer 中读取数据

​ 有两种方式:

​ (1)从 Buffer 读取数据到 Channel。

​ (2)使用 get()方法从 Buffer 中读取数据

//read from buffer into channel.
int bytesWritten = inChannel.write(buf);
byte aByte = buf.get();

1.4 其他方法

​ rewind()将 position 设回 0

​ clear()与 compact()都是清空数据,但有所区别:
​ 一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear()或 compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面

​ mark()与 reset()一个标记一个回到标记点
​ 通过调用 Buffer.mark()方法,可以标记 Buffer 中的一个特定 position。之后可以通过调用 Buffer.reset()方法恢复到这个 position

1.5 缓冲区的分类

缓冲区可以分为四种类型分别为:缓冲区分片、只读缓冲区、直接缓冲区、内存映射文件 I/O

1.6 缓冲区分片

​ 根据现有的缓冲区对象来创建一个子缓冲区,即在现有缓冲区上切出一片来作为一个新的缓冲区,但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的,也就是说,子缓冲区相当于是现有缓冲区的一个视图窗口。调用 slice()方法可以创建一个子缓冲区.

完整演示代码如下:

//缓冲区分片
@Test
public void b01() {
   
    ByteBuffer buffer = ByteBuffer.allocate(10);

    for (int i = 0; i < buffer.capacity(); i++) {
   
        buffer.put((byte)i);
    }

    //创建子缓冲区
    buffer.position(3);
    buffer.limit(7);
    ByteBuffer slice = buffer.slice();

    //改变子缓冲区内容
    for (int i = 0; i <slice.capacity() ; i++) {
   
        byte b = slice.get(i);
        b *=10;
        slice.put(i,b);
    }

    buffer.position(0);
    buffer.limit(buffer.capacity());

    while(buffer.remaining()>0) {
   
        System.out.println(buffer.get());
    }
}

1.7 只读缓冲区

​ 可以读取它们,但是不能向它们写入数据。可以通过调用缓冲区的 asReadOnlyBuffer()方法与原缓冲区共享数据,只不过它是只读的。
​ 如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化。

完整演示代码如下:

//只读缓冲区
@Test
public void b02() {
   
    ByteBuffer buffer = ByteBuffer.allocate(10);

    for (int i = 0; i < buffer.capacity(); i++) {
   
        buffer.put((byte)i);
    }

    //创建只读缓冲区
    ByteBuffer readonly = buffer.asReadOnlyBuffer();

    for (int i = 0; i < buffer.capacity(); i++) {
   
        byte b = buffer.get(i);
        b *=10;
        buffer.put(i,b);
    }

    readonly.position(0);
    readonly.limit(buffer.capacity());

    while (readonly.remaining()>0) {
   
        System.out.println(readonly.get());
    }
}

1.8 直接缓冲区

加快 I/O 速度要分配直接缓冲区,需要调用 allocateDirect()方法,而不是 allocate()方法,使用方式与普通缓冲区并无区别
完整演示代码如下:

//直接缓冲区
@Test
public void b03() throws Exception {
   
    String infile = "d://opencoder1.txt";
    FileInputStream fin = new FileInputStream(infile);
    FileChannel finChannel = fin.getChannel();

    String outfile = "d://opencoder12.txt";
    FileOutputStream fout = new FileOutputStream(outfile);
    FileChannel foutChannel = fout.getChannel();

    //创建直接缓冲区
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

    while (true) {
   
        buffer.clear();
        int r = finChannel.read(buffer);
        if(r == -1) {
   
            break;
        }
        buffer.flip();
        foutChannel.write(buffer);
    }
}

1. 9 内存映射文件 I/O

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快的多

static private final int start = 0;
static private final int size = 1024;

//内存映射文件io
 @Test
 public void b04() throws Exception {
   
     RandomAccessFile raf = new RandomAccessFile("d://opencoder1.txt", "rw");
     FileChannel fc = raf.getChannel();

     MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, start, size);

     mbb.put(0, (byte) 97);
     mbb.put(1023, (byte) 122);
     raf.close();
 }

二、Selector、

  • 通过一个Selector可以检查更多的通道
  • 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销

可选择通道:

  • 不是所有的 Channel 都可以被 Selector 复用的。判断他是否继承了一个抽象类 SelectableChannel。如果继承了SelectableChannel,则可以被复用,否则不能
  • 一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。通道和选择器之间的关系,使用注册的方式完成。SelectableChannel 可以被注册到Selector 对象上,在注册的时候,需要指定通道的哪些操作,是 Selector 感兴趣的

Channel 注册到 Selector

Channel.register(Selector sel,int ops)一个通道注册到一个选择器时。
第一个参数,指定通道要注册的选择器。
第二个参数指定选择器需要查询的通道操作
供选择器查询的通道

  • 可读 : SelectionKey.OP_READ
  • 可写 : SelectionKey.OP_WRITE
  • 连接 : SelectionKey.OP_CONNECT
  • 接收 : SelectionKey.OP_ACCEPT

选择键

  1. Channel 注册到后,并且一旦通道处于某种就绪的状态,就可以被选择器查询到。这个工作,使用选择器 Selector 的 select()方法完成。select 方法的作用,对感兴趣的通道操作,进行就绪状态的查询。
  2. Selector 可以不断的查询 Channel 中发生的操作的就绪状态。并且挑选感兴趣的操作就绪状态。一旦通道有操作的就绪状态达成,并且是 Selector 感兴趣的操作,就会被 Selector 选中,放入选择键集合中

2.1 核心方法

  1. 创建一个 Selector 对象

    // 1、获取 Selector 选择器
    Selector selector = Selector.open();
    
  2. 注册 Channel 到 Selector

    // 1、获取 Selector 选择器
    Selector selector = Selector.open();
    // 2、获取通道
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    // 3.设置为非阻塞
    serverSocketChannel.configureBlocking(false);
    // 4、绑定连接
    serverSocketChannel.bind(new InetSocketAddress(9999));
    // 5、将通道注册到选择器上,并制定监听事件为:“接收”事件
    serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
    
  3. 轮询查询就绪操作

    (1)通过 Selector 的 select()方法,可以查询出已经就绪的通道操作,这些就绪的状态集合,包存在一个元素是 SelectionKey 对象的 Set 集合中。

    (2)下面是 Selector 几个重载的查询 select()方法:

    • select():阻塞到至少有一个通道在你注册的事件上就绪了。
    • select(long timeout):和 select()一样,但最长阻塞事件为 timeout 毫秒。
    • selectNow():非阻塞,只要有通道就绪就立刻返回。

    select()方法返回的 int 值,表示有多少通道已经就绪,更准确的说,是自前一次 select方法以来到这一次 select 方法之间的时间段上,有多少通道变成就绪状态。

    如:首次调用 select()方法,如果有一个通道变成就绪状态,返回了 1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回 1。如果对第一个就绪的channel 没有做任何操作,现在就有两个就绪的通道,但在每次 select()方法调用之间,只有一个通道就绪了。一旦调用 select()方法,并且返回值不为 0 时,在 Selector 中有一个 selectedKeys()方法,用来访问已选择键集合,迭代集合的每一个选择键元素,根据就绪操作的类型,完成对应的操作。

    Set selectedKeys = selector.selectedKeys();
    Iterator keyIterator = selectedKeys.iterator();
    while(keyIterator.hasNext()) {
         
     SelectionKey key = keyIterator.next();
     if(key.isAcceptable()) {
         
     // a connection was accepted by a ServerSocketChannel.
     } else if (key.isConnectable()) {
         
     // a connection was established with a remote server.
     } else if (key.isReadable()) {
         
     // a channel is ready for reading
     } else if (key.isWritable()) {
         
     // a channel is ready for writing
     }
     keyIterator.remove();
    }
    

2.2 完成的一个客户端和服务器端的实战例子

服务端代码:

//服务端代码
@Test
public void serverDemo() throws Exception {
   
    //1 获取服务端通道
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

    //2 切换非阻塞模式
    serverSocketChannel.configureBlocking(false);

    //3 创建buffer
    ByteBuffer serverByteBuffer = ByteBuffer.allocate(1024);

    //4 绑定端口号
    serverSocketChannel.bind(new InetSocketAddress(8080));

    //5 获取selector选择器
    Selector selector = Selector.open();

    //6 通道注册到选择器,进行监听
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    //7 选择器进行轮询,进行后续操作
    while(selector.select()>0) {
   
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        //遍历
        Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
        while(selectionKeyIterator.hasNext()) {
   
            //获取就绪操作
            SelectionKey next = selectionKeyIterator.next();
            //判断什么操作
            if(next.isAcceptable()) {
   
                //获取连接
                SocketChannel accept = serverSocketChannel.accept();

                //切换非阻塞模式
                accept.configureBlocking(false);

                //注册
                accept.register(selector,SelectionKey.OP_READ);

            } else if(next.isReadable()) {
   
                SocketChannel channel = (SocketChannel) next.channel();

                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

                //读取数据
                int length = 0;
                while((length = channel.read(byteBuffer))>0) {
   
                    byteBuffer.flip();
                    System.out.println(new String(byteBuffer.array(),0,length));
                    byteBuffer.clear();
                }

            }

            selectionKeyIterator.remove();
        }
    }
}

客户端代码:

//客户端代码
@Test
public void clientDemo() throws Exception {
   
    //1 获取通道,绑定主机和端口号
    SocketChannel socketChannel =
            SocketChannel.open(new InetSocketAddress("127.0.0.1",8080));

    //2 切换到非阻塞模式
    socketChannel.configureBlocking(false);

    //3 创建buffer
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

    //4 写入buffer数据
    byteBuffer.put(new Date().toString().getBytes());

    //5 模式切换
    byteBuffer.flip();

    //6 写入通道
    socketChannel.write(byteBuffer);

    //7 关闭
    byteBuffer.clear();
}

public static void main(String[] args) throws IOException {
   
    //1 获取通道,绑定主机和端口号
    SocketChannel socketChannel =
            SocketChannel.open(new InetSocketAddress("127.0.0.1",8080));

    //2 切换到非阻塞模式
    socketChannel.configureBlocking(false);

    //3 创建buffer
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

    Scanner scanner = new Scanner(System.in);
    while(scanner.hasNext()) {
   
        String str = scanner.next();

        //4 写入buffer数据
        byteBuffer.put((new Date().toString()+"--->"+str).getBytes());

        //5 模式切换
        byteBuffer.flip();

        //6 写入通道
        socketChannel.write(byteBuffer);

        //7 关闭
        byteBuffer.clear();
    }

}
}

总结

​ 今天主要给大家介绍的是Buffer的基本使用这个也是NIO里面最总要的概率之一,里面的操作也是有一些复杂的同时也是需要大家必须要重点掌握的知识点,同时也介绍了一下Selector的用法下一篇文章我们将为大家介绍Pipe管道以及FileLock文件锁这也是NIO里面最后的一分部内容了。

目录
相关文章
|
17天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(二十九)-java+ selenium自动化测试- Actions的相关操作上篇(详解教程)
【4月更文挑战第21天】本文介绍了Selenium中处理特殊测试场景的方法,如鼠标悬停。Selenium的Actions类提供了鼠标悬停功能,用于模拟用户在网页元素上的悬停行为。文中通过实例展示了如何使用Actions悬停并展开下拉菜单,以及在搜索时选择自动补全的字段。代码示例包括了打开百度首页,悬停在“更多”元素上显示下拉菜单并点击“音乐”,以及在搜索框输入关键词并自动补全的过程。
38 0
|
10天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十六)-java+ selenium自动化测试-单选和多选按钮操作-番外篇(详解教程)
【4月更文挑战第28天】本文简要介绍了自动化测试的实战应用,通过一个在线问卷调查(&lt;https://www.sojump.com/m/2792226.aspx/&gt;)为例,展示了如何遍历并点击问卷中的选项。测试思路包括找到单选和多选按钮的共性以定位元素,然后使用for循环进行点击操作。代码设计方面,提供了Java+Selenium的示例代码,通过WebDriver实现自动答题。运行代码后,可以看到控制台输出和浏览器的相应动作。文章最后做了简单的小结,强调了本次实践是对之前单选多选操作的巩固。
23 0
|
1天前
|
JavaScript 前端开发 Java
《手把手教你》系列技巧篇(四十)-java+ selenium自动化测试-JavaScript的调用执行-下篇(详解教程)
【5月更文挑战第4天】本文介绍了如何使用JavaScriptExecutor在自动化测试中实现元素高亮显示。通过创建并执行JS代码,可以改变元素的样式,例如设置背景色和边框,以突出显示被操作的元素。文中提供了一个Java示例,展示了如何在Selenium中使用此方法,并附有代码截图和运行效果展示。该技术有助于跟踪和理解测试过程中的元素交互。
6 0
|
2天前
|
Web App开发 JavaScript 前端开发
《手把手教你》系列技巧篇(三十九)-java+ selenium自动化测试-JavaScript的调用执行-上篇(详解教程)
【5月更文挑战第3天】本文介绍了如何在Web自动化测试中使用JavaScript执行器(JavascriptExecutor)来完成Selenium API无法处理的任务。首先,需要将WebDriver转换为JavascriptExecutor对象,然后通过executeScript方法执行JavaScript代码。示例用法包括设置JS代码字符串并调用executeScript。文章提供了两个实战场景:一是当时间插件限制输入时,用JS去除元素的readonly属性;二是处理需滚动才能显示的元素,利用JS滚动页面。还给出了一个滚动到底部的代码示例,并提供了详细步骤和解释。
25 10
|
3天前
|
JavaScript 前端开发 测试技术
《手把手教你》系列技巧篇(三十八)-java+ selenium自动化测试-日历时间控件-下篇(详解教程)
【5月更文挑战第2天】在自动化测试过程中,经常会遇到处理日期控件的点击问题。宏哥之前分享过一种方法,但如果输入框是`readonly`属性,这种方法就无法奏效了。不过,通过修改元素属性,依然可以实现自动化填写日期。首先,定位到日期输入框并移除`readonly`属性,然后使用`sendKeys`方法输入日期。这样,即使输入框设置了`readonly`,也能成功处理日期控件。
23 1
|
4天前
|
Java 测试技术 Python
《手把手教你》系列技巧篇(三十七)-java+ selenium自动化测试-日历时间控件-上篇(详解教程)
【5月更文挑战第1天】该文介绍了使用Selenium自动化测试网页日历控件的方法。首先,文章提到在某些Web应用中,日历控件常用于选择日期并筛选数据。接着,它提供了两个实现思路:一是将日历视为文本输入框,直接输入日期;二是模拟用户交互,逐步选择日期。文中给出了JQueryUI网站的一个示例,并展示了对应的Java代码实现,包括点击日历、选择日期等操作。
18 0
|
10天前
|
Java 测试技术 项目管理
Java基础教程(22)-构建工具Maven的基本使用
【4月更文挑战第22天】Maven是Java项目管理及构建工具,简化构建、测试、打包和部署等任务。遵循约定优于配置原则,核心是`pom.xml`配置文件,用于管理依赖和项目信息。安装涉及下载、解压、配置环境变量。在IDEA中使用Maven创建项目,通过`pom.xml`添加依赖和管理版本。常用命令包括`clean`、`compile`、`test`、`package`、`install`和`deploy`。IDEA支持直接执行这些命令。
|
10天前
|
NoSQL Java 关系型数据库
Java基础教程(21)-Java连接MongoDB
【4月更文挑战第21天】MongoDB是开源的NoSQL数据库,强调高性能和灵活性。Java应用通过MongoDB Java驱动与之交互,涉及MongoClient、MongoDatabase、MongoCollection和Document等组件。连接MongoDB的步骤包括:配置连接字符串、创建MongoClient、选择数据库和集合。伪代码示例展示了如何建立连接、插入和查询数据。
|
11天前
|
存储 前端开发 测试技术
《手把手教你》系列技巧篇(三十五)-java+ selenium自动化测试-单选和多选按钮操作-下篇(详解教程)
【4月更文挑战第27天】本文介绍了使用Java+Selenium进行Web自动化测试时,如何遍历和操作多选按钮的方法。文章分为两个部分,首先是一个本地HTML页面的示例,展示了多选按钮的HTML代码和页面效果,并详细解释了遍历多选按钮的思路:找到所有多选按钮的共同点,通过定位这些元素并放入list容器中,然后使用for循环遍历并操作。 第二部分介绍了在JQueryUI网站上的实战,给出了被测网址,展示了代码设计,同样使用了findElements()方法获取所有多选按钮并存储到list中,然后遍历并进行点击操作。最后,文章对整个过程进行了小结,并推荐了作者的其他自动化测试教程资源。
19 0
|
11天前
|
Java 关系型数据库 MySQL
Java基础教程(20)-Java连接mysql数据库CURD
【4月更文挑战第19天】MySQL是流行的关系型数据库管理系统,支持SQL语法。在IDEA中加载jar包到项目类路径:右击项目,选择“Open Module Settings”,添加库文件。使用JDBC连接MySQL,首先下载JDBC驱动,然后通过`Class.forName()`加载驱动,`DriverManager.getConnection()`建立连接。执行CRUD操作,例如创建表、插入数据和查询,使用`Statement`或`PreparedStatement`,并确保正确关闭数据库资源。