IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java NIO(Channel) -> 正文阅读

[Java知识库]Java NIO(Channel)

2.1 Channel概述

Channel是一个通道,可以通过它读取和写入数据,它就像水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的(全双工),流只是在一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而且通道可以用于读、写或者同时用于读写操作。因为Channel是全双工的,所以它可以比流更好地映射底层操作系统的API。

NIO中通过Channel封装了对数据源的操作,通过Channel我们可以操作数据源,但又不必关心数据源的具体物理结构。这个数据源可能是多种的。比如,可以是文件,也可以是网络socket。在大多数应用中,Channel与文件描述符或者socket是一一对应的。Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。

public interface Channel extends Closeable {

    /**
     * Tells whether or not this channel is open.
     *
     * @return <tt>true</tt> if, and only if, this channel is open
     */
    public boolean isOpen();

    /**
     * Closes this channel.
     *
     * <p> After a channel is closed, any further attempt to invoke I/O
     * operations upon it will cause a {@link ClosedChannelException} to be
     * thrown.
     *
     * <p> If this channel is already closed then invoking this method has no
     * effect.
     *
     * <p> This method may be invoked at any time.  If some other thread has
     * already invoked it, however, then another invocation will block until
     * the first invocation is complete, after which it will return without
     * effect. </p>
     *
     * @throws  IOException  If an I/O error occurs
     */
    public void close() throws IOException;

}

与缓冲区不同,通道API主要由接口指定。不同的操作系统上通道实现(Channel Implementation)会有根本性的差异,所以通道API仅仅描述了可以做什么。因此很自然地,通道实现经常使用操作系统的本地代码。通道接口允许您以一种受控且可移植的方式来访问底层的I/O服务。

Channel是一个对象,可以通过它读取和写入数据。拿NIO与原来的I/O做个比较,通道就像是流。所有数据都通过Buffer对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。

Java NIO的通道类似流,但又有些不通:

  • 既可以从通道中读取数据,也可以写数据到通道。但流的读写通常是单向的。
  • 通道可以异步地读写。
  • 通道中的数据总是要先读到一个Buffer,或者总是从一个Buffer中写入。

2.2 Channel实现

下面是Java NIO中最重要的Channel实现:

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

这如你所看到的,这些通道涵盖了UDP和TCP网络IO,以及文件IO。

2.3 FileChannel介绍和示例

FileChannel类可以实现常用的read、write以及scatter/gather操作,同时它也提供了很多专用于文件的新方法。这些方法中的许多都是我们所熟悉的文件操作。

方法描述
int read(BytesBuffer dst)从Channel中读取数据到ByteBuffer
long read(ByteBuffer[] dsts)将Channel中的数据『分散』到ByteBuffer[]
int write(ByteBuffer src)将ByteBuffer中的数据写入到Channel中
long write(ByteBuffer[] srcs)将ByteBuffer[] 中的数据『聚集』到Channel
long position()返回此通道的文件位置
FileChannel position(long p)设置此通道的文件位置
long size()返回此通道的文件的当前大小
FileChannel truncate(long s)将此通道的文件截取为给定大小
void force(boolean metaData)强制将所有对此通道的文件更新写入到存储设备中

2.3.1 入门用例

读取文件内容

file.txt

this is nio!!!

java代码:

package com.study;

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelDemo1 {
    //FileChannel 读取数据到buffer中
    public static void main(String[] args) throws Exception {
        //创建FileChannel
        RandomAccessFile aFile = new RandomAccessFile("/Users/xxx/Desktop/NIO/file.txt", "rw");
        FileChannel channel = aFile.getChannel();
        //创建Buffer
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //读取数据到Buffer中
        int bytesRead = channel.read(buf);
        while(bytesRead != -1){
            System.out.println("读取了:" + bytesRead);
            buf.flip();
            while(buf.hasRemaining()){
                System.out.println((char)buf.get());
            }
            buf.clear();
            bytesRead = channel.read(buf);
        }
        aFile.close();
        System.out.println("结束了");
    }
}

控制台输出:

读取了:14
t
h
i
s
 
i
s
 
n
i
o
!
!
!
结束了

Process finished with exit code 0

2.4 FileChannel操作详解

2.4.1 打开FileChannel

在使用FileChannel之前,必须先打开它。但是,我们无法直接打开一个FileChannel,需要通过一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。下面是通过RandomAccessFile打开FileChannel的示例:

RandomAccessFile aFile = new RandomAccessFile("/Users/xxx/Desktop/NIO/file.txt", "rw");
FileChannel channel = aFile.getChannel();

2.4.2 从FileChannel读取数据

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

//创建Buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//读取数据到Buffer中
int bytesRead = channel.read(buf);

首先,分配一个Buffer。从FileChannel中读取的数据被读到Buffer中。然后调用FileChannel.read()方法。该方法将数据从FIleChannel读取到Buffer中。read()方法返回int值表示了有多少字节被读到了Buffer中。如果返回-1,表示到了文件末尾。

2.4.3 向FileChannel写数据

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

package com.study;

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

//FileChannel写操作
public class FileChannelDemo2 {
    //FileChannel 读取数据到buffer中
    public static void main(String[] args) throws Exception {
        //创建FileChannel
        RandomAccessFile aFile = new RandomAccessFile("/Users/liyabin01/Desktop/计算机基础/面试/NIO/file_w.txt", "rw");
        FileChannel channel = aFile.getChannel();
        //创建Buffer
        ByteBuffer buf = ByteBuffer.allocate(1024);
        String newData = "data write";

        //写入内容
        buf.put(newData.getBytes());
        buf.flip();

        //FileChannel完成最终实现
        while(buf.hasRemaining()){
            channel.write(buf);
        }
        //关闭
        channel.close();
    }
}

注意FileChannel.write()是在while循环中调用的。因为无法保证write()方法一次能向FileChannel写入多少字节,因此需要重复调用write()方法,直到Buffer中已经没有尚未写入通道的字节。

2.4.4 关闭FileChannel

用完FileChannel后必须将其关闭。

channel.close();

2.4.5 FileChannel的position方法

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

这里有两个例子:

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

如果将位置设置在文件结束符之后,然后试图从文件通道中读取数据,读方法将返回-1(文件结束标志)。

如果将位置设置在文件结束符之后,然后向通道中写数据,文件将撑大到当前位置并写入数据。这可能导致『文件空洞』,磁盘上物理文件中写入的数据间有空隙。

2.4.6 FileChannel的size方法

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

long fileSize = channel.size();

2.4.7 FileChannel的truncate方法

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

channel.truncate(1024);

这个例子截取文件的前1024个字节。

2.4.8 FileChannel的force方法

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

force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上

2.4.9 FileChannel的transferTo和transferFrom方法

通道之间的数据传输:

如果两个通道中有一个是FileChannel,那你可以直接将数据从一个channel传输到另一个channel。

(1)transferFrom()方法

FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中。下面是一个FileChannel完成文件间的复制的例子:

示例:

01.txt:

this is from txt 01;

Java源码:

package com.study;

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

//通道间的数据传输
public class FileChannelDemo3 {
    //FileChannel 读取数据到buffer中
    public static void main(String[] args) throws Exception {
        //创建2个FileChannel
        RandomAccessFile aFile = new RandomAccessFile("/Users/xxx/NIO/01.txt", "rw");
        FileChannel fromChannel = aFile.getChannel();

        RandomAccessFile bFile = new RandomAccessFile("/Users/xxx/NIO/02.txt", "rw");
        FileChannel toChannel = bFile.getChannel();

        //fromChannel 传输到 toChannel
        long position = 0;
        long count = fromChannel.size();
        toChannel.transferFrom(fromChannel, position, count);

        aFile.close();
        bFile.close();
        System.out.println("over!!");
    }
}

方法的输入参数position表示从position处开始向目标文件写入数据,count表示最多传输的字节数。如果源通道的剩余空间小于count个字节,则传输的字节数要小于请求的字节数。此处要注意,在SocketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足count字节)。因此,SocketChannel可能不会将请求的所有(Count个字节)全部传输到FileChannel中。

(2)transferTo()方法

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

下面是一个tansferTo()方法的例子:

package com.study;

import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

//通道间的数据传输
public class FileChannelDemo4 {
    //FileChannel 读取数据到buffer中
    public static void main(String[] args) throws Exception {
        //创建2个FileChannel
        RandomAccessFile aFile = new RandomAccessFile("/Users/xxx/NIO/01.txt", "rw");
        FileChannel fromChannel = aFile.getChannel();

        RandomAccessFile bFile = new RandomAccessFile("/Users/xxx/NIO/03.txt", "rw");
        FileChannel toChannel = bFile.getChannel();

        //fromChannel 传输到 toChannel
        long position = 0;
        long count = fromChannel.size();
        fromChannel.transferTo(position, count, toChannel);

        aFile.close();
        bFile.close();
        System.out.println("over!!");
    }
}

2.5 Socket通信

(1)新的Socket通信类可以运行非阻塞模式并且是可选择的,可以激活打程序(如网络服务器和中间件)巨大的可伸缩性和灵活性。本节中我们会看到,再也没有为每个Socket连接使用一个线程的必要了,也避免了管理大量线程所需要的上下文切换开销。借助新的NIO类,一个或几个线程就可以管理成千上百的活动Socket连接了,并且只有很少甚至可能没有性能损失。所有的Socket通道类(DatagramChannel、SocketChannel和ServerSocketChannel)都继承了位于java.nio.channels.spi包中的AbstractSelectableChannel。这意味着我们可以用一个Selector对象来执行socket通道的就绪选择。

(2)请注意DatagramChannel和SocketChannel实现定义读和写功能的接口而ServerSocketChannel不实现。ServerSocketChannel负责监听传入的连接和创建新的SocketChannel对象,它本身从不传输数据。

(3)在我们具体讨论每一种socket通信前,您应该了解socket和socket通道之间的关系。通道是一个连接I/O服务导管并提供与该服务交互的方法。就某个socket而言,它不会再次实现与之对应的socket通道类中的socket协议API,而java.net中已经存在的socket通道都可以被大多数协议操作重复使用

全部socket通道类(DatagramChannel、SocketChannel和ServerSocketChannel)在被实例化时都会创建一个对等socket对象。这些是我们所熟悉的来自java.net的类(Socket、ServerSocket和DatagramSocket),它们已经被更新以识别通道。对等socket可以通过调用socket()方法从一个通道上获取。此外,这三个java.net类现在都有getChannel()方法。

(4)要把一个socket通道置于非阻塞模式,我们要依靠所有的socket通道类的公有超级类:SelectableChannel。就绪选择(readiness selection)是一种可以用来查询通道的机制,该查询可以判断通道是否准备好执行一个目标操作,如读或写。非阻塞I/O和可选择性是紧密相联的,那也正是管理阻塞模式的API代码要在SelectableChannel超级类中定义的原因。

设置或者重新设置一个通道的阻塞模式是很简单的,只要调用configureBlocking()方法即可,传递参数值为true则设为阻塞模式,参数值为false值设为非阻塞模式。可以通过调用isBlocking()方法来判断某个socket通道当前处于哪种模式。

AbstractSelectableChannel.java中实现的configureBlocking()方法如下:

    public final SelectableChannel configureBlocking(boolean block)
        throws IOException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if (blocking == block)
                return this;
            if (block && haveValidKeys())
                throw new IllegalBlockingModeException();
            implConfigureBlocking(block);
            blocking = block;
        }
        return this;
    }
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-06 16:04:04  更:2022-04-06 16:06:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:58:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码