Java IO流详解:从InputStream到文件操作实战

目录

一、InputStream:字节输入流概述

二、InputStream常用子类

三、FileInputStream文件输入流详解

[3.1 构造方法](#3.1 构造方法)

[3.2 核心方法:read()](#3.2 核心方法:read())

[1. read() - 读取单个字节](#1. read() - 读取单个字节)

[2. read(byte\[\] b) - 读取到字节数组](#2. read(byte[] b) - 读取到字节数组)

[3. read(byte\[\] b, int off, int len) - 带偏移量的读取](#3. read(byte[] b, int off, int len) - 带偏移量的读取)

四、FileInputStream实战:读取文本文件

[4.1 基础读取示例](#4.1 基础读取示例)

[4.2 中文读取问题分析](#4.2 中文读取问题分析)

[4.3 优化方案:使用字节数组读取](#4.3 优化方案:使用字节数组读取)

五、FileOutputStream文件输出流

[5.1 构造方法](#5.1 构造方法)

[5.2 核心方法:write()](#5.2 核心方法:write())

[5.3 实战示例:写入字符串到文件](#5.3 实战示例:写入字符串到文件)

六、综合实战:图片文件拷贝

七、其他重要字节流

[7.1 BufferedInputStream缓冲字节输入流](#7.1 BufferedInputStream缓冲字节输入流)

[7.2 ObjectInputStream对象字节输入流](#7.2 ObjectInputStream对象字节输入流)

八、总结与最佳实践


一、InputStream:字节输入流概述

InputStream 是 Java IO 体系中所有字节输入流类的抽象父类,它定义了读取字节数据的基本操作。作为抽象类,InputStream 本身不能被实例化,但它的各种子类为不同的数据源(如文件、网络、内存等)提供了具体的实现。

二、InputStream常用子类

InputStream 的主要子类包括:

  • FileInputStream:文件输入流,用于从文件中读取字节数据
  • ByteArrayInputStream:字节数组输入流,从内存中的字节数组读取数据
  • BufferedInputStream:缓冲字节输入流,提供缓冲功能以提高读取效率
  • ObjectInputStream:对象字节输入流,用于反序列化对象
  • DataInputStream:数据输入流,用于读取基本数据类型

三、FileInputStream文件输入流详解

3.1 构造方法

FileInputStream 提供了三种主要的构造方法:

java 复制代码
// 1. 通过File对象构造
File file = new File("test.txt");
FileInputStream fis1 = new FileInputStream(file);

// 2. 通过文件描述符构造
FileDescriptor fd = ...;
FileInputStream fis2 = new FileInputStream(fd);

// 3. 通过文件路径字符串构造
FileInputStream fis3 = new FileInputStream("test.txt");

3.2 核心方法:read()

FileInputStream 提供了三个重载的 read() 方法:

1. read() - 读取单个字节
java 复制代码
// 从输入流中读取一个字节,返回值为int类型(0-255)
// 当读取到文件末尾时返回-1
int byteData = fis.read();
2. read(byte\[\] b) - 读取到字节数组
java 复制代码
// 从输入流中读取最多b.length个字节到字节数组b中
// 返回实际读取的字节数,文件末尾返回-1
byte[] buffer = new byte[1024];
int bytesRead = fis.read(buffer);
3. read(byte\[\] b, int off, int len) - 带偏移量的读取
java 复制代码
// 从输入流中读取最多len个字节到字节数组b中
// off表示数组b中的起始偏移量
byte[] buffer = new byte[1024];
int bytesRead = fis.read(buffer, 0, 1024);

四、FileInputStream实战:读取文本文件

4.1 基础读取示例

使用 FileInputStream 读取 test.txt 文件内容并打印到控制台:

java 复制代码
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            // 创建文件输入流
            fis = new FileInputStream("test.txt");
            
            int byteData;
            // 循环读取直到文件末尾
            while ((byteData = fis.read()) != -1) {
                System.out.print((char) byteData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 必须关闭流释放资源
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.2 中文读取问题分析

**重要提醒:**使用 read() 方法读取中文文件会出现乱码!

原因分析:

  • 中文字符在 UTF-8 编码中通常占用 3 个字节
  • read() 方法每次只能读取 1 个字节
  • 将单个字节强制转换为 char 会导致编码错误

示例:读取 "你好" 这两个中文字符:

java 复制代码
// 错误示例 - 会出现乱码
while ((byteData = fis.read()) != -1) {
    System.out.print((char) byteData);  // 输出乱码
}

4.3 优化方案:使用字节数组读取

为了提高读取效率和正确处理中文,推荐使用 read(byte\[\] b) 方法:

java 复制代码
import java.io.FileInputStream;
import java.io.IOException;

public class EfficientFileReader {
    public static void main(String[] args) {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("test.txt");
            byte[] buffer = new byte[1024];  // 1KB缓冲区
            int bytesRead;
            
            while ((bytesRead = fis.read(buffer)) != -1) {
                // 将字节数组转换为字符串(指定编码)
                String content = new String(buffer, 0, bytesRead, "UTF-8");
                System.out.print(content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

**注意:**如果一次读取的数据量小于数组长度,之前读取的值不会被清空,因此需要使用 bytesRead 参数来限制转换的范围。

五、FileOutputStream文件输出流

5.1 构造方法

FileOutputStream 的主要构造方法:

java 复制代码
// 1. 通过文件路径创建(覆盖模式)
FileOutputStream fos1 = new FileOutputStream("output.txt");

// 2. 通过文件路径创建(追加模式)
FileOutputStream fos2 = new FileOutputStream("output.txt", true);

// 3. 通过File对象创建
File file = new File("output.txt");
FileOutputStream fos3 = new FileOutputStream(file);

// 4. 通过File对象创建(追加模式)
FileOutputStream fos4 = new FileOutputStream(file, true);

**参数说明:**第二个 boolean 参数为 true 表示追加模式,为 false 或不写表示覆盖模式。

5.2 核心方法:write()

FileOutputStream 提供了三个重载的 write() 方法:

java 复制代码
// 1. 写入单个字节
fos.write(65);  // 写入字符'A'

// 2. 写入字节数组
byte[] data = "Hello".getBytes();
fos.write(data);

// 3. 写入字节数组的指定部分
byte[] data2 = "Hello World".getBytes();
fos.write(data2, 6, 5);  // 写入"World"

5.3 实战示例:写入字符串到文件

java 复制代码
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamDemo {
    public static void main(String[] args) {
        FileOutputStream fos = null;
        try {
            // 创建文件输出流(如果文件不存在会自动创建)
            fos = new FileOutputStream("test.txt");
            
            // 要写入的字符串
            String content = "Hello World";
            
            // 将字符串转换为字节数组(可指定编码)
            byte[] bytes = content.getBytes("UTF-8");
            
            // 写入文件
            fos.write(bytes);
            
            System.out.println("文件写入成功!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

六、综合实战:图片文件拷贝

使用 FileInputStream 和 FileOutputStream 实现图片文件拷贝功能:

java 复制代码
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ImageCopy {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        
        try {
            // 创建输入流读取源图片
            fis = new FileInputStream("source.png");
            
            // 创建输出流写入目标文件
            fos = new FileOutputStream("copy.png");
            
            // 使用缓冲区提高拷贝效率
            byte[] buffer = new byte[8192];  // 8KB缓冲区
            int bytesRead;
            
            // 循环读取并写入
            while ((bytesRead = fis.read(buffer)) != -1) {
                // 重要:必须指定长度,否则可能写入错误数据
                fos.write(buffer, 0, bytesRead);
            }
            
            System.out.println("图片拷贝完成!");
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭输入流
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 关闭输出流
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

**重要提醒:**在 write() 方法中必须指定长度参数(bytesRead),如果错误地写成 write(buffer),可能会写入多余的旧数据,导致文件大小不正确。虽然某些图片查看器可能仍然能打开,但文件内容已经损坏。

七、其他重要字节流

7.1 BufferedInputStream缓冲字节输入流

BufferedInputStream 为输入流提供缓冲功能,可以显著提高读取效率:

java 复制代码
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class BufferedStreamDemo {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream("largefile.dat"))) {
            
            byte[] buffer = new byte[8192];
            int bytesRead;
            
            while ((bytesRead = bis.read(buffer)) != -1) {
                // 处理数据
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7.2 ObjectInputStream对象字节输入流

ObjectInputStream 用于反序列化对象,通常与 ObjectOutputStream 配合使用:

java 复制代码
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectStreamDemo {
    public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("object.dat"))) {
            
            // 读取对象
            Object obj = ois.readObject();
            System.out.println("读取的对象: " + obj);
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

八、总结与最佳实践

**1. 资源管理:**必须使用 try-with-resources 或 finally 块确保流被关闭

**2. 编码处理:**处理文本文件时明确指定字符编码(如 UTF-8)

**3. 缓冲区使用:**使用适当大小的缓冲区提高IO效率

**4. 异常处理:**正确处理 IOException,避免资源泄漏

**5. 文件操作:**FileOutputStream 会自动创建不存在的文件,但不会创建目录

通过本文的学习,你应该已经掌握了 Java 字节输入输出流的基本原理和实战应用。在实际开发中,建议优先使用 Java NIO 的 Files 和 Paths 类进行文件操作,它们提供了更简洁、更安全的API。

相关推荐
Super 含1 小时前
Android 启动优化(五):线程、GC 与 IO 为什么会拖慢启动?
java·服务器·数据库
wuyk5551 小时前
98.C语言易混难点:字符数组与字符串指针的底层差异
c语言·开发语言·c++·stm32·嵌入式硬件·算法
坚持学习前端日记2 小时前
Python SQLAlchemy ORM 从0到1精通实战手册(基础到复杂高阶)
数据库·python·oracle
峥无2 小时前
从0到1手撕红黑树:封装实现 my_map 与 my_set(SGI-STL 源码级深度解析)
开发语言·c++·笔记·算法·stl
波特率1152002 小时前
C++新特性---属性说明符与标准属性
开发语言·c++
程序员小八7772 小时前
上海百度B端java后端日常实习一面
java·开发语言
北斗落凡尘2 小时前
LangGraph 入门实战(11)--输出模式
后端·python·langchain
wp123_12 小时前
IPX8 防水 Type‑C连接器:安费诺 124018792112A 与 TONEVEE TY48087‑24A 技术梳理
c语言·开发语言
不会代码的小猴2 小时前
3. 控件学习1
开发语言·c++·笔记·qt·算法