📜  Java NIO-收集(1)

📅  最后修改于: 2023-12-03 14:42:15.467000             🧑  作者: Mango

Java NIO-收集

Java NIO (New IO) 是 Java 1.4 引入的一组 API,用于提供与传统 IO 区别很大的新 IO 编程体验,其主要目标是提高速度和可扩展性,可以作为 Java Socket 工具包的替代选择。在 Java NIO 中,所有交互都是非阻塞的,允许您在一个线程中处理多个操作,从而提高了处理 IO 操作的效率。

1. 内容概述

这个收集包含有关 Java NIO 中以下几个概念的讨论:

  • 缓冲区 (Buffer)
  • 通道 (Channel)
  • 选择器 (Selector)
  • 文件处理
  • 单线程服务器
  • 复制文件
  • 序列化和反序列化
  • 游戏服务器
  • Netty 框架的简介
2. 缓冲区 (Buffer)

在 Java NIO 中,所有的 IO 操作都需要使用缓冲区。因此学习缓冲区的使用是 Java NIO 编程的基础。缓冲区是一个对象,它包含一些要写入或从其中读取的数据。在 NIO 库中定义了以下缓冲区类:

  • ByteBuffer
  • CharBuffer
  • IntBuffer
  • ShortBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

除了这些基本类型的缓冲区外,Java NIO 还有一些特殊类型的缓冲区,如 MappedByteBuffer 和 DirectByteBuffer。使用一种特殊类型的缓冲区取决于您的具体需求。

在 Java NIO 中,缓冲区具有四个核心属性:

  • 容量 (Capacity):表示缓冲区中的数据元素数量。
  • 限制 (Limit):表示缓冲区中的数据元素数量上限,在写入模式下表示缓冲区中可以插入数据元素的数量上限,在读取模式下表示缓冲区中可以读取的数据元素的数量上限。
  • 位置 (Position):表示下一个要进行读取或写入操作的数据元素的索引位置。
  • 标记 (Mark):表示备忘位置。

以下是一个简单的例子,说明了如何创建一个 ByteBuffer 并访问其核心属性:

// Create a ByteBuffer with a capacity of 10 bytes
ByteBuffer buffer = ByteBuffer.allocate(10);

// Access the buffer's core attributes
System.out.println("Capacity: " + buffer.capacity()); // Prints 'Capacity: 10'
System.out.println("Limit: " + buffer.limit()); // Prints 'Limit: 10'
System.out.println("Position: " + buffer.position()); // Prints 'Position: 0'
System.out.println("Mark: " + buffer.mark()); // Prints 'Mark: not set'
3. 通道 (Channel)

在 Java NIO 中,通道与流 (Stream) 的概念类似,但也有很大的区别。与流不同,通道是双向的,可在同一个通道上进行读取和写入操作。

Java NIO 库中定义了以下通道类:

  • FileChannel:从文件读取数据或将数据写入文件。
  • DatagramChannel:通过 UDP 协议读取或写入网络数据。
  • SocketChannel:通过 TCP 协议读取或写入网络数据。
  • ServerSocketChannel:可以监听新进来的 TCP 连接,并将其转发给指定的线程处理。

以下是一个简单的例子,说明了如何使用 FileChannel 读取文件中的数据:

RandomAccessFile file = new RandomAccessFile("test.txt", "rw");
FileChannel fileChannel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);

while (fileChannel.read(buffer) > 0) {
    buffer.flip();
    while (buffer.hasRemaining()) {
        System.out.print((char) buffer.get());
    }
    buffer.clear();
}
file.close();
4. 选择器 (Selector)

Java NIO 库中还包含一个非常重要的组件:选择器。选择器允许您同时监视多个通道的 IO 事件,从而使单个线程能够管理多个通道。选择器与通道之间的关系可以看作一个三角形:一个选择器与多个通道相关联,每个通道又与一个缓冲区相关联。

要使用选择器,在 Java NIO 中,需要经过以下三个基本步骤:

  1. 创建 Selector
  2. 向 Selector 注册一个或多个通道
  3. 调用 Selector 的 select() 方法,等待 IO 事件的发生

以下是一个简单的例子,说明了如何使用选择器注册 SocketChannel,并在 Socket 服务端和客户端之间进行通信:

// Create a new Selector
Selector selector = Selector.open();

// Create a new ServerSocketChannel and register it with the Selector
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.bind(new InetSocketAddress("localhost", 8080));
serverSocket.configureBlocking(false);
serverSocket.register(selector, SelectionKey.OP_ACCEPT);

// Wait for connections
while (true) {
    // Wait for events
    selector.select();
    Iterator<SelectionKey> keys = selector.selectedKeys().iterator();

    // Process each event
    while (keys.hasNext()) {
        SelectionKey key = keys.next();
        if (key.isAcceptable()) {
            // Accept the connection
            SocketChannel socket = serverSocket.accept();
            socket.configureBlocking(false);
            socket.register(selector, SelectionKey.OP_READ);
            System.out.println("Accepted connection from " + socket);
        } else if (key.isReadable()) {
            // Read data from the connection
            SocketChannel socket = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            socket.read(buffer);
            buffer.flip();
            System.out.println("Received message '" + new String(buffer.array()) + "' from " + socket);

            // Write data back to the client
            socket.write(buffer);
            buffer.clear();
        }
        keys.remove();
    }
}
5. 文件处理

Java NIO 对文件的操作也非常方便。以下是一个用于将文件内容写入其他文件的例子:

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(1024);
while (fromChannel.read(buffer) != -1) {
    buffer.flip();
    toChannel.write(buffer);
    buffer.clear();
}
fromChannel.close();
toChannel.close();
6. 单线程服务器

Java NIO 还使您能够在一个线程中同时为多个客户端提供服务。以下是一个简单的例子:

// Create a new ServerSocketChannel and set it to non-blocking mode
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(9090));
serverSocketChannel.configureBlocking(false);

// Create a Selector and register the ServerSocketChannel with it
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

// Wait for connections and process them
while (true) {
    if (selector.select() <= 0) {
        continue;
    }

    // Get the keys for the selected channels
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> iter = selectedKeys.iterator();

    // Process each key
    while (iter.hasNext()) {
        SelectionKey key = iter.next();

        if (key.isAcceptable()) {
            // Accept the new connection
            ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
            SocketChannel socket = serverSocket.accept();
            socket.configureBlocking(false);
            socket.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            // Read data from the connection and write back the response
            SocketChannel socket = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            socket.read(buffer);
            buffer.flip();
            socket.write(buffer);
            buffer.clear();
        }
        iter.remove();
    }
}
7. 复制文件

以下是一个简单的例子,说明了如何使用 Java NIO 复制一个大文件:

FileInputStream inputStream = new FileInputStream("input.txt");
FileOutputStream outputStream = new FileOutputStream("output.txt");
FileChannel inChannel = inputStream.getChannel();
FileChannel outChannel = outputStream.getChannel();

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

inputStream.close();
outputStream.close();
inChannel.close();
outChannel.close();
8. 序列化和反序列化

Java NIO 还提供了用于序列化和反序列化对象的工具类:

public static void serialize(Object obj, String filename) throws IOException {
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename));
    oos.writeObject(obj);
    oos.close();
}

public static Object deserialize(String filename) throws IOException, ClassNotFoundException {
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
    Object obj = ois.readObject();
    ois.close();
    return obj;
}
9. 游戏服务器

Java NIO 可以非常好地用于编写游戏服务器。以下是一个简单的例子,说明了如何使用 Java NIO 实现一个简单的文字游戏服务器:

// Create a new ServerSocketChannel and set it to non-blocking mode
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(9090));
serverSocketChannel.configureBlocking(false);

// Create a Selector and register the ServerSocketChannel with it
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

// Create a list to store the connected clients
List<Client> clients = new ArrayList<>();

// Wait for connections and process them
while (true) {
    if (selector.select() <= 0) {
        continue;
    }

    // Get the keys for the selected channels
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> iter = selectedKeys.iterator();

    // Process each key
    while (iter.hasNext()) {
        SelectionKey key = iter.next();

        if (key.isAcceptable()) {
            // Accept the new client
            ServerSocketChannel serverSocket = (ServerSocketChannel) key.channel();
            SocketChannel socket = serverSocket.accept();
            socket.configureBlocking(false);
            socket.register(selector, SelectionKey.OP_READ);
            clients.add(new Client(socket));
        } else if (key.isReadable()) {
            // Read data from the client and broadcast it to all other clients
            SocketChannel socket = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            socket.read(buffer);
            buffer.flip();
            String message = new String(buffer.array()).trim();

            for (Client client : clients) {
                if (client.socket != socket) {
                    client.socket.write(ByteBuffer.wrap((message + "\n").getBytes()));
                }
            }
            buffer.clear();
        }
        iter.remove();
    }
}

// The Client class
class Client {
    public SocketChannel socket;

    public Client(SocketChannel socket) {
        this.socket = socket;
    }
}
10. Netty 框架的简介

Netty 是一个用于创建高性能、可扩展的网络应用程序的框架。Netty 基于 Java NIO 构建,提供了异步、事件驱动和可扩展的网络应用程序开发 API,使您可以轻松地创建高性能和可伸缩的网络应用程序。以下是一个简单的例子,说明了如何使用 Netty 实现一个简单的 Echo 服务器:

// Create a new ServerBootstrap
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();

// Configure the ServerBootstrap
serverBootstrap.group(bossGroup, workerGroup)
    .channel(NioServerSocketChannel.class)
    .childHandler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            socketChannel.pipeline().addLast(new EchoServerHandler());
        }
    })
    .option(ChannelOption.SO_BACKLOG, 128)
    .childOption(ChannelOption.SO_KEEPALIVE, true);

// Start the server
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
System.out.println("Server started");

// Wait for the server to close
channelFuture.channel().closeFuture().sync();
System.out.println("Server closed");

// The EchoServerHandler class
class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buffer = (ByteBuf) msg;
        byte[] bytes = new byte[buffer.readableBytes()];
        buffer.getBytes(0, bytes);
        System.out.println("Received message '" + new String(bytes) + "' from " + ctx.channel().remoteAddress());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

以上就是 Java NIO 的一些概念和使用示例。希望本文能够对您有所帮助。