Java基础快速入门: IO流入门

本文纲要

  1. IO概述
  2. IO分类
  3. 字节流 – 字节输出流快速入门
  4. 字节流 – 注意事项
  5. 字节流 – 一次写多个数据
  6. 字节流 – 两个问题:换行与追加写入
  7. 字节流 – try-catch 捕获异常
  8. 字节流 – 字节输出流小结
  9. 字节流 – 字节输入流基本学习
  10. 字节流 – 读多个字节
  11. 字节流 – 文件复制
  12. 字节流 – 定义小数组拷贝
  13. 字节流 – 小数组拷贝原理

IO概述

在Java中,IO(Input/Output)用于实现程序与外部数据(通常是文件)的交互。
早期的数据存储方式往往依赖变量、数组或集合,这些数据保存在内存中,程序结束后便会丢失,无法实现永久化存储。

引入IO后,我们可以将数据写出到硬盘文件,实现持久化;也可以从文件中读取数据到内存中使用。

IO流的两个核心操作:

  • 写数据:将内存中的数据写入文件(Output)。
  • 读数据:将文件中的数据读入内存(Input)。

以内存为参照物的数据流动方向:

输入 Input / 读

输出 Output / 写

硬盘/文件

内存

  • 输入(Input):硬盘 → 内存,由内存进行读操作。
  • 输出(Output):内存 → 硬盘,由内存进行写操作。

因此,学习IO的重点就是掌握如何读如何写

IO分类

根据不同的维度,Java的IO流可以分成以下几种类型:

分类依据类型说明
流向输入流(InputStream硬盘 → 内存(读)
输出流(OutputStream内存 → 硬盘(写)
操作的数据类型字节流(Byte Stream可以操作所有类型的文件(文本、图片、音视频等)
字符流(Character Stream只能操作纯文本文件

纯文本文件的判断标准:
用Windows自带的记事本(Notepad)打开文件,如果能读懂内容,就是纯文本文件;否则就是非纯文本文件,必须使用字节流操作。

例如:

  • .txt 文件:纯文本 → 可用字符流
  • .doc.avi.mp3.jpg 等:非纯文本 → 必须用字节流

注意:Office文件(Word、Excel)虽然包含文字,但内部含有格式、图片等二进制数据,用记事本打开会显示乱码,因此不是纯文本,只能使用字节流。

字节流 – 字节输出流快速入门

写数据的三个步骤:

  1. 创建字节输出流对象,关联目标文件。
  2. 调用write方法写入数据。
  3. 释放资源(调用close方法)。

代码示例(项目结构):

bytestream/
└── src/
    └── com/
        └── wb/
            └── output/
                ├── OutputDemo1.java 
                ├── OutputDemo2.java 
                ├── ...
                └── OutputDemo10.java 

OutputDemo1.java 展示了最基本的写操作:

package com.wb.output;
 
import java.io.FileOutputStream;
import java.io.IOException;
 
public class OutputDemo1 {
    public static void main(String[] args) throws IOException {
        //1.创建字节输出流对象 --- 告诉虚拟机我要往哪个文件中写数据 
        FileOutputStream fos = new FileOutputStream("D:\\a.txt");
        //也可以使用File对象 
        //FileOutputStream fos = new FileOutputStream(new File("D:\\a.txt"));
 
        //2.写数据 
        fos.write(97);  //97对应ASCII码中的'a'
 
        //3.释放资源 
        fos.close();
    }
}

运行后,D:\a.txt被创建,内容为字母a。

字节流 – 注意事项

OutputDemo2.java 演示了以下注意点:

package com.wb.output;
 
import java.io.FileOutputStream;
import java.io.IOException;
 
public class OutputDemo2 {
    public static void main(String[] args) throws IOException {
        //1.创建字节输出流对象 
        //注意:如果文件不存在,会自动创建 
        //      如果文件存在,会清空原有内容 
        FileOutputStream fos = new FileOutputStream("C:\\wb\\a.txt");
 
        //2.写数据:传递一个整数,实际写入的是该整数在码表中对应的字符 
        fos.write(98); //'b'
 
        //3.释放资源:告诉操作系统不再使用该文件 
        fos.close();
    }
}

关键结论:

  • 文件不存在 → 自动创建。
  • 文件已存在 → 内容会被清空(除非开启追加模式)。
  • write(int)写入的是整数对应的字符(ASCII/Unicode码表)。
  • close() 必须调用,否则文件可能被占用而无法删除(可运行一个死循环测试,不调用close时文件无法删除)。

字节流 – 一次写多个数据

字节输出流提供了三种写数据的方式:

方法说明
write(int b)一次写一个字节
write(byte[] b)一次写一个字节数组(全部)
write(byte[] b, int off, int len)写字节数组的一部分,从off索引开始,连续写len个字节

OutputDemo3.java – 一次写多个单字节:

FileOutputStream fos = new FileOutputStream("bytestream\\a.txt");
fos.write(97);   //'a'
fos.write(98);   //'b'
fos.write(99);   //'c'
fos.close();

OutputDemo4.java – 一次写一个字节数组,以及写部分数组:

FileOutputStream fos = new FileOutputStream("bytestream\\a.txt");
 
//一次写整个数组 
byte[] bys = {97, 98, 99};
fos.write(bys);   //写入abc 
 
//一次写数组的一部分 
byte[] bys2 = {97, 98, 99, 100, 101, 102, 103};
fos.write(bys2, 1, 2);  //从索引1开始写2个字节,即98('b')和99('c')
 
fos.close();

注意:第三个参数len是写入的字节个数,不是结束索引。

字节流 – 两个问题:换行与追加写入

1 ) 如何实现换行?

不同操作系统的换行符:

  • Windows:\r\n
  • Linux:\n
  • Mac:\r

将换行符转换成字节数组后写入即可:

fos.write("\r\n".getBytes());

OutputDemo5.java 示例(包含追加和换行):

FileOutputStream fos = new FileOutputStream("bytestream\\a.txt", true); //追加模式 
 
fos.write(97);
fos.write("\r\n".getBytes());
fos.write(98);
fos.write("\r\n".getBytes());
fos.write(99);
fos.write("\r\n".getBytes());
fos.write(100);
fos.write("\r\n".getBytes());
fos.write(101);
fos.write("\r\n".getBytes());
 
fos.close();

2 ) 如何实现追加写入?

构造方法:FileOutputStream(String path, boolean append)

  • append = false(默认):清空原文件内容
  • append = true:打开续写开关,新内容追加到文件末尾

使用true参数后,每次运行都不会清空原有数据,实现追加效果。

字节流 – try-catch捕获异常

之前的代码一直使用throws抛出异常,但更规范的做法是使用try-catch-finally自行处理,特别是保证close()一定被执行。

标准异常处理结构:

FileOutputStream fos = null;
try {
    fos = new FileOutputStream("D:\\a.txt");
    fos.write(97);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //finally中的代码一定会执行(除非JVM退出)
    if (fos != null) {
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意要点:

  • fos必须声明在try外部,否则finally中无法访问。
  • new FileOutputStreamwrite出现异常,fos可能仍为null,调用close会引发空指针异常,因此需做非空判断。
  • close本身也可能抛出异常,需要再次捕获。

字节流 – 字节输出流小结

步骤要点
创建对象关联目标文件;文件不存在则创建,存在则清空(除非append=true
写数据可写单个字节、整个字节数组或数组的一部分;换行需写 \r\n 的字节形式
释放资源close()必须执行,通常放在finally 中并做非空判断

字节流 – 字节输入流基本学习

读取文件的三步骤:

  1. 创建FileInputStream对象,关联源文件。
  2. 调用read方法读取字节。
  3. 释放资源。

OutputDemo7.java:

FileInputStream fis = new FileInputStream("bytestream\\a.txt");
int read = fis.read();
//read方法一次读取一个字节,返回该字节对应的ASCII码(0~255)
//若想看到字符,需强转为char 
System.out.println((char) read); // 输出'a'(假设文件内容为a)
fis.close();

注意:

  • 若文件不存在,直接抛出 FileNotFoundException
  • read() 返回的是字节数据(int类型),到达文件末尾时返回-1

字节流 – 读多个字节

文件中通常有多个字节,需要循环读取,直到返回-1

OutputDemo8.java(标准循环读取):

FileInputStream fis = new FileInputStream("bytestream\\a.txt");
int b;
while ((b = fis.read()) != -1) {
    System.out.print((char) b);
}
fis.close();

错误示范(死循环读取):

//错误:无法停止,且一直读到文件结束后会打印大量-1或乱码 
while (true) {
    int i = fis.read();
    System.out.println(i);
}

正确做法中,以read()返回值是否为-1作为循环结束条件。

字节流 – 文件复制

文件复制的本质:从源文件(数据源)读取字节,写入目标文件(目的地)
需要同时使用 FileInputStreamFileOutputStream

OutputDemo9.java:

FileInputStream fis = new FileInputStream("C:\\wb\\a.avi");
FileOutputStream fos = new FileOutputStream("bytestream\\a.avi");
 
int b;
while ((b = fis.read()) != -1) {
    fos.write(b);   //读一个字节,立即写一个字节 
}
 
fis.close();
fos.close();

该方式每次读写一个字节,当文件较大时效率较低。

字节流 – 定义小数组拷贝

解决大文件拷贝效率问题:一次性读写多个字节,类似“用篮子一次买多个鸡蛋”。

提高效率的方案:
使用read(byte[] b):一次读取多个字节存入数组,返回值是本次读取的有效字节数。
使用write(byte[] b, int off, int len):将数组中指定部分写出。

OutputDemo10.java:

FileInputStream fis = new FileInputStream("C:\\wb\\a.avi");
FileOutputStream fos = new FileOutputStream("bytestream\\a.avi");
 
byte[] bytes = new byte[1024];  //相当于“篮子”,一次读取1024字节 
int len; //本次读到的实际字节数 
 
while ((len = fis.read(bytes)) != -1) {
    fos.write(bytes, 0, len);   //只写出实际读到的有效字节 
}
 
fis.close();
fos.close();

为什么不用整个数组作为写入长度?

最后一次读取可能填不满数组(文件剩余字节不足1024),len记录的就是实际数据量,若写成fos.write(bytes) 会将数组末尾脏数据一并写入,导致文件错误。

字节流 – 小数组拷贝原理

用示意图解释小数组拷贝过程。假设数组长度为2,源文件内容为ABCDE五个字节。

目的地bytes[2]数据源 (ABCDE)目的地bytes[2]数据源 (ABCDE)初始为空[A,B] , len=2[C,D] (覆盖A,B) , len=2[E,D] (只覆盖第一个位置) , len=1第1次read:读2字节(A,B)write(bytes,0,2) 写入 A,B第2次read:读2字节(C,D)write(bytes,0,2) 写入 C,D第3次read:只剩1字节(E)write(bytes,0,1) 只写入 E

len始终表示实际读到的字节数,第3次只有1个字节,所以写出时长度必须为1,否则会把上一次残留的D也写入

这正是为什么在write中必须指定0len的范围。

总结

通过上述内容,我们已经掌握了Java中最基础的字节流操作,包括文件读写、异常处理、效率优化等核心技能。后续可进一步学习字符流、缓冲流等高级特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值