前言:I/O(输入 / 输出)操作是构建各类应用程序的基石之一。Java 提供了功能强大且灵活的 I/O 流机制,用于处理数据的读取与写入,无论是简单的文本文件操作,还是复杂的网络数据传输,都离不开 I/O 流的支持。
目录
一、初识 Java I/O 流
数据的 “传送带”
二、字节流操作
从读取到写入的实战
1. 读取文件(字节流)
2. 写入文件(字节流)
三、字符流操作
读写文本文件的简便之道
1. 读取文件(字符流)
2. 写入文件(字符流)
四、缓冲流
给 I/O 操作插上 “翅膀”
1. 字节缓冲流
2. 字符缓冲流
五、对象序列化
让对象 “穿越时空”
六、Java NIO
给 I/O 操作装上 “涡轮增压”
1. 通道(Channel)和缓冲区(Buffer)
2. 多路复用器(Selector)
总结
I/O 流,说白了就是数据的 “传送带”,负责把数据从一个地方运到另一个地方。在 Java 里,它主要分为两类:字节流和字符流。
字节流 :就像搬砖,一块砖一块砖地搬,适用于处理二进制数据,比如图片、音乐文件啥的。它的顶级老大是 InputStream
和 OutputStream
。
字符流 :类似于搬书,一本一本搬,主要用于处理文本文件,毕竟读文本嘛,还是以字符为单位方便。它的顶级班头是 Reader
和 Writer
。
先来个简单的,用 FileInputStream
读取文件内容。啥文件呢?就叫 “猫猫日记.txt” 吧,说不定里面记载了啥好玩的事儿呢。
import java.io.FileInputStream;
import java.io.IOException;
public class CatDiaryReader {
public static void main(String[] args) {
FileInputStream fis = null;
try {
// 创建 FileInputStream 对象,去 “偷看” 猫猫日记
fis = new FileInputStream("猫猫日记.txt");
int data;
// 开始读取猫猫日记啦,看看里面写啥了
while ((data = fis.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
System.out.println("读取猫猫日记出错了,呜呜呜!");
e.printStackTrace();
} finally {
// 读完了记得关门,不然猫猫会生气的
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
System.out.println("关闭日记本失败,猫猫要抓我了!");
e.printStackTrace();
}
}
}
}
}
这段代码就像在偷偷翻看猫猫的日记本,把里面的内容一字一句读出来。要是读成功了,就能看到猫猫的小心思;要是出错了,就只能哭着喊 “呜呜呜” 了。
再试试用 FileOutputStream
给猫猫写日记。说不定哪天猫猫看了会感动呢。
import java.io.FileOutputStream;
import java.io.IOException;
public class CatDiaryWriter {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
// 创建 FileOutputStream 对象,给猫猫写日记
fos = new FileOutputStream("猫猫日记.txt");
String content = "今天我抓了好多老鼠,真是太开心啦!";
byte[] bytes = content.getBytes(); // 把文字变成字节君
// 把字节君写入猫猫日记本
fos.write(bytes);
System.out.println("猫猫日记写成功啦!");
} catch (IOException e) {
System.out.println("给猫猫写日记失败,猫猫要饿肚子啦!");
e.printStackTrace();
} finally {
// 写完了也要关门,不然字会跑掉的
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
System.out.println("关闭日记本失败,字要跑光啦!");
e.printStackTrace();
}
}
}
}
}
写着写着,感觉猫猫的生活都变得丰富多彩了呢。
用 FileReader
去读取另一个文件,这次是 “狗狗冒险记.txt”,看看狗狗的惊险刺激之旅。
import java.io.FileReader;
import java.io.IOException;
public class DogAdventureReader {
public static void main(String[] args) {
FileReader fr = null;
try {
// 创建 FileReader 对象,去读狗狗冒险记
fr = new FileReader("狗狗冒险记.txt");
char[] buffer = new char[1024]; // 创建一个字符缓冲区
int length;
// 开始读取啦,看看狗狗都经历了啥
while ((length = fr.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, length));
}
} catch (IOException e) {
System.out.println("读取狗狗冒险记出错了,狗狗要惨啦!");
e.printStackTrace();
} finally {
// 读完记得把书合上
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
System.out.println("关闭书本失败,狗狗的故事要散架啦!");
e.printStackTrace();
}
}
}
}
}
这就像在翻阅一本精彩的冒险故事书,一页页地读,把狗狗的刺激经历都展现在眼前。
用 FileWriter
给狗狗写冒险故事,让它的生活更加有戏。
import java.io.FileWriter;
import java.io.IOException;
public class DogAdventureWriter {
public static void main(String[] args) {
FileWriter fw = null;
try {
// 创建 FileWriter 对象,给狗狗写冒险故事
fw = new FileWriter("狗狗冒险记.txt");
String content = "今天我追了一只蝴蝶,跑遍了整个院子,太好玩啦!";
// 把故事写进狗狗的冒险记
fw.write(content);
System.out.println("狗狗冒险记写成功啦!");
} catch (IOException e) {
System.out.println("给狗狗写冒险记失败,狗狗要无聊死啦!");
e.printStackTrace();
} finally {
// 写完记得把笔放下,不然墨水会干掉的
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
System.out.println("放下笔失败,墨水要洒啦!");
e.printStackTrace();
}
}
}
}
}
写着写着,狗狗的生活都变得有声有色了。
BufferedInputStream
:就像给读取数据的路途加了个 “超级加油站”,让数据传输更快。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class FastCatDiaryReader {
public static void main(String[] args) {
BufferedInputStream bis = null;
try {
// 创建 FileInputStream 对象
FileInputStream fis = new FileInputStream("猫猫日记.txt");
// 把 FileInputStream 变成带缓冲的,速度飞起来
bis = new BufferedInputStream(fis);
int data;
while ((data = bis.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
System.out.println("快速读取猫猫日记失败,速度还是不够快!");
e.printStackTrace();
} finally {
// 跑完记得停下来,不然会摔倒的
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
System.out.println("停止失败,还在原地打转转!");
e.printStackTrace();
}
}
}
}
}
BufferedOutputStream
:给写入数据的通道装了个 “超级滑梯”,让写入速度飞起来。
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FastCatDiaryWriter {
public static void main(String[] args) {
BufferedOutputStream bos = null;
try {
// 创建 FileOutputStream 对象
FileOutputStream fos = new FileOutputStream("猫猫日记.txt");
// 把 FileOutputStream 变成带缓冲的,飞速写入
bos = new BufferedOutputStream(fos);
String content = "今天我在阳光下打了个盹,梦到了好多好吃的鱼!";
byte[] bytes = content.getBytes();
bos.write(bytes);
bos.flush(); // 把缓冲区的数据都推出去,别留着啦
System.out.println("快速写入猫猫日记成功啦!");
} catch (IOException e) {
System.out.println("快速写入猫猫日记失败,滑梯卡住了!");
e.printStackTrace();
} finally {
// 玩完滑梯要下来,不然会摔跤的
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
System.out.println("从滑梯下来失败,还在上面摇摇晃晃!");
e.printStackTrace();
}
}
}
}
}
BufferedReader
:就像给读取文本的路途装了个 “超级传送门”,让读取效率倍增。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FastDogAdventureReader {
public static void main(String[] args) {
BufferedReader br = null;
try {
// 创建 FileReader 对象
FileReader fr = new FileReader("狗狗冒险记.txt");
// 把 FileReader 变成带缓冲的,飞速读取
br = new BufferedReader(fr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("快速读取狗狗冒险记失败,传送门故障!");
e.printStackTrace();
} finally {
// 穿过传送门后要出来,不然会迷路的
if (br != null) {
try {
br.close();
} catch (IOException e) {
System.out.println("传送门出口打不开,还在里面转圈圈!");
e.printStackTrace();
}
}
}
}
}
BufferedWriter
:给写入文本的通道安了个 “超级喷气背包”,让写入速度飞起来。
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class FastDogAdventureWriter {
public static void main(String[] args) {
BufferedWriter bw = null;
try {
// 创建 FileWriter 对象
FileWriter fw = new FileWriter("狗狗冒险记.txt");
// 把 FileWriter 变成带缓冲的,飞速写入
bw = new BufferedWriter(fw);
String content = "今天我挖了一个大大的坑,打算明天在里面埋个宝藏!\n挖坑好累啊,但是很有趣。";
bw.write(content);
bw.newLine(); // 换行,就像在纸上换一行写
bw.write("期待明天的宝藏埋藏之旅!");
bw.flush(); // 把喷气背包里的余气都喷完,确保文字都落下来
System.out.println("快速写入狗狗冒险记成功啦!");
} catch (IOException e) {
System.out.println("快速写入狗狗冒险记失败,喷气背包没燃料啦!");
e.printStackTrace();
} finally {
// 飞完记得降落,不然会掉下来
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
System.out.println("降落失败,还在天上飘着呢!");
e.printStackTrace();
}
}
}
}
}
对象序列化就是把 Java 对象变成字节序列,让它可以被存储或者传输。就像给对象施了个 “魔法咒语”,让它能穿越到别的地方。要实现这个魔法,类得实现 Serializable
接口。
import java.io.*;
class Cat implements Serializable {
private static final long serialVersionUID = 1L; // 这是序列化的版本号,很重要哦
private String name;
private int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Cat{name='" + name + "', age=" + age + "}";
}
}
public class CatSerialization {
public static void main(String[] args) {
Cat cat = new Cat("咪咪", 3);
// 把猫猫对象序列化,就像把猫猫装进魔法瓶子里
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("猫猫魔法瓶.ser"))) {
oos.writeObject(cat);
System.out.println("猫猫被成功装进魔法瓶!");
} catch (IOException e) {
System.out.println("装猫猫进魔法瓶失败,魔法失效啦!");
e.printStackTrace();
}
// 把猫猫对象从魔法瓶子里变出来
Cat magicCat = null;
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("猫猫魔法瓶.ser"))) {
magicCat = (Cat) ois.readObject();
System.out.println("猫猫从魔法瓶里成功变出来啦!");
System.out.println(magicCat);
} catch (IOException | ClassNotFoundException e) {
System.out.println("从魔法瓶变猫猫失败,可能是瓶子碎了!");
e.printStackTrace();
}
}
}
Java NIO 提供了一种更高效、更高级的 I/O 操作方式,就像给 I/O 流装上了 “涡轮增压”。
通道就是数据传输的 “高速公路”,缓冲区是放数据的 “集装箱”。
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class DogStoryNIOReader {
public static void main(String[] args) {
try (FileChannel fileChannel = FileChannel.open(Paths.get("狗狗冒险记.txt"), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(1024); // 创建一个集装箱
while (fileChannel.read(buffer) != -1) { // 把数据从通道读到集装箱
buffer.flip(); // 把集装箱翻过来,准备倒数据
while (buffer.hasRemaining()) { // 只要还有数据,就倒出来
System.out.print((char) buffer.get());
}
buffer.clear(); // 倒完数据,清空集装箱
}
} catch (IOException e) {
System.out.println("用 NIO 读取狗狗冒险记失败,高速公路堵车啦!");
e.printStackTrace();
}
}
}
多路复用器就像是个 “超级交通警察”,能同时管理多个通道。
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.Selector;
public class NIOServer {
public static void main(String[] args) {
try {
// 创建 ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080)); // 绑定端口 8080
serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式,就像开启了自动刹车系统
// 创建超级交通警察(Selector)
Selector selector = Selector.open();
// 把 ServerSocketChannel 注册到超级交通警察那,让它监听接受连接事件
serverSocketChannel.register(selector, ServerSocketChannel.validOps());
while (true) {
// 超级交通警察开始工作,等待有通道准备好
selector.select();
// 获取已经被处理好的事件
selector.selectedKeys().forEach(key -> {
try {
if (key.isAcceptable()) {
// 接受新的连接请求
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
System.out.println("有新的客户端来连接啦,IP 是:" + socketChannel.getRemoteAddress());
}
} catch (IOException e) {
System.out.println("处理客户端连接失败,网络信号不好!");
e.printStackTrace();
}
});
// 处理完一个事件,就把这个事件从列表里移除
selector.selectedKeys().clear();
}
} catch (IOException e) {
System.out.println("启动超级交通警察失败,交通瘫痪啦!");
e.printStackTrace();
}
}
}
本文深入浅出地讲解了Java I/O流的知识,从基础的字节流、字符流操作,到提升效率的缓冲流,再到便捷的对象序列化,以及高效的Java NIO技术,逐步深入。通过生动的代码示例和调皮的注释,将晦涩的概念通俗化,助力新手轻松掌握Java I/O流的实用性与趣味性,为大家在编程路上攻克Java I/O流提供了有力支持。