java安全-IO流

  • io流用来在 Java 程序和“外部世界”之间传输数据

  • java项目中的相对路径

    Java 中的相对路径,默认是相对于「程序的工作目录(Working Directory)」
    在 IDEA 里,默认就是「项目根目录」

一、文件相关操作

1.创建file对象

本质上是三种构造方式

  • 根据完整路径字符串创建

    1
    2
    3
    4
    5
    6
    // 直接传递完整路径
    File f1 = new File("D:\\test\\java.txt");
    // 或者使用相对路径(相对于项目根目录)
    File f2 = new File("src/main/resources/config.xml");

    System.out.println(f1.getAbsolutePath());
  • 根据“父路径字符串”和“子路径字符串”创建

    1
    2
    3
    4
    5
    6
    7
    8
    String parentPath = "D:\\test";
    String fileName = "java.txt";

    // 父路径字符串 + 子路径字符串
    // 系统会自动补全中间的分隔符(Windows是\, Linux是/)
    File f3 = new File(parentPath, fileName);

    System.out.println(f3.getPath()); // 输出 D:\test\java.txt
  • 根据“父 File 对象”和“子路径字符串”创建

    1
    2
    3
    4
    5
    6
    7
    8
    // 先创建父级目录对象
    File parentDir = new File("D:\\test");
    String fileName = "java.txt";

    // 父File对象 + 子路径字符串
    File f4 = new File(parentDir, fileName);

    System.out.println(f4.getPath());

boolean created = file.createNewFile();通过调用file对象的这个方法可以创建文件

2.获取file对象信息

  • 获取路径与名称

    image-20251215221009667
  • 获取文件状态

    image-20251215221035460
  • 获取文件属性

    image-20251215221114792

3.目录与文件操作

操作 方法名 返回值 关键注意点
文件/目录删除 delete() boolean 不走回收站,直接抹除;删除目录时,目录必须为空
创建单级目录 mkdir() boolean 只有父目录存在时才能创建成功。
创建多级目录 mkdirs() boolean 最推荐。如果父目录不存在,会自动补全所有父目录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 场景 A:创建单级目录 (mkdir)
// 假设 D:\test 存在,创建 D:\test\demo 才会成功
File dir1 = new File("D:\\test\\demo");
boolean mk1 = dir1.mkdir();
System.out.println("单级目录创建结果:" + mk1);

// 场景 B:创建多级目录 (mkdirs) - 【推荐】
// 即使 D:\newTest 不存在,也会自动创建 D:\newTest\a\b
File dir2 = new File("D:\\newTest\\a\\b");
boolean mk2 = dir2.mkdirs();
System.out.println("多级目录创建结果:" + mk2);


// --- 1. 文件删除 ---

// delete() 方法立即生效,不走回收站
boolean delFile = file.delete();
System.out.println("删除文件(data.txt)结果:" + delFile);

// --- 2. 目录删除 ---
// 【注意】尝试删除 D:\newTest\a 目录
// 现在的结构是 D:\newTest\a\b (b是空目录)
File parentDir = new File("D:\\newTest\\a");
// 此时 a 下面还有 b,所以 a 不是空目录,删除会失败!
boolean delDirFail = parentDir.delete();
System.out.println("删除非空目录(a)结果:" + delDirFail); // 输出 false

// 正确做法:必须先删除 b,再删除 a
boolean delSubDir = new File("D:\\newTest\\a\\b").delete();
System.out.println("删除子目录(b)结果:" + delSubDir); // true (因为b是空的)

boolean delParentDir = parentDir.delete();
System.out.println("再次删除目录(a)结果:" + delParentDir); // true (现在a空了)

二、IO流操作

在 Java 中,数据传输像水流一样。

  • 输入(Input):数据从“外部”(硬盘、网络、键盘)流向“程序内存”。(读)
  • 输出(Output):数据从“程序内存”流向“外部”。(写)
  • 特点:流是有方向的,而且通常是顺序的(先读的先到)。

架构如下:

image-20251216121711728

1.字节输入流(InputStream)

定义:它是所有字节输入流的超类(父类),是一个抽象类。它以**字节(Byte,8位)**为单位读取数据。

常用子类:FileInputStream

核心方法

  • int read(): 一次读一个字节。如果读到文件末尾,返回 -1。
  • int read(byte[] b): 一次读一个字节数组(效率高)。返回读取到的有效字节个数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 目标:使用字节流读取文件内容
try (FileInputStream fis = new FileInputStream("input.jpg")) {
//方式1:一个字节一个字节读(效率极低,不推荐)
int b;
while ((b = fis.read()) != -1) {
System.out.println(b);
}

// 方式2:使用数组缓冲读取(推荐,比如一次搬运1024个字节)
byte[] buffer = new byte[1024];
int len; // 记录每次实际读取到的字节数
while ((len = fis.read(buffer)) != -1) {
// 处理读取到的数据
System.out.println(new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}

2.字节输出流(OutputStream)

定义:它是所有字节输出流的超类。以字节为单位写出数据。

核心场景:复制文件、下载文件、写入二进制数据。

常用子类:FileOutputStream

核心方法

  • void write(int b): 写出一个字节。
  • void write(byte[] b): 写出一个字节数组。
  • void write(byte[] b, int off, int len): 写出数组的一部分。

注意点

  • 覆盖与追加new FileOutputStream("a.txt")默认会清空文件重新写;new FileOutputStream("a.txt", true) 则是追加模式。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
// 目标:向文件写入 "abc"
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
// 写入单个字节 'a' (也就是97)
fos.write(97);

// 写入字节数组
byte[] bytes = "bc".getBytes(); // 字符串转字节数组
fos.write(bytes);

} catch (IOException e) {
e.printStackTrace();
}

3.字符输入流(Reader)

定义:它是所有字符输入流的父类。以**字符(Char)**为单位读取数据。

为什么要有字符流?

  • 一个汉字在 UTF-8 编码下占 3 个字节。
  • 如果用字节流读,一次读一截,汉字会被拆碎,导致乱码
  • 字符流会自动处理编码,一次读一个完整的字符(无论它是英文还是中文)。

常用子类:FileReader

核心方法

  • int read(): 一次读一个字符。
  • int read(char[] cbuf): 一次读一个字符数组。

FileReader fr = new FileReader("chinese.txt",true) 追加操作

代码示例

1
2
3
4
5
6
7
8
9
10
try (FileReader fr = new FileReader("chinese.txt")) {
char[] buffer = new char[1024]; // 这里是 char数组,不是byte数组
int len;
while ((len = fr.read(buffer)) != -1) {
// 直接把字符数组转成字符串,不会乱码
System.out.print(new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}

4.字符输出流(Writer)

定义:它是所有字符输出流的父类。

常用子类FileWriter

核心方法

  • void write(int c): 写出一个字符。
  • void write(String str): 直接写出字符串(非常方便,字节流做不到)。
  • void write(char[] cbuf): 写出字符数组。

重要特性(缓冲区)
Writer 写出数据时,通常是先写到内存的一个缓冲区中,并不会立刻落入硬盘。如果不关闭流或者不刷新,数据可能会丢失!

1
2
3
4
5
6
try (FileWriter fw = new FileWriter("text.txt")) {
fw.write("你好,Java!");
fw.write("可以直接写字符串很方便。");
} catch (IOException e) {
e.printStackTrace();
}

5.流的知识点

  • 将字节转化为字符

    调用 new String(byte数组) 时,Java 会拿着这些数字,去查一本“字典”(编码表,通常是 UTF-8)。

    • Java 看到数字 97 查表 →→发现是字符 ‘a’。
    • Java 看到连续三个数字 -28, -67, -96 →→查表发现这三个凑在一起是汉字 ‘你’。

    new String(buffer, 0, len)这个可以限定字符读取个数,从而避免严重的 Bug(脏数据问题)。

  • 在 FileReader(字符流)中:

    • 我们用 char[] buffer 作为一个**“铲子”**。
    • read(buffer):用铲子从文件里铲出一堆字符(珠子)。
    • new String(buffer, 0, len):把铲子里的珠子加工成项链(字符串),方便程序使用。
  • 流的刷新

    • 缓冲机制:为了提高效率,字符流和部分缓冲流(Buffered…)配备了内存缓冲区。写入的数据会先暂存在内存里,攒够了一波再写给硬盘。

    • 问题:如果程序突然结束,缓冲区里还剩一点数据没攒够,这些数据就不会写入硬盘,导致丢数据

    方法flush()强制把缓冲区的数据“推”到硬盘里。

  • 流的关闭

    • 资源占用:Java 程序通过操作系统来读写文件。流如果不关闭,该文件就会一直被 Java 程序占用(类似于你打开一个 Word 文档不关,别人就删不掉它)。

    • 内存泄露:长期的资源不释放会导致内存泄露。

    方法close()他的底层也调用了flush()

    • 注意:

      image-20251216125637767

  • 目前流关闭与刷新的最佳实践

    try-with-resources代码块实现

    1
    2
    3
    4
    5
    6
    7
    // 语法格式
    try (创建流对象1; 创建流对象2) {
    // 读写操作
    } catch (IOException e) {
    // 异常处理
    }
    // 只要走出这个 try 块(无论是正常结束还是报错),括号里的流都会自动执行 close()。

6.缓冲流

  • 分类

    缓冲流属于处理流(包装流),它自己不能直接连文件,必须包裹在现有的节点流(如 FileInputStream)之上。

image-20251216215505783

  • 字节输出与输入流利用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    File srcFile = new File("video.mp4");
    File destFile = new File("video_copy.mp4");

    // 使用 try-with-resources 语法,自动关闭流
    try (
    // 1. 创建基本流
    FileInputStream fis = new FileInputStream(srcFile);
    FileOutputStream fos = new FileOutputStream(destFile);

    // 2. 创建缓冲流,把基本流"包"起来
    BufferedInputStream bis = new BufferedInputStream(fis);
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    ) {
    // 3. 读写操作
    int len;
    byte[] bytes = new byte[1024]; // 这里还是可以用一个字节数组作为中间桥梁

    // 从 bis 读,虽然看起来和普通流一样,但其实它底层已经在帮你批量预读了
    while ((len = bis.read(bytes)) != -1) {
    bos.write(bytes, 0, len);
    }

    System.out.println("复制完成!");

字符缓冲流 (用于处理文本)

日常开发处理文本(txt, json, config等)最常用的方式,因为它们有两个超级好用的特有方法。

  1. BufferedReader 特有方法:readLine()
    • 一次读取一行文本。
    • 读不到时返回 null(而不是 -1)。
  2. BufferedWriter 特有方法:newLine()
    • 自动写入一个换行符。
    • 好处:它是跨平台的(Windows是 \r\n,Linux是 \n),它会自动根据系统识别。

缓存流:内置了缓冲区(8KB),极大减少了读写硬盘的次数,速度比普通流快很多

image-20251216220705155

但是非缓冲流也可以将数组写很大以达到缓冲流的效果

7.其他高级流

转换流

核心组件:

  • InputStreamReader:字节流通向字符流的桥梁。
  • OutputStreamWriter:字符流通向字节流的桥梁。

InputStreamReader isr = new InputStreamReader(fis, "GBK");就是将普通字节流包装,并将其以规定编码方式读取!

打印流

核心组件:

  • PrintStream(字节打印流):System.out 就是这个类型。
  • PrintWriter(字符打印流):Web 开发中常用。
1
2
3
4
5
PrintWriter pw = new PrintWriter("print.txt");
pw.println("你好");
pw.println(97); // 文件中显示 97,而不是 'a'
pw.println(true);
pw.close();

普通流写整数 97,会写出字符 ‘a’(因为 97 是 ‘a’ 的 ASCII 码)。但打印流写 97,文件里就是 “97”。它提供了 print() 和 println() 方法

数据流

核心组件:

  • DataInputStream
  • DataOutputStream

把一个 int 类型的数字、一个 double 类型的浮点数、或者一个 boolean 值写入文件,并且以后读出来的时候,还能直接被识别为 int, double, boolean

1
2
3
4
5
6
7
8
9
10
11
12
13
// 写
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"));
dos.writeInt(123);
dos.writeBoolean(true);
dos.writeDouble(3.14);
dos.close();

// 读
DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"));
int a = dis.readInt();
boolean b = dis.readBoolean();
double c = dis.readDouble();
// 输出:123, true, 3.14