Java I/O 流与文件操作完全指南:从基础到现代实践

引言:数据流动的艺术

在Java中,输入输出(I/O)操作是程序与外部世界沟通的桥梁。无论是读取配置文件、写入日志,还是处理用户上传的文件,I/O操作无处不在。理解Java的I/O体系,不仅能写出更高效的代码,还能避免许多常见的陷阱。

Java I/O 体系结构概览

I/O流的分类

Java I/O流主要分为两大类:

Java 复制代码
Java I/O 流
├── 按数据方向分
│   ├── 输入流 (InputStream/Reader)
│   └── 输出流 (OutputStream/Writer)
│
├── 按数据类型分
│   ├── 字节流 (处理二进制数据)
│   │   ├── InputStream
│   │   └── OutputStream
│   │
│   └── 字符流 (处理文本数据)
│       ├── Reader
│       └── Writer
│
└── 按功能分
    ├── 节点流 (直接连接数据源)
    └── 处理流/包装流 (装饰器模式,增强功能)

字节流操作:处理二进制数据

FileInputStream 和 FileOutputStream

Java 复制代码
public class ByteStreamExample {
    
    // 复制文件的基本方法(字节流)
    public static void copyFile(String sourcePath, String targetPath) throws IOException {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        
        try {
            fis = new FileInputStream(sourcePath);
            fos = new FileOutputStream(targetPath);
            
            byte[] buffer = new byte[8192]; // 8KB缓冲区
            int bytesRead;
            
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
            
            System.out.println("文件复制完成");
            
        } finally {
            // 确保资源被关闭
            if (fis != null) {
                fis.close();
            }
            if (fos != null) {
                fos.close();
            }
        }
    }
    
    // 使用try-with-resources改进版本
    public static void copyFileModern(String sourcePath, String targetPath) throws IOException {
        try (FileInputStream fis = new FileInputStream(sourcePath);
             FileOutputStream fos = new FileOutputStream(targetPath)) {
            
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
            
            System.out.println("文件复制完成");
        }
    }
    
    // 读取图像文件信息
    public static void readImageInfo(String imagePath) throws IOException {
        try (FileInputStream fis = new FileInputStream(imagePath)) {
            byte[] header = new byte[4];
            fis.read(header);
            
            // 简单判断文件类型
            if (header[0] == (byte) 0xFF && header[1] == (byte) 0xD8) {
                System.out.println("JPEG 图像文件");
            } else if (header[0] == (byte) 'G' && header[1] == (byte) 'I' 
                    && header[2] == (byte) 'F') {
                System.out.println("GIF 图像文件");
            } else {
                System.out.println("未知文件类型");
            }
            
            long fileSize = new File(imagePath).length();
            System.out.println("文件大小: " + fileSize + " 字节");
        }
    }
}

字符流操作:处理文本数据

FileReader 和 FileWriter

Java 复制代码
public class CharacterStreamExample {
    
    // 读取文本文件内容(字符流)
    public static String readTextFile(String filePath) throws IOException {
        StringBuilder content = new StringBuilder();
        
        try (FileReader reader = new FileReader(filePath)) {
            char[] buffer = new char[4096];
            int charsRead;
            
            while ((charsRead = reader.read(buffer)) != -1) {
                content.append(buffer, 0, charsRead);
            }
        }
        
        return content.toString();
    }
    
    // 写入文本文件
    public static void writeTextFile(String filePath, String content) throws IOException {
        try (FileWriter writer = new FileWriter(filePath)) {
            writer.write(content);
        }
    }
    
    // 按行读取文本文件
    public static List<String> readLines(String filePath) throws IOException {
        List<String> lines = new ArrayList<>();
        
        try (FileReader fileReader = new FileReader(filePath);
             BufferedReader bufferedReader = new BufferedReader(fileReader)) {
            
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                lines.add(line);
            }
        }
        
        return lines;
    }
    
    // 处理不同编码的文本文件
    public static String readFileWithEncoding(String filePath, String charsetName) 
            throws IOException {
        StringBuilder content = new StringBuilder();
        
        // 指定编码读取文件
        try (InputStreamReader reader = new InputStreamReader(
                new FileInputStream(filePath), charsetName)) {
            
            char[] buffer = new char[4096];
            int charsRead;
            
            while ((charsRead = reader.read(buffer)) != -1) {
                content.append(buffer, 0, charsRead);
            }
        }
        
        return content.toString();
    }
}

缓冲流:提升I/O性能的关键

缓冲流的威力

Java 复制代码
public class BufferedStreamExample {
    
    public static void performanceComparison() throws IOException {
        String sourceFile = "largefile.dat";
        String target1 = "copy1.dat";
        String target2 = "copy2.dat";
        
        // 1. 无缓冲复制
        long startTime = System.currentTimeMillis();
        copyWithoutBuffer(sourceFile, target1);
        long time1 = System.currentTimeMillis() - startTime;
        
        // 2. 有缓冲复制
        startTime = System.currentTimeMillis();
        copyWithBuffer(sourceFile, target2);
        long time2 = System.currentTimeMillis() - startTime;
        
        System.out.println("无缓冲复制耗时: " + time1 + "ms");
        System.out.println("有缓冲复制耗时: " + time2 + "ms");
        System.out.println("性能提升: " + (time1 - time2) + "ms");
    }
    
    private static void copyWithoutBuffer(String source, String target) throws IOException {
        try (FileInputStream fis = new FileInputStream(source);
             FileOutputStream fos = new FileOutputStream(target)) {
            
            int byteData;
            while ((byteData = fis.read()) != -1) { // 逐字节读取,效率极低!
                fos.write(byteData);
            }
        }
    }
    
    private static void copyWithBuffer(String source, String target) throws IOException {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target))) {
            
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            while ((bytesRead = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
        }
    }
    
    // 带缓冲的字符流
    public static void processTextFileWithBuffer(String inputFile, String outputFile) 
            throws IOException {
        
        try (BufferedReader reader = new BufferedReader(new FileReader(inputFile));
             BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
            
            String line;
            int lineNumber = 0;
            
            while ((line = reader.readLine()) != null) {
                lineNumber++;
                // 处理每一行,例如添加行号
                String processedLine = String.format("%04d: %s", lineNumber, line);
                writer.write(processedLine);
                writer.newLine(); // 跨平台换行符
            }
        }
    }
}

对象序列化:保存和恢复对象状态

Serializable接口

Java 复制代码
public class SerializationExample {
    
    // 可序列化的对象
    public static class Person implements Serializable {
        private static final long serialVersionUID = 1L; // 序列化版本号
        
        private String name;
        private transient int age; // transient修饰的字段不会被序列化
        private List<String> hobbies;
        
        public Person(String name, int age, List<String> hobbies) {
            this.name = name;
            this.age = age;
            this.hobbies = hobbies;
        }
        
        @Override
        public String toString() {
            return "Person{name='" + name + "', age=" + age + 
                   ", hobbies=" + hobbies + "}";
        }
    }
    
    // 序列化对象到文件
    public static void serializeObject(Object obj, String filePath) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(
                new FileOutputStream(filePath))) {
            oos.writeObject(obj);
            System.out.println("对象序列化完成: " + filePath);
        }
    }
    
    // 从文件反序列化对象
    public static Object deserializeObject(String filePath) 
            throws IOException, ClassNotFoundException {
        
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream(filePath))) {
            return ois.readObject();
        }
    }
    
    public static void main(String[] args) throws Exception {
        List<String> hobbies = Arrays.asList("阅读", "游泳", "编程");
        Person person = new Person("张三", 25, hobbies);
        
        // 序列化
        serializeObject(person, "person.dat");
        
        // 反序列化
        Person restoredPerson = (Person) deserializeObject("person.dat");
        System.out.println("恢复的对象: " + restoredPerson);
        // 注意:age字段为0,因为被transient修饰
    }
    
    // 自定义序列化过程
    public static class CustomSerializable implements Serializable {
        private String data;
        private transient String sensitiveData; // 敏感数据,不直接序列化
        
        public CustomSerializable(String data, String sensitiveData) {
            this.data = data;
            this.sensitiveData = sensitiveData;
        }
        
        // 自定义序列化逻辑
        private void writeObject(ObjectOutputStream out) throws IOException {
            out.defaultWriteObject(); // 默认序列化
            // 对敏感数据进行加密后再序列化
            String encrypted = encrypt(sensitiveData);
            out.writeObject(encrypted);
        }
        
        // 自定义反序列化逻辑
        private void readObject(ObjectInputStream in) 
                throws IOException, ClassNotFoundException {
            
            in.defaultReadObject(); // 默认反序列化
            // 读取并解密敏感数据
            String encrypted = (String) in.readObject();
            this.sensitiveData = decrypt(encrypted);
        }
        
        private String encrypt(String data) {
            // 简单的加密示例(实际项目应使用安全的加密算法)
            return Base64.getEncoder().encodeToString(data.getBytes());
        }
        
        private String decrypt(String encrypted) {
            byte[] bytes = Base64.getDecoder().decode(encrypted);
            return new String(bytes);
        }
    }
}

现代Java I/O:NIO与NIO.2

Path API:更优雅的文件路径操作

Java 复制代码
public class ModernIOExample {
    
    public static void pathOperations() {
        // 1. 创建Path对象
        Path path1 = Paths.get("/Users/test/documents", "file.txt");
        Path path2 = Path.of("data", "input", "config.properties");
        
        // 2. 获取路径信息
        System.out.println("文件名: " + path1.getFileName());
        System.out.println("父目录: " + path1.getParent());
        System.out.println("根目录: " + path1.getRoot());
        System.out.println("绝对路径: " + path1.toAbsolutePath());
        
        // 3. 路径操作
        Path resolved = path2.resolve("subfolder/file.txt");
        Path relativized = path1.relativize(Paths.get("/Users/test"));
        Path normalized = Paths.get("/a/./b/../c").normalize();
        
        // 4. 文件系统操作
        try {
            // 检查文件属性
            boolean exists = Files.exists(path1);
            boolean isDirectory = Files.isDirectory(path1);
            boolean isReadable = Files.isReadable(path1);
            long size = Files.size(path1);
            FileTime lastModified = Files.getLastModifiedTime(path1);
            
            // 创建目录(包括父目录)
            Path newDir = Paths.get("new/directory");
            Files.createDirectories(newDir);
            
            // 创建文件
            Path newFile = newDir.resolve("test.txt");
            Files.createFile(newFile);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    // 使用Files类进行高效文件操作
    public static void filesClassExamples() throws IOException {
        // 1. 读取所有行
        Path filePath = Paths.get("data.txt");
        List<String> allLines = Files.readAllLines(filePath, StandardCharsets.UTF_8);
        
        // 2. 流式处理大文件
        try (Stream<String> lines = Files.lines(filePath, StandardCharsets.UTF_8)) {
            List<String> filtered = lines
                .filter(line -> line.contains("error"))
                .collect(Collectors.toList());
            
            System.out.println("找到 " + filtered.size() + " 个错误行");
        }
        
        // 3. 复制文件
        Path source = Paths.get("source.txt");
        Path target = Paths.get("target.txt");
        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
        
        // 4. 移动/重命名文件
        Files.move(target, Paths.get("renamed.txt"), 
                  StandardCopyOption.REPLACE_EXISTING);
        
        // 5. 删除文件
        Files.deleteIfExists(Paths.get("temp.txt"));
        
        // 6. 遍历目录
        Path dir = Paths.get("src/main/java");
        try (Stream<Path> paths = Files.walk(dir, 3)) { // 最大深度3层
            paths.filter(Files::isRegularFile)
                 .filter(p -> p.toString().endsWith(".java"))
                 .forEach(System.out::println);
        }
    }
    
    // 文件属性操作
    public static void fileAttributes() throws IOException {
        Path path = Paths.get("test.txt");
        
        // 基本属性
        BasicFileAttributes attrs = Files.readAttributes(
            path, BasicFileAttributes.class);
        
        System.out.println("创建时间: " + attrs.creationTime());
        System.out.println("最后访问时间: " + attrs.lastAccessTime());
        System.out.println("最后修改时间: " + attrs.lastModifiedTime());
        System.out.println("是否目录: " + attrs.isDirectory());
        System.out.println("大小: " + attrs.size() + " 字节");
        
        // 设置文件属性
        Files.setAttribute(path, "dos:hidden", true);
        Files.setLastModifiedTime(path, 
            FileTime.fromMillis(System.currentTimeMillis()));
    }
}

高级I/O技巧与实践

1. 文件锁:多进程/多线程安全访问

Java 复制代码
public class FileLockExample {
    
    public static void writeWithLock(String filePath, String content) throws IOException {
        Path path = Paths.get(filePath);
        
        try (FileChannel channel = FileChannel.open(path, 
                StandardOpenOption.WRITE, StandardOpenOption.CREATE);
             FileLock lock = channel.lock()) { // 获取独占锁
            
            System.out.println("获取文件锁,开始写入...");
            
            // 写入内容
            byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
            ByteBuffer buffer = ByteBuffer.wrap(bytes);
            channel.write(buffer);
            
            Thread.sleep(2000); // 模拟长时间操作
            
            System.out.println("写入完成,释放锁");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void tryReadLock(String filePath) throws IOException {
        Path path = Paths.get(filePath);
        
        try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);
             FileLock lock = channel.tryLock(0, Long.MAX_VALUE, true)) { // 尝试获取共享锁
            
            if (lock != null) {
                System.out.println("成功获取读锁");
                
                // 读取文件内容
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                while (channel.read(buffer) > 0) {
                    buffer.flip();
                    System.out.print(StandardCharsets.UTF_8.decode(buffer));
                    buffer.clear();
                }
                
                lock.release();
            } else {
                System.out.println("文件被锁定,无法读取");
            }
        }
    }
}

2. 内存映射文件:超大文件高效处理

Java 复制代码
public class MemoryMappedFileExample {
    
    public static void processLargeFile(String filePath) throws IOException {
        Path path = Paths.get(filePath);
        
        try (FileChannel channel = FileChannel.open(path, 
                StandardOpenOption.READ, StandardOpenOption.WRITE)) {
            
            long fileSize = channel.size();
            long position = 0;
            long chunkSize = 1024 * 1024 * 100; // 每次映射100MB
            
            while (position < fileSize) {
                long size = Math.min(chunkSize, fileSize - position);
                
                // 映射文件的一部分到内存
                MappedByteBuffer buffer = channel.map(
                    FileChannel.MapMode.READ_WRITE, position, size);
                
                // 在内存中直接操作数据
                processBuffer(buffer);
                
                position += size;
            }
        }
    }
    
    private static void processBuffer(ByteBuffer buffer) {
        // 示例:搜索特定字节模式
        while (buffer.hasRemaining()) {
            byte b = buffer.get();
            // 处理逻辑...
        }
    }
}

3. 管道通信:线程间数据传输

Java 复制代码
public class PipeExample {
    
    public static void threadCommunication() throws IOException {
        // 创建管道
        Pipe pipe = Pipe.open();
        
        // 写入线程
        Thread writerThread = new Thread(() -> {
            try (Pipe.SinkChannel sinkChannel = pipe.sink()) {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                
                for (int i = 0; i < 10; i++) {
                    String message = "Message " + i + "\n";
                    buffer.put(message.getBytes());
                    buffer.flip();
                    
                    while (buffer.hasRemaining()) {
                        sinkChannel.write(buffer);
                    }
                    
                    buffer.clear();
                    Thread.sleep(500);
                }
                
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        
        // 读取线程
        Thread readerThread = new Thread(() -> {
            try (Pipe.SourceChannel sourceChannel = pipe.source()) {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                
                while (true) {
                    int bytesRead = sourceChannel.read(buffer);
                    if (bytesRead > 0) {
                        buffer.flip();
                        System.out.print(StandardCharsets.UTF_8.decode(buffer));
                        buffer.clear();
                    }
                }
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        
        readerThread.start();
        writerThread.start();
    }
}

I/O最佳实践与性能优化

最佳实践总结

Java 复制代码
public class IOBestPractices {
    
    // 1. 总是使用try-with-resources
    public static void goodPractice1() throws IOException {
        // ✅ 正确做法
        try (BufferedReader reader = Files.newBufferedReader(Paths.get("file.txt"))) {
            // 使用reader
        }
        
        // ❌ 错误做法
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("file.txt"));
            // 使用reader
        } finally {
            if (reader != null) {
                reader.close(); // 可能忘记关闭
            }
        }
    }
    
    // 2. 选择合适的缓冲区大小
    public static void bufferSizeSelection() {
        // 根据场景选择合适的缓冲区大小
        int[] bufferSizes = {
            1024,      // 小文件或网络操作
            8192,      // 默认推荐值
            65536,     // 大文件处理
            1024 * 1024 // 超大文件或数据库操作
        };
        
        // 测试找到最佳缓冲区大小
        for (int bufferSize : bufferSizes) {
            try {
                long time = testBufferPerformance(bufferSize);
                System.out.printf("缓冲区大小 %d: %d ms%n", bufferSize, time);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    // 3. 处理大文件的正确方式
    public static void processLargeFileEfficiently(String inputFile, String outputFile) 
            throws IOException {
        
        // 使用流式处理,避免一次性加载到内存
        try (BufferedReader reader = Files.newBufferedReader(Paths.get(inputFile));
             BufferedWriter writer = Files.newBufferedWriter(Paths.get(outputFile))) {
            
            String line;
            while ((line = reader.readLine()) != null) {
                // 处理每一行
                String processed = processLine(line);
                writer.write(processed);
                writer.newLine();
                
                // 定期flush,但不频繁
                if (Math.random() < 0.001) {
                    writer.flush();
                }
            }
        }
    }
    
    // 4. 安全的临时文件处理
    public static void safeTempFileUsage() throws IOException {
        // 创建临时文件(会自动删除)
        Path tempFile = Files.createTempFile("prefix_", ".tmp");
        
        try {
            // 使用临时文件
            Files.write(tempFile, "临时数据".getBytes());
            
            // 处理文件...
            
        } finally {
            // 确保临时文件被删除
            Files.deleteIfExists(tempFile);
        }
        
        // 或者使用deleteOnExit(程序退出时删除)
        File temp = File.createTempFile("temp", ".txt");
        temp.deleteOnExit();
    }
    
    // 5. 监控I/O操作进度
    public static void copyWithProgress(String source, String target) 
            throws IOException {
        
        long totalSize = Files.size(Paths.get(source));
        long copied = 0;
        
        try (FileInputStream fis = new FileInputStream(source);
             FileOutputStream fos = new FileOutputStream(target)) {
            
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            while ((bytesRead = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
                copied += bytesRead;
                
                // 显示进度
                double progress = (double) copied / totalSize * 100;
                if ((int) progress % 10 == 0) {
                    System.out.printf("复制进度: %.1f%%%n", progress);
                }
            }
        }
    }
    
    private static long testBufferPerformance(int bufferSize) throws IOException {
        // 性能测试逻辑
        return System.currentTimeMillis(); // 示例返回值
    }
    
    private static String processLine(String line) {
        // 示例处理逻辑
        return line.toUpperCase();
    }
}

实战:实现一个简单的文件管理器

Java 复制代码
public class SimpleFileManager {
    
    public static class FileInfo {
        private String name;
        private String type;
        private long size;
        private LocalDateTime lastModified;
        private boolean isDirectory;
        
        // 构造函数、getter、setter省略
        
        @Override
        public String toString() {
            return String.format("%-20s %-10s %12d  %s", 
                name, 
                type, 
                size, 
                lastModified.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
        }
    }
    
    public List<FileInfo> listFiles(String directory) throws IOException {
        List<FileInfo> fileList = new ArrayList<>();
        
        try (Stream<Path> paths = Files.list(Paths.get(directory))) {
            paths.forEach(path -> {
                try {
                    FileInfo info = new FileInfo();
                    info.setName(path.getFileName().toString());
                    
                    BasicFileAttributes attrs = Files.readAttributes(
                        path, BasicFileAttributes.class);
                    
                    info.setDirectory(attrs.isDirectory());
                    info.setType(attrs.isDirectory() ? "DIR" : getFileExtension(path));
                    info.setSize(attrs.size());
                    info.setLastModified(LocalDateTime.ofInstant(
                        attrs.lastModifiedTime().toInstant(), ZoneId.systemDefault()));
                    
                    fileList.add(info);
                    
                } catch (IOException e) {
                    System.err.println("无法读取文件信息: " + path);
                }
            });
        }
        
        return fileList.stream()
            .sorted(Comparator.comparing(FileInfo::isDirectory).reversed()
                    .thenComparing(FileInfo::getName))
            .collect(Collectors.toList());
    }
    
    public void searchFiles(String directory, String pattern) throws IOException {
        Path startDir = Paths.get(directory);
        
        if (!Files.exists(startDir) || !Files.isDirectory(startDir)) {
            throw new IllegalArgumentException("目录不存在: " + directory);
        }
        
        try (Stream<Path> paths = Files.walk(startDir)) {
            paths.filter(Files::isRegularFile)
                 .filter(path -> path.getFileName().toString().matches(pattern))
                 .forEach(System.out::println);
        }
    }
    
    public long calculateDirectorySize(String directory) throws IOException {
        Path dirPath = Paths.get(directory);
        
        try (Stream<Path> paths = Files.walk(dirPath)) {
            return paths.filter(Files::isRegularFile)
                       .mapToLong(path -> {
                           try {
                               return Files.size(path);
                           } catch (IOException e) {
                               return 0L;
                           }
                       })
                       .sum();
        }
    }
    
    public void backupDirectory(String sourceDir, String backupDir) throws IOException {
        Path source = Paths.get(sourceDir);
        Path backup = Paths.get(backupDir);
        
        if (!Files.exists(backup)) {
            Files.createDirectories(backup);
        }
        
        try (Stream<Path> paths = Files.walk(source)) {
            paths.forEach(sourcePath -> {
                try {
                    Path targetPath = backup.resolve(source.relativize(sourcePath));
                    
                    if (Files.isDirectory(sourcePath)) {
                        Files.createDirectories(targetPath);
                    } else {
                        Files.copy(sourcePath, targetPath, 
                                  StandardCopyOption.REPLACE_EXISTING);
                    }
                    
                } catch (IOException e) {
                    System.err.println("备份失败: " + sourcePath);
                }
            });
        }
    }
    
    private String getFileExtension(Path path) {
        String fileName = path.getFileName().toString();
        int dotIndex = fileName.lastIndexOf('.');
        return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1).toUpperCase();
    }
    
    public static void main(String[] args) throws IOException {
        SimpleFileManager manager = new SimpleFileManager();
        
        // 示例使用
        System.out.println("=== 当前目录文件列表 ===");
        List<FileInfo> files = manager.listFiles(".");
        files.forEach(System.out::println);
        
        System.out.println("\n=== 目录大小 ===");
        long size = manager.calculateDirectorySize(".");
        System.out.println("总大小: " + size + " 字节 (" + (size/1024.0/1024.0) + " MB)");
        
        System.out.println("\n=== 搜索Java文件 ===");
        manager.searchFiles(".", ".*\.java$");
    }
}

总结与进阶学习

核心要点回顾

  1. 理解流的概念:区分字节流和字符流,节点流和处理流
  2. 正确管理资源:始终使用try-with-resources
  3. 使用缓冲提高性能:特别是处理大文件时
  4. 掌握NIO.2 API:Path和Files类提供了更现代的接口
  5. 处理编码问题:明确指定字符编码,避免乱码

常见陷阱与解决方案

Java 复制代码
// 陷阱1:未指定编码
// ❌
new FileReader("file.txt");
// ✅
new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8);

// 陷阱2:忘记flush缓冲区
// ❌ 数据可能丢失
writer.write("data");
// ✅
writer.write("data");
writer.flush(); // 或使用try-with-resources自动flush

// 陷阱3:不处理符号链接
// ❌ 可能陷入循环
Files.walk(dirPath);
// ✅
Files.walk(dirPath, FileVisitOption.FOLLOW_LINKS);
相关推荐
宸津-代码粉碎机几秒前
Spring 6.0+Boot 3.0实战避坑全指南:5大类高频问题与解决方案(附代码示例)
java·数据仓库·hive·hadoop·python·技术文档编写
笃行客从不躺平3 分钟前
ThreadLocal 复习一
java·开发语言
程序帝国5 分钟前
SpringBoot整合RediSearch(完整,详细,连接池版本)
java·spring boot·redis·后端·redisearch
安卓程序员_谢伟光7 分钟前
如何监听System.exit(0)的调用栈
java·服务器·前端
Pluto_CSND16 分钟前
JSONPath解析JSON数据结构
java·数据结构·json
xiaoliuliu1234523 分钟前
Tomcat Connectors 1.2.32 源码编译安装教程(含 mod_jk 配置步骤)
java·tomcat
CYTElena26 分钟前
JAVA关于集合的笔记
java·开发语言·笔记
源码获取_wx:Fegn089534 分钟前
基于springboot + vueOA工程项目管理系统
java·vue.js·spring boot·后端·spring
短剑重铸之日35 分钟前
《Java并发编程研读》第三章:锁机制
java·java并发编程·java锁机制
一 乐1 小时前
健康管理|基于springboot + vue健康管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·学习