Java IO(CS-Notes)

转载自https://github.com/CyC2018/CS-Notes/blob/master/notes/Java%20IO.md#%E4%B8%80%E6%A6%82%E8%A7%88


 

目录

1  概览

2  磁盘操作

3  字节操作

3.1  实现文件复制

3.2  装饰者模式

4  字符操作

4.1  编码与解码

4.2  String 的编码方式

4.3  Reader 与 Writer

4.4  实现逐行输出文本文件的内容

5  对象操作

5.1  序列化

5.2  Serializable

ObjectOutputStream

 ObjectInputStream

5.3  transient

6  网络操作

6.1  InetAddress

6.2  URL

6.3  Sockets

6.4  Datagram

7  NIO

7.1  流与块

7.2  通道与缓冲区

1. 通道

2. 缓冲区

2.3  缓冲区状态变量

2.4  文件 NIO 实例

2.5  选择器

1. 创建 选择器

2. 将 通道 注册到选择器上

3. 监听事件

4. 获取到达的事件

5. 事件循环

2.6  套接字 NIO 实例

2.7  内存映射文件

2.8  对比

8  参考资料

微信公众号


1  概览

Java 的 I/O 大概可以分成以下几类:

  • 磁盘操作:File
  • 字节操作:InputStream OutputStream
  • 字符操作:Reader Writer
  • 对象操作:Serializable
  • 网络操作:Socket
  • 新的输入/输出:NIO

 

2  磁盘操作

File 类 可以用于表示文件和目录的信息,但是它不表示文件的内容

递归地列出一个目录下所有文件:

public static void listAllFiles(File dir) {
    if (dir == null || !dir.exists()) {
        return;
    }
    if (dir.isFile()) {
        System.out.println(dir.getName());
        return;
    }
    for (File file : dir.listFiles()) {
        listAllFiles(file);
    }
}

从 Java7 开始,可以使用 Paths 和 Files 代替 File。

 

代码测试如下:

import java.io.File;

public class FileTest {

    /**
     * 递归地列出一个目录下所有文件
     * @param dir File类型的对象
     */
    public static void listAllFiles(File dir) {
        if (dir == null || !dir.exists()) {
            return;
        }
        if (dir.isFile()) {
            System.out.println(dir.getName());
            return;
        }
        for (File file : dir.listFiles()) {
            listAllFiles(file);
        }
    }

    public static void main(String[] args) {
        listAllFiles(new File("D:\\Program Files (x86)\\JetBrains\\IntelliJ IDEA Community Edition 2017.3.2"));    //注意路径要转义
    }
}

 

3  字节操作

3.1  实现文件复制

public static void copyFile(String src, String dist) throws IOException {
    FileInputStream in = new FileInputStream(src);
    FileOutputStream out = new FileOutputStream(dist);

    byte[] buffer = new byte[20 * 1024];
    int cnt;

    // read() 最多读取 buffer.length 个字节
    // 返回的是实际读取的个数
    // 返回 -1 的时候表示读到 eof,即文件尾
    while ((cnt = in.read(buffer, 0, buffer.length)) != -1) {
        out.write(buffer, 0, cnt);
    }

    in.close();
    out.close();
}

代码测试如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyFileTest {

    public static void copyFile(String src, String dist) throws IOException {
        FileInputStream in = new FileInputStream(src);
        FileOutputStream out = new FileOutputStream(dist);

        byte[] buffer = new byte[20 * 1024];
        int cnt;

        // read() 最多读取 buffer.length 个字节
        // 返回的是实际读取的个数
        // 返回 -1 的时候表示读到 eof,即文件尾
        while ((cnt = in.read(buffer, 0, buffer.length)) != -1) {
            out.write(buffer, 0, cnt); //将file1的数据读取到字节数组buffer,然后将buffer里的字节写入到file2
        }

        in.close();  //释放资源
        out.close();
    }


    public static void main(String[] args) {
        try {
            copyFile("D:\\Program Files (x86)\\JetBrains\\IOTest\\file1.txt","D:\\Program Files (x86)\\JetBrains\\IOTest\\file2.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

结果如下,file1的内容被复制到了file2


 

3.2  装饰者模式

Java I/O 使用了装饰者模式来实现。以 InputStream 为例,

  • InputStream 是抽象组件;
  • FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作
  • FilterInputStream 属于抽象装饰者装饰者 用于 装饰组件为组件提供额外的功能。例如 BufferedInputStream(装饰者) FileInputStream(被装饰者装饰的具体组件) 提供缓存的功能。

实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。

FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); //用FileInputStream对象作为参数,实例化一个BufferedInputStream对象。

DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。


 

4  字符操作

4.1  编码与解码

编码就是把字符转换为字节,而解码是把字节重新组合成字符

如果编码和解码过程使用不同的编码方式那么就出现了乱码。

  • GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
  • UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
  • UTF-16be 编码中,中文字符和英文字符都占 2 个字节

UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。

Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储

 

4.2  String 的编码方式

String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。

String str1 = "中文";
byte[] bytes = str1.getBytes("UTF-8");  //指定一个编码方式,将String编码为字节数组
String str2 = new String(bytes, "UTF-8");  //指定一个编码方式,将一个字节数组 解码为 String
System.out.println(str2);

在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码getBytes() 的默认编码方式与平台有关,一般为 UTF-8

byte[] bytes = str1.getBytes();  //无参数的getBytes()的默认编码方式与平台有关,一般为 UTF-8

 

4.3  Reader 与 Writer

不管是磁盘还是网络传输最小的存储单元都是字节,而不是字符。但是在程序中操作的 通常是字符形式的数据,因此需要提供对字符进行操作的方法

  • InputStreamReader 实现从字节流解码成字符流
  • OutputStreamWriter 实现字符流编码成为字节流

 

 

4.4  实现逐行输出文本文件的内容

https://www.runoob.com/java/java-filereader.html

FileReader类从InputStreamReader类继承而来。该类 按字符读取 流中数据。

public static void readFileContent(String filePath) throws IOException {

    FileReader fileReader = new FileReader(filePath);  //获取 字符流
    BufferedReader bufferedReader = new BufferedReader(fileReader);  //具有缓存功能的 字符流对象

    String line;
    while ((line = bufferedReader.readLine()) != null) {  //循环从字符流中读取一行
        System.out.println(line);
    }

    // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象
    // 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法
    // 因此只要一个 close() 调用即可
    bufferedReader.close();  /* 虽然上面开启的两个流,但是只需要调用一个close */
}

具体可以看我的博客:

https://blog.youkuaiyun.com/Rex_WUST/article/details/96431496 


 

5  对象操作

5.1  序列化

序列化就是将一个对象转换成字节序列,方便存储和传输。

  • 序列化:ObjectOutputStream.writeObject()
  • 反序列化:ObjectInputStream.readObject()

不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态

 

5.2  Serializable

序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现Serializable接口 而去进行序列化,会抛出异常。

public static void main(String[] args) throws IOException, ClassNotFoundException {

    A a1 = new A(123, "abc");
    String objectFile ="D:\\Program Files (x86)\\JetBrains\\IOTest\\serializable.txt";

    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
    objectOutputStream.writeObject(a1); //序列化
    objectOutputStream.close();

    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile));
    A a2 = (A) objectInputStream.readObject();  //反序列
    objectInputStream.close();
    System.out.println(a2);
}

private static class A implements Serializable {

    private int x;
    private String y;

    A(int x, String y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString() {
        return "x = " + x + "  " + "y = " + y;
    }
}

ObjectOutputStream

An ObjectOutputStream writes primitive data types and graphs of Java objects to an OutputStream.  The objects can be read (reconstituted) using an ObjectInputStream.  Persistent storage of objects can be accomplished by using a file for the stream.  If the stream is a network socket stream, the objects can be reconstituted on another host or in another process.
ObjectOutputStream 将原始数据类型和Java对象的图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。对象的持久存储可以通过使用流的文件来完成。如果流是network socket stream,则可以在另一台主机或另一个进程中重新构建对象。

Only objects that support the java.io.Serializable interface can be written to streams.  The class of each serializable object is encoded including the class name and signature of the class, the values of the object's fields and arrays, and the closure of any other objects referenced from the initial objects.
只有支持java.io.serializable接口的对象才能写入流。对每个可序列化对象的类进行编码,包括类的类名和签名、对象字段和数组的值以及从初始对象引用的任何其他对象的闭包。

The method writeObject is used to write an object to the stream.  Any object, including Strings and arrays, is written with writeObject. Multiple objects or primitives can be written to the stream.  The objects must be read back from the corresponding ObjectInputstream with the same types and in the same order as they were written.
WriteObject方法 用于将对象写入流。任何对象,包括字符串和数组,都是用WriteObject编写的。可以将多个对象或原语写入流。必须从相应的objectinputstream中读取对象,这些对象的类型和写入顺序必须相同。 必须从相应的ObjectInputstream中读取对象,这些对象具有与写入时相同的类型和顺序。

Primitive data types can also be written to the stream using the appropriate methods from DataOutput. Strings can also be written using the writeUTF method.
也可以使用DataOutput中的适当方法将原始数据类型写入流中。 也可以使用writeUTF方法编写字符串。

The default serialization mechanism for an object writes the class of the object, the class signature, and the values of all non-transient and non-static fields.  References to other objects (except in transient or static fields) cause those objects to be written also. Multiple references to a single object are encoded using a reference sharing mechanism so that graphs of objects can be restored to the same shape as when the original was written.

对象的默认序列化机制写入对象的类、类签名以及所有非暂时字段和非静态字段的值。对其他对象的引用(瞬态或静态字段除外)也会导致这些对象被写入。使用引用共享机制对对单个对象的多个引用进行编码,以便可以将对象图形恢复为与写入原始图像时相同的形状。 

 ObjectInputStream

An ObjectInputStream deserializes primitive data and objects previously written using an ObjectOutputStream.
ObjectInputStream对先前使用ObjectOutputStream编写的基元数据和对象进行反序列化。

ObjectOutputStream and ObjectInputStream can provide an application with persistent storage for graphs of objects when used with a FileOutputStream and FileInputStream respectively.  ObjectInputStream is used to recover those objects previously serialized. Other uses include passing objects between hosts using a socket stream or for marshaling and unmarshaling arguments and parameters in a remote communication system.
当与FileOutputStream和FileInputStream一起使用时,ObjectOutputStream和ObjectInputStream可以为对象图提供持久存储的应用程序。 ObjectInputStream用于恢复以前序列化的对象。 其他用途包括使用套接字流在主机之间传递对象,或者用于在远程通信系统中编组和解组参数和参数。

ObjectInputStream ensures that the types of all objects in the graph created from the stream match the classes present in the Java Virtual Machine.  Classes are loaded as required using the standard mechanisms.
ObjectInputStream确保从流创建的图中的所有对象的类型与Java虚拟机中存在的类相匹配。 使用标准机制根据需要加载类。

Only objects that support the java.io.Serializable or java.io.Externalizable interface can be read from streams.
只能从流中读取支持java.io.Serializable或java.io.Externalizable接口的对象。

The method <code>readObject</code> is used to read an object from the stream.  Java's safe casting should be used to get the desired type.  In Java, strings and arrays are objects and are treated as objects during serialization. When read they need to be cast to the expected type.
方法<code> readObject </ code>用于从流中读取对象。 应该使用Java的安全转换来获得所需的类型。 在Java中,字符串和数组是对象,在序列化期间被视为对象。 读取时,需要将它们转换为预期的类型。

Primitive data types can be read from the stream using the appropriate method on DataInput.
可以使用DataInput上的适当方法从流中读取原始数据类型。

The default deserialization mechanism for objects restores the contents of each field to the value and type it had when it was written.  Fields declared as transient or static are ignored by the deserialization process. References to other objects cause those objects to be read from the stream as necessary.  Graphs of objects are restored correctly using a reference sharing mechanism.  New objects are always allocated when deserializing, which prevents existing objects from being overwritten.
对象的默认反序列化机制将每个字段的内容恢复为其写入时的值和类型。 反序列化过程将忽略声明为transient或static的字段。 对其他对象的引用会导致必要时从流中读取这些对象。 使用参考共享机制正确恢复对象图。 在反序列化时始终会分配新对象,这会阻止已经存在的对象被覆盖。

Reading an object is analogous to running the constructors of a new object.  Memory is allocated for the object and initialized to zero (NULL). No-arg constructors are invoked for the non-serializable classes and then the fields of the serializable classes are restored from the stream starting with the serializable class closest to java.lang.object and finishing with the object's most specific class.

读取对象类似于运行新对象的构造函数。 为对象分配内存并初始化为零(NULL)。 对非序列化类调用no-arg构造函数,然后从最接近java.lang.object的可序列化类开始从流中恢复可序列化类的字段,并以对象的最特定类结束。

5.3  transient

transient 关键字可以使一些属性不会被序列化

ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化反序列化方法,使得可以只序列化数组中有内容的那部分数据

private transient Object[] elementData;

 

6  网络操作

Java 中的网络支持:

  • InetAddress:用于表示网络上的硬件资源,即 IP 地址
  • URL:统一资源定位符;
  • Sockets:使用 TCP 协议实现网络通信;
  • Datagram:使用 UDP 协议实现网络通信。

6.1  InetAddress

没有公有的构造函数,只能通过静态方法来创建实例。

InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] address);

 

6.2  URL

可以直接从 URL 中,读取字节流数据。

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

    URL url = new URL("http://www.baidu.com");

    /* 字节流 */
    InputStream is = url.openStream();

    /* 字符流 */
    InputStreamReader isr = new InputStreamReader(is, "utf-8");

    /* 提供缓存功能 */
    BufferedReader br = new BufferedReader(isr);

    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }

    br.close();
}

 

 

6.3  Sockets

  • ServerSocket:服务器端类
  • Socket:客户端类
  • 服务器和客户端通过 InputStream OutputStream 进行输入输出。

 

6.4  Datagram

  • DatagramSocket:通信类
  • DatagramPacket:数据包类

 

7  NIO

新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。

java.nio全称java non-blocking IO

7.1  流与块

I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O的方式处理数据,而 NIO的方式处理数据。

面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O  通常相当慢

面向块的 I/O 一次处理一个数据块按块处理数据按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。

I/O 包和 NIO 已经很好地集成了,java.io.* 已经以 NIO 为基础重新实现了,所以现在I/O 也可以利用 NIO 的一些特性。例如,java.io.* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中处理速度也会更快

 

7.2  通道与缓冲区

1. 通道

Channel,中文意思“通道”,表示IO源与目标打开的连接,类似于传统的“流”。但是Channel不能直接访问数据,需要和缓冲区buffer进行交互。

通道 Channel 原 I/O 包中的流的模拟。可以通过通道 读取和写入数据

通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写

通道包括以下类型:

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

 

2. 缓冲区

发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区

缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程

缓冲区包括以下类型:

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

 

2.3  缓冲区状态变量

  • capacity:最大容量;
  • position:当前已经读写的字节数;
  • limit:还可以读写的字节数。

状态变量的改变过程举例:

① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。

② 从输入通道读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。

③ 在将缓冲区的数据 写到 输出通道 之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0

从缓冲区中取 4 个字节输出缓冲中,此时 position 设为 4。

最后需要调用 clear() 方法来清空缓冲区,此时 position limit 都被设置为最初位置

 

2.4  文件 NIO 实例

以下展示了使用 NIO 快速复制文件的实例:

public static void fastCopy(String src, String dist) throws IOException {

    /* 获得 源文件的输入字节流 */
    FileInputStream fin = new FileInputStream(src);

    /* 获取 输入字节流的文件通道 */
    FileChannel fcin = fin.getChannel();//输入通道

    /* 获取 目标文件的输出字节流 */
    FileOutputStream fout = new FileOutputStream(dist);

    /* 获取 输出字节流的文件通道 */
    FileChannel fcout = fout.getChannel();//输出通道

    /* 为缓冲区分配 1024 个字节 */
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);//缓冲区

    while (true) {

        /* 从输入通道中读取数据,到缓冲区中 */
        int r = fcin.read(buffer);

        /* read() 返回 -1 表示 EOF */
        if (r == -1) {
            break;
        }

        /* 切换读写 !!! */
        buffer.flip();

        /* 把缓冲区的内容,写入输出文件中。不能直接从输入通道直接把数据传到输出通道,数据必须经过缓冲区 */
        fcout.write(buffer);

        /* 清空缓冲区 */
        buffer.clear();
    }
}

FileChannel 

A channel for reading, writing, mapping, and manipulating a file.
用于读取,写入,映射和操作文件的通道。

A file channel is a {@link SeekableByteChannel} that is connected to a file. It has a current <i>position</i> within its file which can be both {@link #position() <i>queried</i>} and {@link #position(long) <i>modified</i>}.  The file itself contains a variable-length sequence of bytes that can be read and written and whose current {@link #size <i>size</i>} can be queried.  The size of the file increases when bytes are written beyond its current size; the size of the file decreases when it is {@link #truncate <i>truncated</i>}.  The file may also have some associated <i>metadata</i> such as access permissions, content type, and last-modification time; this class does not define methods for metadata access.

文件通道是连接到文件的{@link SeekableByteChannel}。 它在其文件中有一个当前position,可以是{@link #position()queried}和{@link #position(long)modified }。该文件本身包含一个可变长度的字节序列,可以读取和写入,并且可以查询其当前{@link #size size }。当写入的字节超过其当前大小时,文件的大小将增大;当文件为 {@link #truncate <i>truncated</i>}时,文件的大小将减小。该文件还可以具有一些关联的元数据,例如访问权限,内容类型和最后修改时间;此类未定义元数据访问的方法。

2.5  选择器

NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。

NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件

通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel找到 IO 事件已经到达的 Channel 执行。

因为创建和切换线程的开销很大,因此使用一个线程 来处理多个事件,而不是一个线程处理一个事件.对于 IO 密集型的应用具有很好地性能。

应该注意的是,只有 套接字 Channel 才能配置为非阻塞。 而 FileChannel 不能为 FileChannel 配置非阻塞也没有意义。

1. 创建 选择器

Selector selector = Selector.open();

 

 

2. 将 通道 注册到选择器上

ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);

通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了。因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件必须等待这个事件处理完毕才能去处理其它事件。显然这和选择器的作用背道而驰。

 

在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类:

  • SelectionKey.OP_CONNECT
  • SelectionKey.OP_ACCEPT
  • SelectionKey.OP_READ
  • SelectionKey.OP_WRITE

它们在 SelectionKey 的定义如下:

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;

可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

 

 

3. 监听事件

int num = selector.select();

使用 select() 来监听到达的事件,它会一直阻塞 直到有至少一个事件到达

Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if (key.isAcceptable()) {
        // ...
    } else if (key.isReadable()) {
        // ...
    }
    keyIterator.remove();
}

 

 

4. 获取到达的事件

Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if (key.isAcceptable()) {
        // ...
    } else if (key.isReadable()) {
        // ...
    }
    keyIterator.remove();
}

 

 

5. 事件循环

因为一次 select() 调用不能处理完 所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内

while (true) {
    int num = selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> keyIterator = keys.iterator();
    while (keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();
        if (key.isAcceptable()) {
            // ...
        } else if (key.isReadable()) {
            // ...
        }
        keyIterator.remove();
    }
}

 

 

2.6  套接字 NIO 实例

注意这个例子,NIOServer 和 NIOClient 需要在两个不同的IDEA工程里面分别运行。(不能在同一个工程里运行,因为一个工程一个时间点只能执行一个main()方法。)

public class NIOServer {

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

        Selector selector = Selector.open();

        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        ssChannel.configureBlocking(false);
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        ServerSocket serverSocket = ssChannel.socket();
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
        serverSocket.bind(address);

        while (true) {

            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();

            while (keyIterator.hasNext()) {

                SelectionKey key = keyIterator.next();

                if (key.isAcceptable()) {

                    ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();

                    // 服务器会为每个新连接创建一个 SocketChannel
                    SocketChannel sChannel = ssChannel1.accept();
                    sChannel.configureBlocking(false);

                    // 这个新连接主要用于从客户端读取数据
                    sChannel.register(selector, SelectionKey.OP_READ);

                } else if (key.isReadable()) {

                    SocketChannel sChannel = (SocketChannel) key.channel();
                    System.out.println(readDataFromSocketChannel(sChannel));
                    sChannel.close();
                }

                keyIterator.remove();
            }
        }
    }

    private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {

        ByteBuffer buffer = ByteBuffer.allocate(1024);
        StringBuilder data = new StringBuilder();

        while (true) {

            buffer.clear();
            int n = sChannel.read(buffer);
            if (n == -1) {
                break;
            }
            buffer.flip();
            int limit = buffer.limit();
            char[] dst = new char[limit];
            for (int i = 0; i < limit; i++) {
                dst[i] = (char) buffer.get(i);
            }
            data.append(dst);
            buffer.clear();
        }
        return data.toString();
    }
}

 

public class NIOClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        OutputStream out = socket.getOutputStream();
        String s = "hello world";
        out.write(s.getBytes());
        out.close();
    }
}

 

 

2.7  内存映射文件

内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多

向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件修改数据将数据保存到磁盘 是没有分开的。

下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区操作系统会在需要时 负责执行映射。

 

2.8  对比

NIO 与 普通 I/O 的区别主要有以下两点:

  • NIO 是非阻塞的;
  • NIO 面向,I/O 面向

 

8  参考资料

微信公众号

更多精彩内容将发布在微信公众号 CyC2018 上,你也可以在公众号后台和我交流学习和求职相关的问题。另外,公众号提供了该项目的 PDF 等离线阅读版本,后台回复 "下载" 即可领取。公众号也提供了一份技术面试复习大纲,不仅系统整理了面试知识点,而且标注了各个知识点的重要程度,从而帮你理清多而杂的面试知识点,后台回复 "大纲" 即可领取。我基本是按照这个大纲来进行复习的,对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习,就不用看很多不重要的内容,也可以知道哪些内容很重要从而多安排一些复习时间。

 

内容概要:本文是一篇关于使用RandLANet模型对SensatUrban数据集进行点云语义分割的实战教程,系统介绍了从环境搭建、数据准备、模型训练与测试到精度评估的完整流程。文章详细说明了在Ubuntu系统下配置TensorFlow 2.2、CUDA及cuDNN等深度学习环境的方法,并指导用户下载和预处理SensatUrban数据集。随后,逐步讲解RandLANet代码的获取与运行方式,包括训练、测试命令的执行与参数含义,以及如何监控训练过程中的关键指标。最后,教程涵盖测试结果分析、向官方平台提交结果、解读评估报告及可视化效果等内容,并针对常见问题提供解决方案。; 适合人群:具备一定深度学习基础,熟悉Python编程和深度学习框架,从事计算机视觉或三维点云相关研究的学生、研究人员及工程师;适合希望动手实践点云语义分割项目的初学者与进阶者。; 使用场景及目标:①掌握RandLANet网络结构及其在点云语义分割任务中的应用;②学会完整部署一个点云分割项目,包括数据处理、模型训练、测试与性能评估;③为参与相关竞赛或科研项目提供技术支撑。; 阅读建议:建议读者结合提供的代码链接和密码访问完整资料,在本地或云端环境中边操作边学习,重点关注数据格式要求与训练参数设置,遇到问题时参考“常见问题与解决技巧”部分及时排查。
内容概要:本文详细介绍了三相异步电机SVPWM-DTC(空间矢量脉宽调制-直接转矩控制)的Simulink仿真实现方法,结合DTC响应快与SVPWM谐波小的优点,构建高性能电机控制系统。文章系统阐述了控制原理,包括定子磁链观测、转矩与磁链误差滞环比较、扇区判断及电压矢量选择,并通过SVPWM技术生成固定频率PWM信号,提升系统稳态性能。同时提供了完整的Simulink建模流程,涵盖电机本体、磁链观测器、误差比较、矢量选择、SVPWM调制、逆变器驱动等模块的搭建与参数设置,给出了仿真调试要点与预期结果,如电流正弦性、转矩响应快、磁链轨迹趋圆等,并提出了模型优化与扩展方向,如改进观测器、自适应滞环、弱磁控制和转速闭环等。; 适合人群:电气工程、自动化及相关专业本科生、研究生,从事电机控制算法开发的工程师,具备一定MATLAB/Simulink和电机控制理论基础的技术人员。; 使用场景及目标:①掌握SVPWM-DTC控制策略的核心原理与实现方式;②在Simulink中独立完成三相异步电机高性能控制系统的建模与仿真;③通过仿真验证控制算法有效性,为实际工程应用提供设计依据。; 阅读建议:学习过程中应结合文中提供的电机参数和模块配置逐步搭建模型,重点关注磁链观测、矢量选择表和SVPWM调制的实现细节,仿真时注意滞环宽度与开关频率的调试,建议配合MATLAB官方工具箱文档进行参数校准与结果分析。
已经博主授权,源码转载自 https://pan.quark.cn/s/bf1e0d5b9490 本文重点阐述了Vue2.0多Tab切换组件的封装实践,详细说明了通过封装Tab切换组件达成多Tab切换功能,从而满足日常应用需求。 知识点1:Vue2.0多Tab切换组件的封装* 借助封装Tab切换组件,达成多Tab切换功能* 支持tab切换、tab定位、tab自动化仿React多Tab实现知识点2:TabItems组件的应用* 在index.vue文件中应用TabItems组件,借助name属性设定tab的标题* 通过:isContTab属性来设定tab的内容* 能够采用子组件作为tab的内容知识点3:TabItems组件的样式* 借助index.less文件来设定TabItems组件的样式* 设定tab的标题样式、背景色彩、边框样式等* 使用animation达成tab的切换动画知识点4:Vue2.0多Tab切换组件的构建* 借助运用Vue2.0框架,达成多Tab切换组件的封装* 使用Vue2.0的组件化理念,达成TabItems组件的封装* 通过运用Vue2.0的指令和绑定机制,达成tab的切换功能知识点5:Vue2.0多Tab切换组件的优势* 达成多Tab切换功能,满足日常应用需求* 支持tab切换、tab定位、tab自动化仿React多Tab实现* 能够满足多样的业务需求,具备良好的扩展性知识点6:Vue2.0多Tab切换组件的应用场景* 能够应用于多样的业务场景,例如:管理系统、电商平台、社交媒体等* 能够满足不同的业务需求,例如:多Tab切换、数据展示、交互式操作等* 能够与其它Vue2.0组件结合运用,达成复杂的业务逻辑Vue2.0多Tab切换组件的封装实例提供了...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值