Java NIO学习 (1)

写在前面

Java NIO 由三个核心组成:Channels、Buffers、Selectors

Buffer是数据的载体,
Channel里面如果有数据,通过 Channels.receive(Buffer) 接收,
Channel里面需要数据,通过 Channels.send(Buffer,接收地址) 发送。

Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。
在这里插入图片描述

1、Buffers

Java NIO 有以下Buffer类型:
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer

1.1、Buffer的方法

ByteBuffer buf = ByteBuffer.allocate(48);      // 设置缓存区大小为48
buf.flip();                                    // 将Buffer从写模式切换到读模式
int bytesRead = channel.read(buf);             // 从channel中读取数据放入buf
int bytesWritten = channel.write(buf);         // 将数据写入到channel
buf.put(127);                                  // 将数据写入buf
byte aByte = buf.get();                        // 从buf读取数据
buf.rewind();                                  // 重读
buf.clear();                                   // 清空
buf.compact();                                 // 清空已读数据
buf.mark();                                    // 标记当前读取位置
buf.reset();                                   // 跳到标记位置
buf.equals(buf1);                              // 判断两个Buffer内容是否相等
buf.compareTo(buf1);                           // 比较两个Buffer的剩余元素大小

1.2、Scatter/Gather

scatter:分散,从Channel中读取是指在读操作时将读取的数据写入多个buffer中。
gather:聚集,写入Channel是指在写操作时将多个buffer的数据写入同一个Channel。

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufs = { header, body };
// 分散读取
channel.read(buffs);
// 聚集写入
Channel.write(buffs);

2、Channels

Channels有下面几个实现类:

实现类用途
FileChannel从文件中读写数据
DatagramChannel通过UDP读写网络中的数据
SocketChannel通过TCP读写网络中的数据
ServerSocketChannel监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel

2.1、FileChannel

FileChannel读取文件

2.1.1、从文件读取数据

        RandomAccessFile aFile = new RandomAccessFile("1.txt", "rw");
        FileChannel channel = aFile.getChannel();

        ByteBuffer buf = ByteBuffer.allocate(48);
        int bytesRead = channel.read(buf);

        while (bytesRead != -1) {

            // 从写模式切换到读模式
            buf.flip();

            while (buf.hasRemaining()) {
                // 一次从buf中读取一个字节,并且转化为char输出
                System.out.print((char) buf.get());
            }

            buf.clear();
            bytesRead = channel.read(buf);
        }
        aFile.close();
    }

2.1.2、将数据写入文件

transferFrom()方法可以将数据从源通道传输到FileChannel中

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

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

long position = 0;
long count = fromChannel.size();

// 从文件1写入到文件2
channel1.transferFrom(position, count, channel2);

position=0表示从0处开始向目标文件写入数据,count表示最多传输的字节数。如果源通道的剩余空间小于计数 个字节,则所传输的字节数要小于请求的字节数。

要注意,在SoketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足计数字节)。因此,SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。

transferTo()方法将数据从FileChannel传输到其他的channel中。

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

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

long position = 0;
long count = fromChannel.size();

// 从文件1写入到文件2
channel1.transferTo(position, count, channel2);

2.1.3、FileChannel方法

long pos = channel.position();    //  获取FileChannel的当前位置
channel.position(pos +123);

long fileSize = channel.size();  // 返回该实例所关联文件的大小
channel.truncate(1024);          // 将文件中指定长度后面的部分将被删除。
channel.force(true);             // 将文件数据和元数据强制写到磁盘上

2.2、SocketChannel

SocketChannelTCP网络套接字的通道

// 打开一个tcp通道
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://**.com", 8080));

// 读取数据
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

// 关闭SocketChannel
socketChannel.close();

写入数据

// 打开一个tcp通道
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://**.com", 8080));

ByteBuffer buf = ByteBuffer.allocate(48);
String data = "这是一串需要传递的文字";
buf.clear();
buf.put(data.getBytes());

// buffer从写模式转为读模式
buf.flip();

// 循环写入
while(buf.hasRemaining()) {
    channel.write(buf);
}

非阻塞模式

// 打开一个tcp通道
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://**.com", 8080));

while(! socketChannel.finishConnect() ){
    // 写入数据
}

非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。
非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值,它会告诉你读取了多少字节。

2.3、ServerSocketChannel

ServerSocketChannel 是一个可以监听新进来的TCP连接的通道

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(8000));

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();
    // ...
}

serverSocketChannel.accept();监听新进来的连接

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();
    // ...
}

2.4、DatagramChannel

DatagramChannel是一个能收发UDP包的通道。

接收数据

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);

发送数据

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

String data = "数据...";
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(data.getBytes());
buf.flip();

int bytesSent = channel.send(buf, new InetSocketAddress("127.0.0.1", 80));

连接到特定的地址

channel.connect(new InetSocketAddress("127.0.0.1", 80));

3、Selector

Selector可以用一个线程去管理多个channel

3.1、将channel注册到selector

// 创建一个Selector
Selector selector = Selector.open();        

// 将channel注册到selector上
channel.configureBlocking(false);   // 切换为非阻塞状态,与Selector一起使用时,Channel必须处于非阻塞模式
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

3.2、interest集合

register()方法的第二个参数,表示监听类型,是一个“interest集合”,有下列4种类型:

类型说明
SelectionKey.OP_CONNECT连接就绪
SelectionKey.OP_ACCEPT接收就绪
SelectionKey.OP_READ读就绪
SelectionKey.OP_WRITE写就绪

对多个就绪状态监听:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合

// 获取事件集合
int interestSet = selectionKey.interestOps();

// 判断某个事件是否在集合中,返回true表示在
boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;

3.3、SelectionKey

当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。
selectionKey对象可调用的方法:

Channel channel = selectionKey.channel();         // 返回通道
Selector selector = selectionKey.selector();      // 返回selector
int interestSet = selectionKey.interestOps();     // 返回监听类型集合
Object attachedObj = selectionKey.attachment();   // 返回附加对象

int readySet = selectionKey.readyOps();           // 返回通道已经准备就绪的操作的集合
boolean b = selectionKey.isAcceptable();          // 判断是否“接收就绪”
boolean b = selectionKey.isConnectable();         // 判断是否“连接就绪”
boolean b = selectionKey.isReadable();            // 判断是否“读就绪”
boolean b = selectionKey.isWritable();            // 判断是否“写就绪”

下面是写入附加对象

// 添加附加对象
selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

//或者在注册时就加入附加对象:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

sselector对象可调用的方法

selector.select();                                // 阻塞,直到监听类型已就绪
selector.selectNow();                             // 不阻塞,不管什么通道就绪都立刻返回,没有通道变成可选择的,则此方法直接返回零。

Set selectedKeys = selector.selectedKeys();       // 一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集”中的就绪通道。
selector.wakeup();                                // 调用后,阻塞在select()方法上的线程会立马返回
selector.close();

遍历Set selectedKeys = selector.selectedKeys();这个已选择的键集合来访问就绪的通道

Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // 接收已就绪
    } else if (key.isConnectable()) {
        // 连接已就绪
    } else if (key.isReadable()) {
        // 读就绪
    } else if (key.isWritable()) {
        // 写就绪
    }
    keyIterator.remove();
}

示例

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;
  Set selectedKeys = selector.selectedKeys();
  Iterator keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    
    if(key.isAcceptable()) {
    } else if (key.isConnectable()) {
    } else if (key.isReadable()) {
    } else if (key.isWritable()) {}
    
    keyIterator.remove();
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值