Java高级程序设计

输入输出

I/O


https://docs.oracle.com/javase/tutorial/essential/io/index.html

Stream

A stream is a sequence of data.

  • A program uses an input stream to read data from a source, one item at a time.
  • A program uses an output stream to write data to a destination, one item at a time.

Java中的Stream分为Byte Streams和Character Streams两大类

Byte Streams

Programs use byte streams to perform input and output of 8-bit bytes. All byte stream classes are descended from InputStream and OutputStream.

InputStream

public abstract class InputStream
    extends Object
    implements Closeable 

This abstract class is the superclass of all classes representing an input stream of bytes.

Direct Known Subclasses:
AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream

OutputStream

public abstract class OutputStream
    extends Object
    implements Closeable, Flushable

This abstract class is the superclass of all classes representing an output stream of bytes. An output stream accepts output bytes and sends them to some sink.

Direct Known Subclasses:
ByteArrayOutputStream, FileOutputStream, FilterOutputStream, ObjectOutputStream, OutputStream, PipedOutputStream

看个例子

public static void main(String[] args) throws IOException {
    FileInputStream in = null;
    FileOutputStream out = null;
    try {
        in = new FileInputStream("xanadu.txt");
        out = new FileOutputStream("outagain.txt");
        int c;
        while ((c = in.read()) != -1) {
            out.write(c);
        }
    } finally {
        if (in != null) {
            in.close(); //记得关
        }
        if (out != null) {
            out.close();//记得关
        }
    }
}

示意图

Character Streams

The Java platform stores character values using Unicode conventions. Character stream I/O automatically translates this internal format to and from the local character set. In Western locales, the local character set is usually an 8-bit superset of ASCII.

All character stream classes are descended from Reader and Writer.

Reader

public abstract class Reader
    extends Object
    implements Readable, Closeable

Abstract class for reading character streams.

Direct Known Subclasses:
BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, StringReader

Writer

public abstract class Writer
    extends Object
    implements Appendable, Closeable, Flushable

Abstract class for writing to character streams.

Direct Known Subclasses:
BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, PipedWriter, PrintWriter, StringWriter

看个例子

public static void main(String[] args) throws IOException {
    try (FileReader inputStream = new FileReader("xanadu.txt");
         FileWriter outputStream = new FileWriter("characteroutput.txt")) {
        int c;
        while ((c = inputStream.read()) != -1) {
            outputStream.write(c);
        }
    }
}

差别与联系

Character streams are often "wrappers" for byte streams.

The character stream uses the byte stream to perform the physical I/O, while the character stream handles translation between characters and bytes. FileReader, for example, uses FileInputStream, while FileWriter uses FileOutputStream.

字符编码 (Charset & Encoding)

public static void main(String[] args) {
    String text = "你好世界";
    
    // 字符串编码
    byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
    
    // 字节解码
    String decoded = new String(bytes, StandardCharsets.UTF_8);
    
    System.out.println(decoded);  // 你好世界
}

常见乱码问题

public static void main(String[] args) {
    String text = "你好世界";
    // 错误:使用系统默认编码
    byte[] wrong = text.getBytes();  // 可能使用GBK
    // 错误:解码时使用错误编码
    String wrongDecoded = new String(wrong, StandardCharsets.ISO_8859_1);
    System.out.println(wrongDecoded);  // 乱码
    // 正确:明确指定UTF-8
    byte[] correct = text.getBytes(StandardCharsets.UTF_8);
    String correctDecoded = new String(correct, StandardCharsets.UTF_8);
    System.out.println(correctDecoded);  // 你好世界
}

字符串在内存中的形式

String text = "你好世界";

Java 8及之前:使用char[]数组存储,每个char占2字节(UTF-16编码),每个中文字符占2字节

Java 9+:使用byte[]数组存储,根据字符内容选择编码,并通过coder标志区分编码方式:

  • ASCII字符(0-255):使用Latin-1编码,1字节
  • 中文等Unicode字符:使用UTF-16编码,2字节

关键点

  • Java 8及之前:内部始终使用UTF-16编码
  • Java 9+:内部根据字符内容选择Latin-1或UTF-16编码
  • 与外部编码(UTF-8、GBK等)无关

Java 8及之前的内存布局

String text = "你好世界";

String对象
├── char[] value = ['\u4f60', '\u597d', '\u4e16', '\u754c']  // 你=0x4F60, 好=0x597D, 世=0x4E16, 界=0x754C
├── int offset = 0
├── int count = 4
└── int hash = ...

内存中的字节表示(每个char占2字节):
[0x4F, 0x60, 0x59, 0x7D, 0x4E, 0x16, 0x75, 0x4C]  // 总共8字节

Java 9+的内存布局

包含中文的字符串(使用UTF-16):

String text = "你好世界";

String对象
├── byte[] value = [0x4F, 0x60, 0x59, 0x7D, 0x4E, 0x16, 0x75, 0x4C]  // UTF-16编码的字节
├── byte coder = UTF16 (0)  // 标志:0=UTF16, 1=Latin1
└── int hash = ...

内存中的字节表示:
[0x4F, 0x60, 0x59, 0x7D, 0x4E, 0x16, 0x75, 0x4C]  // 总共8字节(每个字符2字节)

Java 9+的内存布局

只包含ASCII的字符串(使用Latin-1):

String text = "Hello";

String对象
├── byte[] value = [0x48, 0x65, 0x6C, 0x6C, 0x6F]  // Latin-1编码的字节
├── byte coder = LATIN1 (1)  // 标志:0=UTF16, 1=Latin1
└── int hash = ...

内存中的字节表示:
[0x48, 0x65, 0x6C, 0x6C, 0x6F]  // 总共5字节(每个字符1字节)

编码转换的必要性

String text = "你好世界";
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);

原因

  1. String内部是UTF-16:String对象内部使用UTF-16编码
  2. 外部需要不同编码:网络传输、文件存储等需要UTF-8、GBK等
  3. 编码转换:需要将UTF-16转换为目标编码
  4. 不指定编码的风险:使用系统默认编码,可能在不同环境不一致

所以为什么使用错误编码会导致问题?

String text = "你好世界";
// 编码:UTF-8
byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8);
// 错误:使用GBK解码UTF-8编码的字节
String wrong = new String(utf8Bytes, Charset.forName("GBK"));
System.out.println(wrong);  // 乱码:浣犲ソ涓栫晫
  1. 编码规则不同:UTF-8和GBK对同一字符的编码不同
  2. 字节长度不同:UTF-8中文字符3字节,GBK中文字符2字节
  3. 解码错误:用错误编码解码会导致字节解析错误
  4. 字符映射错误:错误的字节序列映射到错误的字符

UTF-8

代码单元:8位(1字节),采用变长编码:1-4字节

  • "你" = U+4F60 → UTF-8: 0xE4 0xBD 0xA0 (3字节)
  • "H" = U+0048 → UTF-8: 0x48 (1字节)
Unicode码点范围 UTF-8编码长度
0x0000 - 0x007F (ASCII) 1字节
0x0080 - 0x07FF 2字节
0x0800 - 0xFFFF 3字节
0x10000 - 0x10FFFF 4字节

UTF-16

代码单元:16位(2字节)变长编码:2字节或4字节

  • "你" = U+4F60 → UTF-16: 0x4F60 (2字节)
  • "H" = U+0048 → UTF-16: 0x0048 (2字节)

编码规则

Unicode码点范围 UTF-16编码长度
0x0000 - 0xFFFF (BMP) 2字节
0x10000 - 0x10FFFF 4字节(代理对)

GBK

  • 中文编码标准:GB2312的扩展
  • 只支持中文和ASCII:不支持其他语言
    • "你" = GBK: 0xC4 0xE3 (2字节)
    • "H" = GBK: 0x48 (1字节)

编码规则

字符类型 GBK编码长度
ASCII字符 1字节
中文字符 2字节

三种编码对比

编码 ASCII字符 中文字符 优势
UTF-8 1字节 3字节 网络传输友好,ASCII兼容
UTF-16 2字节 2字节 中文更省空间,Java内部使用
GBK 1字节 2字节 中文省空间,但只支持中文

选择建议

  • 网络传输、文件存储:使用UTF-8(兼容性好)
  • Java内部:使用UTF-16(Java String默认)
  • 中文环境:可以使用GBK(节省空间,但兼容性差)

性能考虑

Most of the examples we've seen so far use unbuffered I/O. This means each read or write request is handled directly by the underlying OS.

This can make a program much less efficient, since each such request often triggers disk access, network activity, or some other operation that is relatively expensive.

Buffered Streams

Buffered input streams read data from a memory area known as a buffer; the native input API is called only when the buffer is empty. Similarly, buffered output streams write data to a buffer, and the native output API is called only when the buffer is full.

  • BufferedInputStream and BufferedOutputStream create buffered byte streams
  • BufferedReader and BufferedWriter create buffered character streams

Stream wrapping

A program can convert an unbuffered stream into a buffered stream using the wrapping idiom we've used several times now, where the unbuffered stream object is passed to the constructor for a buffered stream class.

inputStream = new BufferedReader(new FileReader("xanadu.txt"));
outputStream = new BufferedWriter(new FileWriter("characteroutput.txt"));

对应的软件工程概念:Decorator Pattern

public static void main(String[] args) throws IOException {
    // 装饰器模式:多层包装
    InputStream fileStream = new FileInputStream("data.txt");
    InputStream bufferedStream = new BufferedInputStream(fileStream);
    DataInputStream dataStream = new DataInputStream(bufferedStream);
    
    // 每层装饰器添加新功能
    // FileInputStream: 基础文件读取
    // BufferedInputStream: 添加缓冲功能
    // DataInputStream: 添加数据类型读取
    
    int value = dataStream.readInt();
    String text = dataStream.readUTF();
    
    dataStream.close();  // 关闭时只需要关闭最外层
}

缓冲策略

public static void main(String[] args) throws IOException {
    // 1. 选择合适的缓冲区大小
    int bufferSize = 8192; // 8KB,通常是最优大小

    // 2. 使用缓冲流
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("largefile.txt"), bufferSize);

    // 3. 批量读取
    byte[] buffer = new byte[bufferSize];
    int bytesRead;
    while ((bytesRead = bis.read(buffer)) != -1) {
        // 处理数据
    }
    
    bis.close();
}

行终止符的跨平台问题

字符IO通常以比单个字符更大的单位进行操作。一个常见的单位是行(line):以行终止符结尾的字符串。

操作系统 行终止符 表示方式
Windows 回车+换行 "\r\n"
Unix/Linux/macOS 换行 "\n"
旧版Mac 回车 "\r"

为什么需要支持所有行终止符?

支持所有可能的行终止符,可以让程序读取在不同操作系统上创建的文件,实现跨平台兼容。

BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();  // 自动识别并处理所有行终止符
  • BufferedReader.readLine() 方法会自动识别和处理所有类型的行终止符
  • 返回的字符串不包含行终止符
  • 无需手动处理不同操作系统的差异

管道流 (PipedInputStream/OutputStream)

管道流用于在线程间传输数据,实现生产者-消费者模式。

public static void main(String[] args) throws IOException {
    PipedInputStream pipedInput = new PipedInputStream();
    PipedOutputStream pipedOutput = new PipedOutputStream();
    pipedInput.connect(pipedOutput);  // 连接管道
    
    // 生产者线程:写入数据
    Thread producer = new Thread(() -> {
        try {
            pipedOutput.write("Hello\n".getBytes());
            pipedOutput.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    
    // 消费者线程:读取数据
    Thread consumer = new Thread(() -> {
        try {
            int data;
            while ((data = pipedInput.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    
    producer.start();
    consumer.start();
}

PushbackInputStream

支持回退(pushback)机制的输入流,可以将已读取的字节重新放回流中。

public static void main(String[] args) throws IOException {
    String data = "Hello";
    ByteArrayInputStream bais = new ByteArrayInputStream(data.getBytes());
    PushbackInputStream pushback = new PushbackInputStream(bais);
    
    // 读取字符
    int ch = pushback.read();
    System.out.println("Read: " + (char) ch);  // H
    
    // 回退字符
    pushback.unread(ch);
    
    // 再次读取(读取到刚才回退的字符)
    ch = pushback.read();
    System.out.println("Read again: " + (char) ch);  // H
}

Scanning and Formatting

Programming I/O often involves translating to and from the neatly formatted data humans like to work with. To assist you with these chores, the Java platform provides two APIs. The scanner API breaks input into individual tokens associated with bits of data. The formatting API assembles data into nicely formatted, human-readable form.

Scanning

Objects of type Scanner are useful for breaking down formatted input into tokens and translating individual tokens according to their data type.

https://docs.oracle.com/javase/8/docs/api/java/util/Scanner.html

看个例子

public static void main(String[] args) throws IOException {
    Scanner s = null;
    try {
        s = new Scanner(new BufferedReader(new FileReader("xanadu.txt")));
        while (s.hasNext()) {
            System.out.println(s.next());
        }
    } finally {
        if (s != null) {
            s.close();
        }
    }
}

Formatting

Stream objects that implement formatting are instances of either PrintWriter, a character stream class, or PrintStream, a byte stream class.

System

public final class System extends Object

The System class contains several useful class fields (Standard Streams) and methods.

你最喜欢的System.out

public static final PrintStream out

The "standard" output stream. This stream is already open and ready to accept output data. Typically this stream corresponds to display output or another output destination specified by the host environment or user.

System.out.println(data)

https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#out

The format Method

public static void main(String[] args) {
    int i = 2;
    double r = Math.sqrt(i);
    
    System.out.format("The square root of %d is %f.%n", i, r);
}
public static void main(String[] args) {
    System.out.format("%f, %1$+020.10f %n", Math.PI);
}

System.in

public static final InputStream in

The "standard" input stream. This stream is already open and ready to supply input data. Typically this stream corresponds to keyboard input or another input source specified by the host environment or user.

Console

public static void main(String[] args) throws IOException {
    Console c = System.console();
    if (c == null) {
        System.err.println("No console.");
        System.exit(1);
    }

    String login = c.readLine("Enter your login: ");
    char[] oldPassword = c.readPassword("Enter your old password: ");
}

Data Streams

Data streams support binary I/O of primitive data type values (boolean, char, byte, short, int, long, float, and double) as well as String values. All data streams implement either the DataInput interface or the DataOutput interface.

Object Streams

Just as data streams support I/O of primitive data types, object streams support I/O of objects. Most, but not all, standard classes support serialization of their objects. Those that do implement the marker interface Serializable.

The object stream classes are ObjectInputStream and ObjectOutputStream. These classes implement ObjectInput and ObjectOutput, which are subinterfaces of DataInput and DataOutput. That means that all the primitive data I/O methods covered in Data Streams are also implemented in object streams.

Java Object Serialization/Deserialization

Serialization is the conversion of the state of an object into a byte stream; deserialization does the opposite. Stated differently, serialization is the conversion of a Java object into a static stream (sequence) of bytes which can then be saved to a database or transferred over a network.

https://www.baeldung.com/java-serialization

看个例子

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    static String country = "ITALY";
    private int age;
    private String name;
    transient int height;

    // getters and setters
}
对象状态保持
public void whenSerializingAndDeserializing_ThenObjectIsTheSame() 
    throws IOException, ClassNotFoundException { 
    Person person = new Person();
    person.setAge(20);
    person.setName("Joe");
    
    FileOutputStream fileOutputStream = new FileOutputStream("yourfile.txt");
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
    objectOutputStream.writeObject(person);
    objectOutputStream.close();
    
    FileInputStream fileInputStream = new FileInputStream("yourfile.txt");
    ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
    Person p2 = (Person) objectInputStream.readObject();
    objectInputStream.close(); 
}

Custom Serialization

public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private transient Address address;
    private Person person;
    // setters and getters
    private void writeObject(ObjectOutputStream oos)  throws IOException {
        oos.defaultWriteObject();
        oos.writeObject(address.getHouseNumber());
    }

    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        Integer houseNumber = (Integer) ois.readObject();
        Address a = new Address();
        a.setHouseNumber(houseNumber);
        this.setAddress(a);
    }
}
public class Address { private int houseNumber; }

File

public class File
    extends Object
    implements Serializable, Comparable<File>

An abstract representation of file and directory pathnames.

The File class gives us the ability to work with files and directories on the file system.

https://www.baeldung.com/java-io-file

RandomAccessFile

public class RandomAccessFile
    extends Object implements DataOutput, DataInput, Closeable

Instances of this class support both reading and writing to a random access file.

https://docs.oracle.com/javase/8/docs/api/java/io/RandomAccessFile.html

简单例子

try{
    RandomAccessFile file=new RandomAccessFile("xanadu.txt","rw");
    file.seek(file.length()); 
    file.writeBytes(s+"\n");
    file.close();
} catch(IOException e) {
    e.printStackTrace();
}

getChannel for RandomAccessFile

public final FileChannel getChannel()

Returns the unique FileChannel object associated with this file.

Returns: the file channel associated with this file
Since: 1.4

https://docs.oracle.com/javase/8/docs/api/java/io/RandomAccessFile.html#getChannel--

FileChannel

public abstract class FileChannel
extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel

A channel for reading, writing, mapping, and manipulating a file.

A file channel can also be obtained from an existing FileInputStream, FileOutputStream, or RandomAccessFile object by invoking that object's getChannel method, which returns a file channel that is connected to the same underlying file.

Java IO vs NIO

The java.io package was introduced in Java 1.0. It provides:

  • InputStream and OutputStream and Reader and Writer
  • blocking mode – to wait for a complete message

The java.nio package was introduced in Java 1.4 and updated in Java 1.7 (NIO.2). It provides:

  • non-blocking mode – to read whatever is ready

Channels and Buffers

Java NIO: Channels read data into Buffers, and Buffers write data into Channels

http://tutorials.jenkov.com/java-nio/channels.html

看个例子

RandomAccessFile file = new RandomAccessFile("file.txt", "rw");
FileChannel channel = file.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);

int bytesRead = channel.read(buffer);
while (bytesRead != -1) {
    buffer.flip();  // 准备读取
    while (buffer.hasRemaining()) {
        System.out.print((char) buffer.get());
    }
    buffer.clear();  // 准备写入
    bytesRead = channel.read(buffer);
}
file.close();

Channels

Here are the most important Channel implementations in Java NIO:

  • The FileChannel reads data from and to files.
  • The DatagramChannel can read and write data over the network via UDP.
  • The SocketChannel can read and write data over the network via TCP.
  • The ServerSocketChannel allows you to listen for incoming TCP connections, like a web server does. For each incoming connection a SocketChannel is created.

Buffers

Java NIO Buffers are used when interacting with NIO Channels. As you know, data is read from channels into buffers, and written from buffers into channels.

A buffer is essentially a block of memory into which you can write data, which you can then later read again. This memory block is wrapped in a NIO Buffer object, which provides a set of methods that makes it easier to work with the memory block.

Buffer Usage

Using a Buffer to read and write data typically follows this little 4-step process:

  1. Write data into the Buffer
  2. Call buffer.flip()
  3. Read data out of the Buffer
  4. Call buffer.clear() or buffer.compact()

Buffer Capacity, Position and Limit

The meaning of position and limit depends on whether the Buffer is in read or write mode. Capacity always means the same, no matter the buffer mode.

NIO Selector

The Java NIO Selector is a component which can examine one or more Java NIO Channel instances, and determine which channels are ready for e.g. reading or writing. This way a single thread can manage multiple channels, and thus multiple network connections.

网络IO (SocketChannel) - TCP客户端

SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress("localhost", 8080));

// 发送数据
ByteBuffer buffer = ByteBuffer.wrap("Hello Server".getBytes());
channel.write(buffer);

// 接收数据
buffer.clear();
channel.read(buffer);
buffer.flip();
while (buffer.hasRemaining()) {
    System.out.print((char) buffer.get());
}

channel.close();

ServerSocketChannel - TCP服务器

ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.bind(new InetSocketAddress(8080));

while (true) {
    SocketChannel clientChannel = serverChannel.accept();
    
    // 读取客户端数据
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    clientChannel.read(buffer);
    
    // 响应客户端
    buffer.clear();
    buffer.put("Hello Client".getBytes());
    buffer.flip();
    clientChannel.write(buffer);
    
    clientChannel.close();
}

DatagramChannel - UDP通信

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

// 接收数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.receive(buffer);

// 发送数据
buffer.clear();
buffer.put("Hello UDP".getBytes());
buffer.flip();
channel.send(buffer, new InetSocketAddress("localhost", 8888));

channel.close();

IO vs NIO详细对比

特性 IO NIO
阻塞模式 阻塞 非阻塞
线程模型 一个连接一个线程 一个线程多个连接
缓冲区 Stream(无缓冲区) Buffer(有缓冲区)
选择器 Selector
适用场景 连接数少 连接数多
性能 连接数少时好 连接数多时好

IO vs NIO性能对比 - 传统IO(阻塞)

public void handleClient(Socket socket) throws IOException {
    InputStream input = socket.getInputStream();
    OutputStream output = socket.getOutputStream();
    
    // 阻塞:等待数据
    int data = input.read();
    
    // 处理数据
    output.write(data);
}
// 每个客户端需要一个线程

IO vs NIO性能对比 - NIO(非阻塞)

public void handleClient(Selector selector) throws IOException {
    selector.select(); // 非阻塞:检查就绪的通道
    
    Set<SelectionKey> keys = selector.selectedKeys();
    for (SelectionKey key : keys) {
        if (key.isReadable()) {
            // 处理读取
        }
        if (key.isWritable()) {
            // 处理写入
        }
    }
}
// 一个线程可以处理多个客户端

NIO.2 (Path & Files API)

NIO.2(New I/O 2)是Java 7引入的现代化文件IO API,是对传统java.io.File的改进。

  • Path接口:跨平台路径表示,替代File
  • Files类:提供丰富的文件操作方法
  • WatchService:文件系统监控
  • 异步IO:支持异步文件操作
  • 符号链接支持:更好的符号链接处理

Path接口

public static void main(String[] args) {
    // 创建Path对象
    Path path = Paths.get("/home/user", "file.txt");
    
    // 路径操作
    Path parent = path.getParent();      // /home/user
    Path fileName = path.getFileName();  // file.txt
    
    // 路径组合
    Path resolved = path.resolve("subdir");  // /home/user/file.txt/subdir
    
    System.out.println("Path: " + path);
    System.out.println("Parent: " + parent);
    System.out.println("FileName: " + fileName);
}

Files类

public static void main(String[] args) throws Exception {
    Path path = Paths.get("file.txt");
    
    // 读取文件
    List<String> lines = Files.readAllLines(path);
    byte[] bytes = Files.readAllBytes(path);
    
    // 写入文件
    Files.write(path, "Hello World".getBytes());
    
    // 文件属性
    boolean exists = Files.exists(path);
    long size = Files.size(path);
}

WatchService - 文件系统监控

public static void main(String[] args) throws IOException, InterruptedException {
    Path dir = Paths.get(".");
    WatchService watchService = FileSystems.getDefault().newWatchService();
    
    dir.register(watchService, 
        StandardWatchEventKinds.ENTRY_CREATE,
        StandardWatchEventKinds.ENTRY_DELETE,
        StandardWatchEventKinds.ENTRY_MODIFY);
    
    while (true) {
        WatchKey key = watchService.take();
        
        for (WatchEvent<?> event : key.pollEvents()) {
            Kind<?> kind = event.kind();
            Path context = (Path) event.context();
            System.out.println("Event: " + kind + ", File: " + context);
        }
        boolean valid = key.reset();
        if (!valid) {
            break;
        }
    }
}

内存映射文件 (Memory-Mapped Files)

public static void main(String[] args) throws Exception {
    RandomAccessFile file = new RandomAccessFile("file.txt", "rw");
    FileChannel channel = file.getChannel();
    
    // 创建内存映射
    MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
    
    // 直接操作内存
    buffer.put("Hello".getBytes());
    buffer.force();  // 刷新到磁盘
    
    // 读取数据
    buffer.position(0);
    while (buffer.hasRemaining()) {
        System.out.print((char) buffer.get());
    }
}

内存映射文件的优势

特性 传统IO 内存映射
数据拷贝 需要多次拷贝 零拷贝
系统调用 每次读写都需要 映射时一次
性能 较慢 非常快
适用场景 小文件 大文件

主要使用场景:大文件处理、高性能数据库、日志文件读写、共享内存通信等

文件锁 (FileLock)

public static void main(String[] args) throws Exception {
    RandomAccessFile file = new RandomAccessFile("file.txt", "rw");
    FileChannel channel = file.getChannel();
    
    // 获取排他锁(整个文件)
    FileLock lock = channel.lock();
    try {
        // 执行文件操作
        file.write("Hello".getBytes());
    } finally {
        lock.release();  // 释放锁
    }
    
    // 获取区域锁(锁定前100字节)
    FileLock regionLock = channel.lock(0, 100, false);
    try {
        // 读取文件的前100字节
    } finally {
        regionLock.release();
    }
}

共享锁 vs 排他锁

// 排他锁(写锁)- 默认
FileLock exclusiveLock = channel.lock();  // 整个文件
FileLock exclusiveLock2 = channel.lock(0, 100, false);  // 区域锁

// 共享锁(读锁)
FileLock sharedLock = channel.lock(0, 100, true);  // 区域锁

// 检查锁类型
boolean isShared = sharedLock.isShared();  // 是否为共享锁

异步IO (AsynchronousFileChannel)

public static void main(String[] args) throws Exception {
    AsynchronousFileChannel channel = AsynchronousFileChannel.open(
        Paths.get("file.txt"), StandardOpenOption.READ);
    
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    Future<Integer> operation = channel.read(buffer, 0);
    
    // 主线程可以继续做其他事情
    int bytesRead = operation.get();  // 等待完成
    System.out.println("Bytes read: " + bytesRead);
}

使用CompletionHandler

public static void main(String[] args) throws Exception {
    AsynchronousFileChannel channel = AsynchronousFileChannel.open(
        Paths.get("file.txt"), StandardOpenOption.READ);
    
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    
    // 使用CompletionHandler回调
    channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
        @Override
        public void completed(Integer result, ByteBuffer attachment) {
            System.out.println("Bytes read: " + result);
        }
        
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
            System.err.println("Read failed: " + exc.getMessage());
        }
    });
    
    Thread.sleep(1000);  // 等待异步操作完成
}

IO Exceptions

public class IOException extends Exception
Direct Known Subclasses: ChangedCharSetException, CharacterCodingException, CharConversionException, ClosedChannelException, EOFException, FileLockInterruptionException, FileNotFoundException, FilerException, FileSystemException, HttpRetryException, IIOException, InterruptedByTimeoutException, InterruptedIOException, InvalidPropertiesFormatException, JMXProviderException, JMXServerErrorException, MalformedURLException, ObjectStreamException, ProtocolException, RemoteException, SaslException, SocketException, SSLException, SyncFailedException, UnknownHostException, UnknownServiceException, UnsupportedDataTypeException, UnsupportedEncodingException, UserPrincipalNotFoundException, UTFDataFormatException, ZipException

Try with Resources

The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.