IO学习——Channel

概述

属于java.nio.channels包下,基本上所有的 IO 操作都是从 Channel 开始。数据可以从 Channel 读取到 Buffer 中,也可以从 Buffer 写到 Channel 中

Channel 对比 Stream

  1. 对于同一个 Channel ,我们可以从它读取数据,也可以向它写入数据。而对于同一个 Stream ,通畅要么只能读,要么只能写,二选一( 有些文章也描述成“单向”,也是这个意思 )。
  2. Channel 可以非阻塞的读写 IO 操作,而 Stream 只能阻塞的读写 IO 操作。
  3. Channel 必须配合 Buffer 使用,总是先读取到一个 Buffer 中,又或者是向一个 Buffer 写入。也就是说,我们无法绕过 Buffer ,直接向 Channel 写入数据

Channel 的实现类

代码

public interface Channel extends Closeable {

    /**
     * 判断此通道是否处于打开状态。 
     */
    public boolean isOpen();

    /**
     * 关闭通道。 
     */
    public void close() throws IOException;

}

 

SocketChannel

打开channel

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("地址", 80));

关闭channel

socketChannel.close();

读数据

read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1,表示已经读到了流的末尾(连接关闭了)。

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

写数据

注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
    channel.write(buf);
}

 

非阻塞模式

可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("地址", 80));
while(! socketChannel.finishConnect() ){
    //wait, or do something else...
}

write()

非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了,这里就不赘述了。

read()

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

非阻塞模式与选择器

非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等

 

ServerSocketChannel

例子

//打开通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定地址
serverSocketChannel.socket().bind(new InetSocketAddress(9999));

while(true){
    //确认请求
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    //do something with socketChannel...
}

打开channel

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

关闭channel

serverSocketChannel.close();

监听

通过 ServerSocketChannel.accept() 方法监听新进来的连接。当 accept()方法返回的时候,它返回一个包含新进来的连接的 SocketChannel。因此, accept()方法会一直阻塞到有新连接到达。

while(true){
    SocketChannel socketChannel =
            serverSocketChannel.accept();
    //do something with socketChannel...

}

非阻塞模式

ServerSocketChannel可以设置成非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回,如果还没有新进来的连接,返回的将是null。 因此,需要检查返回的SocketChannel是否是null.如:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));
//非阻塞模式
serverSocketChannel.configureBlocking(false);

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

    if(socketChannel != null){
        //do something with socketChannel...
    }
}

DatagramChannel

打开channel

这个例子打开的 DatagramChannel可以在UDP端口9999上接收数据包。

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

 

接收数据

receive()方法会将接收到的数据包内容复制到指定的Buffer. 如果Buffer容不下收到的数据,多出的数据将被丢弃。

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

发送数据

这个例子发送一串字符到”jenkov.com”服务器的UDP端口80。 因为服务端并没有监控这个端口,所以什么也不会发生。也不会通知你发出的数据包是否已收到,因为UDP在数据传送方面没有任何保证。

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

连接到特定的地址

可以将DatagramChannel“连接”到网络中的特定地址的。由于UDP是无连接的,连接到特定地址并不会像TCP通道那样创建一个真正的连接。而是锁住DatagramChannel ,让其只能从特定地址收发数据

	channel.connect(new InetSocketAddress("jenkov.com", 80));

 

FileChannel

 

打开channel

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

 

关闭channel

channel.close();

 

读数据

调用多个read()方法之一从FileChannel中读取数据。如:

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

写数据

使用FileChannel.write()方法向FileChannel写数据,该方法的参数是一个Buffer。如:

String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
hile(buf.hasRemaining()) {
    channel.write(buf);
}

主要方法

 

position

有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取FileChannel的当前位置。

long pos = channel.position();
channel.position(pos +123);

size

FileChannel实例的size()方法将返回该实例所关联文件的大小。如:

long fileSize = channel.size();

truncate

可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:

channel.truncate(1024);

force

FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。

channel.force(true);

 

参考文章:

http://www.iocoder.cn/

http://ifeve.com/socket-channel/

http://ifeve.com/server-socket-channel/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大·风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值