Java IO 模型(2)

Java IO 模型

3.5.5 文件copy

在这里插入图片描述

public class NIOChannelCopy {

    public static void main(String[] args) throws Exception {

        String filePath = "files//1.txt";

        FileInputStream inputStream = new FileInputStream(new File(filePath));
        FileChannel inputStreamChannel = inputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("files//2.txt");
        FileChannel outputStreamChannel = fileOutputStream.getChannel();

        ByteBuffer buffer = ByteBuffer.allocate(32);

        while (true){

            // 清空缓存
            buffer.clear();

            // 读取数据到缓存
            int read = inputStreamChannel.read(buffer);

            if (read == -1){
                break;
            }

            // 读写反转
            buffer.flip();

            // 将缓存中的数据写到 file
            outputStreamChannel.write(buffer);

        }


        outputStreamChannel.close();
        fileOutputStream.close();
        inputStreamChannel.close();
        inputStream.close();


    }

}
    public static void main(String[] args) throws Exception{

        FileInputStream fileInputStream = new FileInputStream(new File("files//1.txt"));
        FileChannel inputStreamChannel = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("files//2.txt");
        FileChannel outputStreamChannel = fileOutputStream.getChannel();

        outputStreamChannel.transferFrom(inputStreamChannel,0,inputStreamChannel.size());


        outputStreamChannel.close();
        fileOutputStream.close();
        inputStreamChannel.close();
        fileInputStream.close();

    }

3.5.6 Buffer 补充

Buffer 读写类型要一致

public class BufferType {

    public static void main(String[] args) {

        ByteBuffer buffer = ByteBuffer.allocate(32);

        buffer.put("123".getBytes(StandardCharsets.UTF_8));
        buffer.putInt(1);
        buffer.putChar('3');

        buffer.flip();

        System.out.println(buffer.getInt());
        System.out.println(buffer.getInt());
        System.out.println(buffer.getInt());

//        825373440
//        256
//        Exception in thread "main" java.nio.BufferUnderflowException
//        at java.base/java.nio.Buffer.nextGetIndex(Buffer.java:651)
//        at java.base/java.nio.HeapByteBuffer.getInt(HeapByteBuffer.java:402)
//        at com.bak.BufferType.main(BufferType.java:23)
//        

    }

}

MappedByteBuffer 直接在内存中操作文件

public class NIOMappedByteBuffer {

    public static void main(String[] args) throws Exception{

        RandomAccessFile randomAccessFile = new RandomAccessFile("files//1.txt","rw");

        FileChannel channel = randomAccessFile.getChannel();

        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());

        mappedByteBuffer.put(0,(byte) 'Q');

        

        channel.close();
        randomAccessFile.close();


    }

}

NIO 还支持通过多个 Buffer (即 Buffer 数组) 完成读写操作,即 Scattering和 Gathering

public class NIOScatteringAndGathering {

    public static void main(String[] args)throws Exception {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.socket().bind(new InetSocketAddress(9099));

        ByteBuffer[] buffers = new ByteBuffer[2];
        buffers[0] = ByteBuffer.allocate(3);
        buffers[1] = ByteBuffer.allocate(2);

        SocketChannel socketChannel = serverSocketChannel.accept();

        int messagelen = 5;

        while (true){

            int readLen = 0;

            while (readLen < messagelen){
                long read = socketChannel.read(buffers);
                readLen+=read;

                System.out.println("readLen = " + readLen);

            }

            // 将所有的buffer进行flip
            Arrays.stream(buffers).map(r -> r.flip());

            long writeLen = 0;

            while (writeLen < messagelen){
                long write = socketChannel.write(buffers);
                writeLen+=write;
                System.out.println("w = " + writeLen);
            }

            Arrays.stream(buffers).map(r -> r.clear());

        }


    }

}

3.6 选择器(Selector)

3.6.1 基本介绍

在这里插入图片描述

1.一个线程通过 selector 处理多个客户端
2.selector 能够检测其注册通道上是否发生事件,如果有事件则会根据不同的事件做相应的处理
3.通过 selector 在不同的通道上切换,就达到使用一个线程处理多个客户端
4.只有注册的通道真正发生读写请求时,才会进行读写
5.使用单线程处理多个客户端,避免了多线之间的切换开销

3.6.2 NIO Socket demo

public class NIOSelectorServer {

    public static void main(String[] args) throws Exception{

        // 获取一个 selector
        Selector selector = Selector.open();

        // 服务器启动 socket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",9099));

        // 设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 将 channel 注册到 selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("selector 上注册的所有 channel size = " + selector.keys().size());

        while (true){

            // channel 无事件
            if (selector.select(1000) == 0){
                System.out.println("server 等待了 1s,无连接...");
                continue;
            }

            // channel 有事件

            // 获取所有有事件的 channel
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            // 遍历所有 channel 根据 channel 的事件类型进行处理
            while (iterator.hasNext()){

                SelectionKey key = iterator.next();

                // 连接事件
                if (key.isAcceptable()){

                    // 新接入的连接,为其创建一个 channel
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    System.out.println("client 端 port = " + socketChannel.getRemoteAddress() + " 连接成功,并为其生成一个 channel = " + socketChannel.hashCode());

                    // 将 channel 设置为非阻塞模式
                    socketChannel.configureBlocking(false);

                    // 将 socketChannel 注册到 selector,并将通道关注事件设置为 OP_READ 同时为通道创建一个 buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                }

                // 读事件
                if (key.isReadable()){

                    // 从 buffer 中读取数据

                    // 获取 key 关联的 channel
                    SocketChannel channel = (SocketChannel) key.channel();

                    // 获取 key 关联的 buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();

                    channel.read(buffer);

                    System.out.println("from client port = " + channel.getRemoteAddress() + " 读取到的数据:" + new String(buffer.array()));
                }

                // 移除处理完的 channel
                iterator.remove();

            }


        }


    }

}

public class NIOSelectorClient {

    public static void main(String[] args) throws Exception{

        SocketChannel socketChannel = SocketChannel.open();

        socketChannel.configureBlocking(false);

        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9099);

        if (!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()){
                System.out.println("client端正在连接server端....");
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }

        System.out.println("client 连接 server 端成功.....");


        String data = "hello,netty";

        ByteBuffer buffer = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8));

        // 将数据写入 buffer 发送到 socket
        socketChannel.write(buffer);

        System.in.read();

    }

}

3.6.3 Selector Api

public static Selector open() 获取一个 selector 对象
public abstract int selectNow() 立即返回 不阻塞
public abstract int select() 阻塞
public abstract int select(long timeout) 阻塞 等待 xxx 毫秒
监控所有注册的通道,当其有 IO 操作时,将返回对应的 SelectionKey
public abstract Set keys() 获取 selector 上注册的所有 channel 的
public abstract Set selectedKeys() 获取 selector 上有事件发生的 channel
public abstract Selector wakeup() 唤醒 selector

3.6.4 SelectionKey

SelectionKey 表示 Selector 和 网络通道的注册关系

OP_READ = 1读操作
OP_WRITE = 4写操作
OP_CONNECT = 8建立连接
OP_ACCEPT = 16有新的连接
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
public abstract Selector selector() 得到与之关联的 Selector
public abstract SelectableChannel channel()           Channel
public final Object attachment()  得到与之关联的共享数据

public abstract int interestOps() 
public abstract SelectionKey interestOps(int ops) 设置或修改监听事件
public final boolean isAcceptable() 是否可accept
public final boolean isReadable() 是否可读
public final boolean isWritable() 是否可写


3.6.5 ServerSocketChannel

ServerSocketChannel 在服务器监听新的客户端 socket 连接

public static ServerSocketChannel open() 得到一个ServerSocketChannel通道 

public abstract ServerSocketChannel bind(SocketAddress local, int backlog) 
public final ServerSocketChannel bind(SocketAddress local)    设置服务器端端口 号

public final SelectableChannel configureBlocking(boolean block)  设置阻塞或非阻塞模式,false表示采用非阻塞
public abstract SocketChannel accept() 接受一个连接,返回代表这个连接的通道对 象
public final SelectionKey register(Selector sel, int ops) 注册一个选择器并设置 监听事件

3.6.6 SocketChannel

SocketChannel,网络IO通道,负责读写操作。NIO把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区

public static SocketChannel open(SocketAddress remote) 得到一个SocketChannel通道
public final SelectableChannel configureBlocking(boolean block) 设置阻塞或非阻塞 模式,取值false表示采用非阻塞模式
public abstract boolean connect(SocketAddress remote) 连接服务器
public abstract boolean finishConnect()  如果上面的方法连接失败,接下来就要通过该方法 完成连接操作
public abstract int write(ByteBuffer src)  往通道里写数据 
public abstract int read(ByteBuffer dst) 从通道里读数据
public final SelectionKey register(Selector sel, int ops,Object att) 注册一个选择器并 设置监听事件,最后一个参数可以设置共享数据
public final void close()  关闭通道

3.6.7 NIO Socket demo 分析

1.服务器端通过 ServerSocketChannel 绑定 网络通信地址
2.ServerSocketChannel 注册到 selector 上,并设置关注事件
3.selector 通过 select() 方法监听 ,返回有事件发生的通道数
4.根据 channel 的事件做,指定的处理
5.如果是 accept 事件,为其初始化,创建 channel buffer 并将 channel 注册到 selector

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值