Java I/O操作详解:从控制台到文件读写

1. Java输入输出方法概述

在Java编程中,输入输出(I/O)是最基础也是最重要的操作之一。无论是控制台程序、桌面应用还是Web服务,都离不开数据的输入和输出。Java提供了丰富的I/O类库,可以满足各种场景下的数据交互需求。

对于初学者来说,掌握Java的I/O操作是迈向实际开发的第一步。我见过太多新手因为不熟悉I/O操作而卡在简单的数据交互环节,导致学习进度受阻。本文将系统介绍Java中最常用的输入输出方法,包括控制台输入输出、格式化输出、文件读写等核心内容。

2. 控制台输出方法详解

2.1 基本输出方法

Java中最简单的输出方式是使用System.out提供的三个方法:

System.out.print("Hello"); // 不换行输出 System.out.println("World"); // 带换行输出 System.out.printf("PI=%.2f", 3.14159); // 格式化输出

print()和println()的区别仅在于是否在输出后添加换行符。在实际开发中,println()使用频率最高,因为它能让输出结果更清晰易读。

注意:System.out.print()系列方法实际上属于PrintStream类,System.out是Java标准库预定义的一个PrintStream实例。

2.2 格式化输出技巧

printf()方法提供了强大的格式化输出能力,其语法与C语言的printf类似:

double price = 19.99; int quantity = 5; System.out.printf("单价: %.2f, 数量: %d, 总价: %.2f\n", price, quantity, price*quantity);

常用格式说明符:

  • %d:十进制整数
  • %f:浮点数
  • %s:字符串
  • %n:平台相关的换行符
  • %t:日期时间

格式控制还可以指定宽度、精度和对齐方式:

System.out.printf("|%10s|%-10s|%10.2f|", "Hello", "World", 3.1415);

3. 控制台输入方法详解

3.1 使用Scanner类

Java中最常用的输入方式是使用java.util.Scanner类:

import java.util.Scanner; public class InputDemo { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.print("请输入姓名: "); String name = scanner.nextLine(); System.out.print("请输入年龄: "); int age = scanner.nextInt(); System.out.printf("你好,%s,你今年%d岁", name, age); } }

Scanner提供了多种方法来读取不同类型的数据:

  • nextLine():读取一行文本
  • nextInt():读取整数
  • nextDouble():读取双精度浮点数
  • nextBoolean():读取布尔值

3.2 输入验证技巧

实际开发中,用户输入往往不可靠,需要进行验证:

Scanner scanner = new Scanner(System.in); int age = 0; while(true) { System.out.print("请输入年龄: "); if(scanner.hasNextInt()) { age = scanner.nextInt(); break; } else { System.out.println("输入无效,请重新输入数字!"); scanner.next(); // 清除无效输入 } }

经验:使用hasNextXxx()方法先验证输入类型,可以有效避免InputMismatchException异常。

4. 文件读写操作

4.1 文件读取

Java提供了多种文件读取方式,以下是使用BufferedReader的示例:

import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class FileReadDemo { public static void main(String[] args) { try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) { String line; while ((line = br.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } }

4.2 文件写入

使用BufferedWriter写入文件:

import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; public class FileWriteDemo { public static void main(String[] args) { try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) { bw.write("第一行内容"); bw.newLine(); bw.write("第二行内容"); } catch (IOException e) { e.printStackTrace(); } } }

4.3 使用try-with-resources

Java 7引入的try-with-resources语法可以自动关闭资源,避免内存泄漏:

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) { // 使用资源 } catch (IOException e) { // 异常处理 }

5. 高级I/O操作

5.1 字节流与字符流

Java I/O分为字节流和字符流两大体系:

  • 字节流:InputStream/OutputStream,处理二进制数据
  • 字符流:Reader/Writer,处理文本数据

常用实现类:

  • FileInputStream/FileOutputStream:文件字节流
  • FileReader/FileWriter:文件字符流
  • BufferedInputStream/BufferedOutputStream:缓冲字节流
  • BufferedReader/BufferedWriter:缓冲字符流

5.2 对象序列化

Java对象可以通过实现Serializable接口进行序列化:

import java.io.*; class Person implements Serializable { String name; int age; } public class SerializationDemo { public static void main(String[] args) { Person p = new Person(); p.name = "张三"; p.age = 25; // 序列化 try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("person.dat"))) { oos.writeObject(p); } catch (IOException e) { e.printStackTrace(); } // 反序列化 try (ObjectInputStream ois = new ObjectInputStream( new FileInputStream("person.dat"))) { Person p2 = (Person) ois.readObject(); System.out.println(p2.name + ", " + p2.age); } catch (Exception e) { e.printStackTrace(); } } }

6. 常见问题与解决方案

6.1 中文乱码问题

文件读写时经常遇到中文乱码,解决方案是指定正确的字符编码:

BufferedReader br = new BufferedReader( new InputStreamReader(new FileInputStream("file.txt"), "UTF-8"));

6.2 文件路径问题

相对路径和绝对路径的使用需要注意:

// 相对路径相对于项目根目录 File file = new File("data/test.txt"); // 获取绝对路径 String absPath = file.getAbsolutePath();

6.3 资源释放问题

忘记关闭I/O流会导致资源泄漏,推荐使用try-with-resources语法。

6.4 大文件处理技巧

处理大文件时,应该使用缓冲并逐行/逐块处理:

try (BufferedReader br = new BufferedReader(new FileReader("large.txt"))) { String line; while ((line = br.readLine()) != null) { // 处理每一行 } } catch (IOException e) { e.printStackTrace(); }

7. 性能优化建议

  1. 使用缓冲流(BufferedInputStream/BufferedReader)可以提高I/O性能
  2. 批量读写比单字节/字符读写效率高得多
  3. 对于频繁读写操作,考虑使用内存映射文件(MappedByteBuffer)
  4. 合理设置缓冲区大小(通常8KB是个不错的起点)
  5. 对于大量小文件操作,考虑合并处理或使用NIO

8. Java NIO简介

Java NIO(New I/O)提供了更高效的I/O操作方式:

import java.nio.file.*; import java.util.List; public class NioDemo { public static void main(String[] args) { try { // 读取所有行 List<String> lines = Files.readAllLines(Paths.get("file.txt")); // 写入文件 Files.write(Paths.get("output.txt"), "Hello NIO".getBytes(), StandardOpenOption.CREATE); } catch (IOException e) { e.printStackTrace(); } } }

NIO的主要优势:

  • 通道(Channel)和缓冲区(Buffer)的概念
  • 非阻塞I/O支持
  • 选择器(Selector)实现多路复用
  • 更灵活的文件操作API

9. 实际应用案例

9.1 配置文件读取

import java.util.Properties; import java.io.FileInputStream; public class ConfigReader { public static void main(String[] args) { Properties prop = new Properties(); try (FileInputStream fis = new FileInputStream("config.properties")) { prop.load(fis); String dbUrl = prop.getProperty("db.url"); String user = prop.getProperty("db.user"); System.out.println("数据库URL: " + dbUrl); System.out.println("用户名: " + user); } catch (Exception e) { e.printStackTrace(); } } }

9.2 日志文件分析

import java.nio.file.*; import java.util.stream.*; public class LogAnalyzer { public static void main(String[] args) { try (Stream<String> lines = Files.lines(Paths.get("server.log"))) { long errorCount = lines .filter(line -> line.contains("ERROR")) .count(); System.out.println("错误日志数量: " + errorCount); } catch (IOException e) { e.printStackTrace(); } } }

10. 最佳实践总结

  1. 优先使用try-with-resources确保资源释放
  2. 文本处理使用字符流,二进制数据处理使用字节流
  3. 总是考虑字符编码问题,推荐统一使用UTF-8
  4. 大文件处理使用缓冲和流式处理
  5. 用户输入必须进行验证和异常处理
  6. 考虑使用NIO API处理高性能I/O需求
  7. 合理使用第三方库如Apache Commons IO简化开发
  8. 重要操作添加日志记录,便于问题排查

Java的I/O系统看似简单,实则包含大量细节和技巧。在实际项目中,合理的I/O操作能显著提升应用性能和稳定性。建议开发者不仅要掌握基本用法,还要深入理解底层原理,这样才能在面对复杂场景时游刃有余。