当前位置: 首页 > news >正文

Java字节流详解FileInputStream和FileOutputStream

Java 字节流详解:FileInputStream 和 FileOutputStream 从入门到实践

一、前言

在 Java 中,文件的读写操作是最基础也是最高频的 I/O 场景之一。字节流(Byte Stream)作为 Java I/O 体系的两大分支之一,负责处理所有二进制数据的传输,图片、音频、视频以及任何非文本文件都离不开它。FileInputStreamFileOutputStream是字节流中最核心的两个实现类,掌握它们就掌握了 Java 文件 I/O 的基石。

本文将围绕这两个类的构造、核心方法、使用方式、底层原理以及常见坑点展开,确保看完后能写出健壮的文件读写代码。

二、字节流简介

Java 的 I/O 流分为两大类:

  • 字节流(Byte Stream):以InputStream/OutputStream为抽象父类,按字节(8 bit)读写,适合所有类型的文件。
  • 字符流(Character Stream):以Reader/Writer为抽象父类,按字符(16 bit)读写,适合纯文本文件。

FileInputStreamFileOutputStream分别继承自InputStreamOutputStream,是操作文件的直接入口。

InputStream (抽象) └── FileInputStream ← 从文件读取字节 OutputStream (抽象) └── FileOutputStream ← 向文件写入字节

三、核心 API 速览

3.1 FileInputStream

方法签名说明
FileInputStream(String name)根据文件路径创建输入流
FileInputStream(File file)根据 File 对象创建输入流
int read()读取一个字节,返回0~255的 int,读到末尾返回-1
int read(byte[] b)读取最多b.length个字节到数组,返回实际读取的字节数
int read(byte[] b, int off, int len)读取最多len个字节到数组的指定偏移位置
long skip(long n)跳过并丢弃 n 个字节
int available()返回可读的字节数估计值
void close()关闭流,释放系统资源

3.2 FileOutputStream

方法签名说明
FileOutputStream(String name)根据路径创建输出流(覆盖模式)
FileOutputStream(String name, boolean append)指定是否追加模式
FileOutputStream(File file)根据 File 对象创建输出流
void write(int b)写入一个字节(只取 int 的低 8 位)
void write(byte[] b)写入整个字节数组
void write(byte[] b, int off, int len)写入数组的一部分
void flush()刷新缓冲区(字节流 flush 是空实现,但遵循约定)
void close()关闭流,释放系统资源

四、代码实现

4.1 基础用法:单个字节读写(不推荐用于大文件)

importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOException;publicclassFileInputStreamDemo{publicstaticvoidmain(String[]args)throwsIOException{// 用 try-with-resources 自动关闭流(JDK 7+)try(FileInputStreamfis=newFileInputStream("source.txt");FileOutputStreamfos=newFileOutputStream("dest.txt")){intdata;while((data=fis.read())!=-1){fos.write(data);}}System.out.println("文件复制完成");}}

缺点:每次只读写一个字节,每次 read()/write() 都涉及一次 native 调用,大文件下性能极差。

4.2 推荐用法:字节数组批量读写

importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.IOException;publicclassFileCopyWithBuffer{publicstaticvoidmain(String[]args){Stringsource="photo.jpg";Stringdest="photo_copy.jpg";try(FileInputStreamfis=newFileInputStream(source);FileOutputStreamfos=newFileOutputStream(dest)){byte[]buffer=newbyte[8192];// 8KB 缓冲区intlen;while((len=fis.read(buffer))!=-1){fos.write(buffer,0,len);// 注意:必须使用 write(buffer, 0, len) 而不是 write(buffer)// 因为最后一次读取可能不足 8192 字节}System.out.println("复制完成");}catch(IOExceptione){System.err.println("文件操作失败: "+e.getMessage());}}}

4.3 追加模式写入

importjava.io.FileOutputStream;importjava.io.IOException;importjava.nio.charset.StandardCharsets;publicclassFileAppendDemo{publicstaticvoidmain(String[]args){try(FileOutputStreamfos=newFileOutputStream("log.txt",true)){Stringline="["+System.currentTimeMillis()+"] 用户登录成功\n";fos.write(line.getBytes(StandardCharsets.UTF_8));}catch(IOExceptione){e.printStackTrace();}}}

FileOutputStream的第二个参数appendtrue时,写入内容追加到文件末尾而非覆盖。

五、执行流程与底层原理

read(byte[])为例,一次读取的完整链路如下:

  1. Java 应用层调用FileInputStream.read(byte[])
  2. JDK 内部调用native readBytes方法
  3. JNI进入 C 层,调用操作系统的ReadFile(Windows)或read()(Linux/POSIX)
  4. 内核从磁盘读取数据到内核空间缓冲区
  5. 内核 → 用户空间数据从内核缓冲区拷贝到 Java 传入的 byte 数组
  6. 返回实际读取的字节数(-1 表示 EOF)

这个过程中涉及一次用户态 ↔ 内核态切换和一次数据拷贝。这也是为什么批量读(减少 native 调用次数)比逐字节读快几个数量级的原因。

六、常见问题与注意事项

6.1 流必须关闭

未关闭流会导致文件句柄泄漏,最终可能触发Too many open files异常。

✅ 正确做法:优先使用try-with-resources(JDK 7+),它会自动调用 close(),即使在异常发生时也能正确关闭。

6.2write(byte[])write(byte[], 0, len)的区别

fos.write(buffer)会把整个 buffer 写入文件。如果最后一次读取不足 buffer 长度,会写入上一次残留的脏数据。永远使用write(buffer, 0, len)

6.4 字符编码问题

字节流不涉及编码转换,写入什么字节就读出什么字节。如果要在字节流上处理文本,必须外部指定字符集

// 写入时指定编码fos.write("中文".getBytes(StandardCharsets.UTF_8));// 读取时指定编码Stringtext=newString(bytes,StandardCharsets.UTF_8);

6.5 FileNotFoundException 不等于文件不存在

FileInputStream构造时如果文件不存在会抛FileNotFoundException。但如果文件是个目录,或者路径包含不可读的目录,同一异常也会抛出。不要只把它当成"文件不存在"来处理

七、最佳实践

  1. 能用字符流的场景(纯文本)用字符流,否则用字节流。FileReader / FileWriter 内部封装了字节转换,更省心。
  2. 大文件优先用 BufferedInputStream / BufferedOutputStream包装,它们在内部自动维护更大的缓冲区,减少 native 调用。
  3. 版本兼容检查— FileInputStream 和 FileOutputStream 从 JDK 1.0 就有,不存在兼容性问题。
  4. Files.copy() 可以替代— 对于单纯复制文件,java.nio.file.Files.copy()一行代码搞定,底层使用更高效的 FileChannel。
// NIO 一行复制Files.copy(Path.of("source.txt"),Path.of("dest.txt"),StandardCopyOption.REPLACE_EXISTING);

八、总结

  • FileInputStreamFileOutputStream是 Java 字节流体系中最基础的文件读写类,支持所有文件类型。
  • 批量读写(字节数组)比逐字节读写性能高出几个数量级。
  • 始终使用try-with-resources确保流被正确关闭。
  • 写入时注意write(buffer, 0, len)而非write(buffer),避免脏数据。
  • 纯文本场景优先考虑FileReader/FileWriter或 NIO 的Files工具类。

掌握字节流是理解 Java I/O 体系的第一步,也是编写高可靠文件操作代码的必备基础。


推荐标签:

  • Java IO
  • FileInputStream
  • FileOutputStream
  • 字节流
  • Java 文件操作
http://www.jsqmd.com/news/741543/

相关文章:

  • Stable Diffusion风格优化器:LoRA与参数调优实战指南
  • 小龙虾算法COA实战:调参指南与在CEC2005测试函数上的表现分析
  • 嵌入式安全升级生死线(2026年起所有新认证产品强制要求):C语言OTA工具中必须植入的3道可信执行边界——TPM2.0桥接、Secure Boot Chain延伸、运行时完整性度量
  • 【20年CPython核心贡献者亲授】:Python 3.15类型系统增强的7个隐藏API、3个兼容性陷阱与1套企业级迁移Checklist
  • 避开“毒王”分子:药物化学家如何利用警示子结构(SA)库提前规避研发雷区
  • 2026-05-03:避免禁用值的最小交换次数。用go语言,给定两个长度为 n 的整数数组 nums 和 forbidden。你需要通过反复执行交换操作来调整 nums,使得对每个位置 i,都满足 n
  • 大模型动态记忆管理:MemAct框架原理与实践
  • PORTool:基于奖励树的LLM工具调用优化方案
  • 高斯模型与预算分配在多选题评分中的应用实践
  • Memorix:轻量级本地知识库构建与AI集成实战指南
  • 《AI大模型应用开发实战从入门到精通共60篇》041、异步编程:用asyncio提升LLM应用的并发性能
  • C语言PLCopen在线调试实战:5步定位ST代码运行时异常,98%工程师忽略的符号表同步陷阱
  • 为什么92%的C语言PLC项目在PLCopen Level A认证时失败?——基于37个真实产线案例的12项隐性合规红线清单
  • C++实现Windows防休眠工具:模拟鼠标移动与系统API调用详解
  • NHSE:动物森友会存档编辑框架的技术架构与生态价值
  • RTMP视频流的帧格式分析
  • 创业团队如何利用Taotoken管理多个项目的API Key与访问权限
  • 5个AI象棋实战技巧:从新手到高手的Vin象棋完全指南
  • 避开这些坑!OpenMV4颜色阈值调试保姆级指南(附Lab颜色空间工具)
  • 算法训练营第二十天|150.逆波兰表达式求值
  • 单目3D重建技术:从深度学习到工业应用
  • 2026成都八喜热水器维修标杆名录:前锋热水器官方维修、华帝壁挂炉24小时维修、华帝热水器官方维修、博世壁挂炉官方维修选择指南 - 优质品牌商家
  • 杀戮尖塔2mod二次元猎宝
  • 编程入门:if和switch分支结构
  • 云原生入门系列|第30集(终章):从零入门到实战落地,解锁云原生核心能力
  • Docker容器化部署OpenClaw AI智能体:安全隔离与自动化实践指南
  • CLM技术架构:构建企业级证书自动化管理平台
  • 百度网盘秒传脚本完整指南:永久文件分享的终极解决方案
  • 实测避坑:ESP32 ADC采样率虚标?手把手教你用DMA模式获取真实数据(附IDF V4.4.2修复方案)
  • CaaS商业模式解析:证书即服务如何创造商业价值