nio(一)——综述

背景

  JDK1.4的java.nio.*包中引入了新的JavaI/O类库,其目的在于 提高速度。实际上,旧的I/O包已经使用nio重新实现过。因此,即使不显式使用nio编写代码,也能从中受益。
  I/O的应用场景分为文件I/O和网络I/O,在这里之研究前者。

组成部分

  nio主要有三大组成部分:通道(Channel)、缓冲器(Buffer)、Selector(选择区)。
  因为nio的数据传输结构更接近于操作系统执行I/O的方式:通道缓冲器,而传统的I/O是面向字节流和字符流,所以nio的速度更快。
  通道用于存放数据,缓冲器用于传输数据,选择区用于监听多个通道的事件(比如:连接打开,数据到达)。

通道和缓冲器

  我们并没有直接和通道交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
  唯一直接与通道交互的缓冲器是ByteBuffer,即可以存储未加工字节的缓冲器。它是一个很基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个选择集。用于以原始的字节形式或基本数据类型输出和读取数据。但是它不能直接读取或输出对象,即使是字符串对象也不行。虽然这种处理很低级,但是却更贴合操作系统的处理方式。
  旧I/O类库中的FileInputStream、FileOutputStream、RandomAccessFile这三个类被修改了,用以产生FileChannel。这三个类都是字节操纵流,与nio性质一致。像Reader和Writer这种面向字符流的类就不能用于产生通道;但是java.nio.channels.Channels类中提供了方法,可以在通道中产生Reader和Writer。

读写实例

public class GetChannel {

    private static final int BSIZE = 2014;
    public static void main(String[] args) throws IOException {
        /**
         * 写数据,
         * 若已有内容,原内容会被覆盖;
         * 若不存在文件,则会创建新文件并写入新内容
         */
        FileChannel fc = new FileOutputStream("data.txt").getChannel();
        fc.write(ByteBuffer.wrap("some text".getBytes()));
        fc.close();

        /**
         * 添加内容至文件末尾
         */
        fc = new RandomAccessFile("data.txt", "rw").getChannel();
        fc.position(fc.size());     // 移动到文件末尾
        fc.write(ByteBuffer.wrap("Some more".getBytes()));
        fc.close();


        /**
         * 读取文件
         */
        fc = new FileInputStream("data.txt").getChannel();
        ByteBuffer buff = ByteBuffer.allocate(BSIZE);
        fc.read(buff);
        buff.flip();
        while(buff.hasRemaining()){
            System.out.print((char)buff.get());
        }
    }
}
  1. channel:对于上述三种流类,getChannel()方法都可以获得一个FileChannel。通道是一种相当基础的东西:可以向它传送用于读写的ByteBuffer,并且可以锁定文件的某些区域用于独占式访问
  2. ByteBuffer:将字节存放于ByteBuffer的方法之一是:使用一种“put”方法直接对它们进行填充,填入一个或多个字节,或基本数据类型的值。也可以使用wrap()方法将已存在的字节数组“包装”到ByteBuffer,这样就把一个数组包装为ByteBuffer缓冲器,一旦完成包装,底层数据就可以通过缓冲区或者直接访问。我们称第二种为数组支持的ByteBuffer
  3. position:可以使用FileChannelposition方法来在文件中移动FileChannel。在上例中是将其移动到文件末尾,方便内容的增加。
  4. allocate:对于只读访问,我们要显式使用静态的allocate()方法来分配ByteBuffer,nio的目标就是快速移动大量数据,因此ByteBuffer的大小就显得尤为重要——事实上,这里使用的1K可能比我们通常要使用的小一点(必须通过实际运行程序来找到最佳尺寸)。使用allocateDirect替代allocate,以产生一个与操作系统有更高耦合性的“直接”缓冲器还有可能达到更高的速度。但是,这种分配会增加开支,并且具体的实现也随操作系统的不同而不同。
  5. read()flip():一旦调用read()来告知FileChannelByteBuffer存储字节,就必须调用缓冲器上的flip(),让它做好让别人读取字节的准备。如果打算使用缓冲器执行进一步的read()操作,我们也必须调用clear()来为每个read()做好准备。如下例:
public class ChannelCopy {

    private static final int BSIZE = 2014;

    public static void main(String[] args) throws IOException {
        FileChannel in = new FileInputStream("src/main/resources/in.txt").getChannel();
        FileChannel out = new FileOutputStream("src/main/resources/out.txt").getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(BSIZE);
        while(in.read(buffer) != -1){
            buffer.flip();  // 准备写
            out.write(buffer);
            buffer.clear();  // 准备读
        }

        /**
          * 常用的是直接使用transferTo 或者 transferFrom
          */
//        in.transferTo(0, in.size(), out);
//         或者out.transferFrom(in, 0, in.size());
    }
}

  这里有两个FileChannel,一个用于读,一个用于写。每次read()操作之后,就会将数据输入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲区中,接着clear()操作则对所有的内部指针重新安排,以便缓冲器在另一个read()操作期间能够做好接收数据的准备。
  文件内容复制之类的操作,一般使用transferTo或者transferFrom方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一个简单的使用NIO写的服务端示例代码: ```java import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class NIOServer { private Selector selector; public void initServer(int port) throws IOException { // 创建一个选择器 selector = Selector.open(); // 创建一个服务端通道 ServerSocketChannel serverChannel = ServerSocketChannel.open(); // 配置为非阻塞模式 serverChannel.configureBlocking(false); // 绑定端口 serverChannel.socket().bind(new InetSocketAddress(port)); // 注册到选择器并监听连接事件 serverChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("Server start on " + port + "..."); } public void listen() throws IOException { while (true) { // 阻塞直到有事件发生 selector.select(); // 处理事件 Iterator<SelectionKey> keys = selector.selectedKeys().iterator(); while (keys.hasNext()) { SelectionKey key = keys.next(); keys.remove(); // 如果是新连接事件 if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel channel = server.accept(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); System.out.println("New connection from " + channel.getRemoteAddress()); } // 如果是读事件 else if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); channel.read(buffer); String msg = new String(buffer.array()).trim(); System.out.println("Receive message from " + channel.getRemoteAddress() + ": " + msg); } } } } public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(8888); server.listen(); } } ``` 这个服务端使用了Java NIO的API,可以监听端口,接受客户端连接,并读取客户端发送的消息。稍作修改也可以实现回复客户端消息的功能。需要注意的是,由于NIO是非阻塞的,所以需要在事件到来时进行处理,否则会阻塞线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vi_NSN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值