文件

创建文件

主要方法:

  1. new File(String pathname):根据路径构建一个 File 对象

    1
    2
    3
    4
    5
    public void create01() throws IOException {
    String pathname = "/Users/suweijin/Desktop/test.txt";
    File file = new File(pathname);
    file.createNewFile();
    }
  2. new File(File parent,String child):根据父目录文件 + 子路径构建

    1
    2
    3
    4
    5
    6
    public void create02() throws IOException {
    File parent = new File("/Users/suweijin/Desktop/");
    String child = "test.txt";
    File file = new File(parent,child);
    file.createNewFile();
    }
  3. new File(String parent,String child):根据父目录 + 子路径构建

    1
    2
    3
    4
    5
    6
    public void create03() throws IOException {
    String parent = "/Users/suweijin/Desktop/";
    String child = "test.txt";
    File file = new File(parent, child);
    file.createNewFile();
    }

获取文件信息

部分方法:

  1. getName():获取文件名
  2. getAbsolutePath():获取文件绝对路径
  3. getParent():获取文件父目录
  4. length():获取文件大小,单位字节
  5. exists():判断文件是否存在
  6. isFile():判断是否是一个文件
  7. isDirectory():判断是否是一个目录
1
2
3
4
5
6
7
8
9
10
public void info() {
File file = new File("/Users/suweijin/Desktop/test.txt");
System.out.println(file.getName()); // test.txt
System.out.println(file.getAbsolutePath()); // /Users/suweijin/Desktop/test.txt
System.out.println(file.getParent()); // /Users/suweijin/Desktop
System.out.println(file.length()); // 0 (单位字节)
System.out.println(file.exists()); // true
System.out.println(file.isFile()); // true
System.out.println(file.isDirectory()); // false
}

需体会到,Java 中目录也被当成文件。

目录操作

  1. mkdir():创建一级目录

  2. mkdirs():创建多级目录

    1
    2
    3
    4
    public void dir() {
    File file = new File("/Users/suweijin/Desktop/test");
    file.mkdirs();
    }
  3. delete():删除空目录或文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void dir() {
    File file = new File("/Users/suweijin/Desktop/test");
    if (file.exists()){
    if (file.delete()){
    System.out.println("删除成功");
    }else {
    System.out.println("删除失败");
    }
    }else {
    System.out.println("目录不存在");
    }
    }

IO 流

文件在程序中以流的形式进行操作。

分类

  • 按操作单位划分:字节流和字符流。
  • 按流向划分:输入流和输出流。
  • 按角色划分:节点流和处理流/包装流。
抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OuputStream Writer
  1. Java 中的 IO 流涉及 40 多个类,都是从以上四个抽象基类派生而来。
  2. 由这四个类派生出来的子类名都是以父类名作为后缀。

字节流

1)FileInputStream

构造方法:

  1. FilelnputStream(File file)

    通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过 File 对象指定。

  2. FileInputStream(FileDescriptor fdObj)

    通过使用文件描述符 fdObj 创建一个 FileInputStream,该文件描述符表示到实际文件的现有连接。

  3. FileInputStream(String name)

    通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过路径名指定。

普通方法:

  1. int available():返回输入流中剩余未被读取的字节数;
  2. void close():关闭此文件输入流并释放与流有关的系统资源;
  3. void finalize():可以在输入流对象被回收时调用其 close 方法;
  4. FileChannel getChannel():返回与此输入流有关的唯一 FileChannel 对象,该对象提供了对底层 I/O 操作的支持,允许对文件进行更低级别的操作;
  5. FileDescriptor getFD():返回此连接的 FileDescriptor 对象;
  6. int read():从输入流中读取一个字节数据,将数据存储在 int 类型并返回,若返回 -1 表示读取完毕;
  7. int read(byte[] b):从输入流中读取最多 b.length 个字节到 byte 数组中。返回实际读取字节数,若返回 -1 表示读取完毕;
  8. int read(byte[] b, int off, int len):读取最多 len 个字节到 byte 数组中,off 表示在字节数组中存储数据的起始偏移量;
  9. long skip(long n):跳过并丢弃输入流中 n 个字节数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void read() throws IOException {
String filepath = "/Users/suweijin/Desktop/test.txt";
FileInputStream fileInputStream = null;
int data;
try {
fileInputStream = new FileInputStream(filepath);
while ((data = fileInputStream.read()) != -1) {
System.out.print((char) data);
}
} catch (FileNotFoundException e) {
System.out.print("文件不存在");
} finally {
assert fileInputStream != null;
fileInputStream.close();
}
}
// 单个字节读取,效率较低。

如果文件存在中文,由于一个中文不止一个字节,而上述代码每读取一个字节就进行输出,必然导致乱码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void read() throws IOException {
String filepath = "/Users/suweijin/Desktop/test.txt";
FileInputStream fileInputStream = null;
byte[] data = new byte[10];
int len;
try {
fileInputStream = new FileInputStream(filepath);
while ((len = fileInputStream.read(data)) != -1) {
System.out.print(new String(data, 0, len));
}
} catch (FileNotFoundException e) {
System.out.print("文件不存在");
} finally {
assert fileInputStream != null;
fileInputStream.close();
}
}

如果文件存在中文,很有可能造成乱码。因为中文还是可能被拆分在两次读取中。

2)FileOuputStream

构造方法和 FilelnputStream 一致,同时新增了以下方法:

  1. FileOutputStream(String name, boolean append)
  2. FileOutputStream(File file, boolean append)

append 表示是否以追加的方式写入文件,默认 flase

普通方法:

  1. close()
  2. finalize()
  3. getChannel()
  4. getFD()
  5. write(byte[] b):将 b.length 个字节从 byte 数组中写入文件输出流中;
  6. write(byte[] b, int off, int len):将 byte 数组中从偏移量 off 开始的 len 个字节写入文件输出流;
  7. write(int b):将指定字节写入文件输出流。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void write() throws IOException {
String filepath = "/Users/suweijin/Desktop/test.txt";
FileOutputStream fileOutputStream = null;
try {
// 数据写入前会清空原本文件中内容。如果文件不存在会自动创建,前提目录存在。
fileOutputStream = new FileOutputStream(filepath);
// 写入一个字节
// fileOutputStream.write('a');
// 写入字符串
String str = "Hello World";
fileOutputStream.write(str.getBytes());
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
assert fileOutputStream != null;
fileOutputStream.close();
}
}

文件拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void copy() throws IOException {
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
String oldPath = "/Users/suweijin/Documents/img/blog-img/painting-3995999_1280.jpg";
String newPath = "/Users/suweijin/Desktop/test.jpg";
try {
fileInputStream = new FileInputStream(oldPath);
fileOutputStream = new FileOutputStream(newPath);
byte[] data = new byte[1024];
int len;
while ((len = fileInputStream.read(data)) != -1) {
// 边读边写
fileOutputStream.write(data, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
assert fileInputStream != null;
fileInputStream.close();
assert fileOutputStream != null;
fileOutputStream.close();
}
}

字符流

1)FileReader

构造方法:

  1. new FileReader(File file)
  2. new FileReader(String name)
  3. new FileReader(FileDescriptor fdObj)

相关方法:

  1. int read():每次读取一个字符,并返回该字符,文件结束返回 -1

  2. int read(char[] cbuf):最多读取字符数组大小的数据,返回成功读取的字符数,文件结束返回 -1

  3. int read (CharBuffer target):返回成功读取的字符数,如果达到文件末尾,则返回 -1

    1
    CharBuffer charBuffer = CharBuffer.allocate(1024);
  4. int read (char[] cbuf, int offset, int Length)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void read() throws IOException {
FileReader fileReader = null;
try {
fileReader = new FileReader("/Users/suweijin/Desktop/test.txt");
char[] cbuff = new char[10];
int len;
while ((len = fileReader.read(cbuff)) != -1) {
System.out.print(new String(cbuff, 0, len));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
assert fileReader != null;
fileReader.close();
}
}

以字符为单位,不会造成中文乱码。

2)FileWriter

构造方法和 FileOuputStream 一致。

相关方法:

  1. write(int):写入单个字符;
  2. write(String):写入字符串;
  3. write(String, off, len):写入字符串的指定部分;
  4. write(char[]):写入字符数组;
  5. write(char[], off, len):写入字符数组的指定部分。
1
2
3
4
5
6
7
8
9
10
11
12
public void write() throws IOException {
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter("/Users/suweijin/Desktop/test.txt");
fileWriter.write("你好");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
assert fileWriter != null;
fileWriter.close();
}
}

FileWriter 使用后,必须进行关闭(close)或刷新(flush),否则写入不到指定的文件。

节点流/处理流

  1. 节点流:从一个特定的数据源读写数据,如 FileReaderFileWriter
  2. 处理流(包装流):包装已存在的流,为程序提供更强大的读写功能,如BufferedReaderBufferedWriter

image-20240205193444529

BufferedReader 为例,此类内部存在一个 Reader 变量,可以给 BufferedReader 传入某个 Reader 的子类,BufferedReader 类中的代码逻辑中实现了对传入流的包装,以提供更丰富的功能。

BufferedReaderBufferedWriter 属于字符流,是以字符为单位来读写数据的。不要用它去操作二进制文件(视频、图片、doc、pdf 等),可能造成文件损坏。

BufferedInputStreamBufferedOuputStream 属于字节处理流。

字符处理流

1)BufferedReader

1
2
3
4
5
6
7
8
9
10
public void read() throws IOException {
FileReader fileReader = new FileReader("/Users/suweijin/Desktop/test.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
// 按行读取,返回 null 表示文件结束
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
bufferedReader.close(); // 会自动关闭 fileReader 节点流
}

2)BufferedWriter

1
2
3
4
5
6
7
8
public void write() throws IOException {
FileWriter fileWriter = new FileWriter("/Users/suweijin/Desktop/test.txt");
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write("Hello");
bufferedWriter.newLine(); // 换行
bufferedWriter.write("World");
bufferedWriter.close();
}

字节处理流 BufferedInputStreamBufferedOuputStream 同理。

对象处理流

需求:将 int i = 100 这个 int 数据保存到文件中,不是只保存 100 这个数字,而是保存 int 100。并且能够从文件中直接恢复 int = 100

对象处理流能够将基本数据类型或者对象保存到文件并能够从文件中恢复。前提对象必须实现序列化

对象处理流本质是字节处理流,需要传入 InputStreamOutputStream 的子类。

1)ObjectOutputStream

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
public class App {
public static void main(String[] args) throws IOException {
// 文件后缀随意,因为序列化后的数据并不是文本
String filepath = "/Users/suweijin/Desktop/data.dat";
FileOutputStream fileOutputStream = new FileOutputStream(filepath);
ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
oos.writeInt(100); // int 会自动装箱成 Integer,而 Integer 已实现序列化
oos.writeBoolean(true); // boolean --> Boolean
oos.writeChar('a'); // char --> Character
oos.writeDouble(6.6); // double --> Double
oos.writeUTF("Hello,World"); // String
oos.writeObject(new User("围巾", 24));
oos.close();
}
}

class User implements Serializable {
private String name;
private int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}

@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

image-20240206110626598

2)ObjectInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void read() throws IOException, ClassNotFoundException {
String filepath = "/Users/suweijin/Desktop/data.dat";
FileInputStream fileInputStream = new FileInputStream(filepath);
ObjectInputStream ois = new ObjectInputStream(fileInputStream);
// 反序列化顺序需要和保存时一致
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
System.out.println(ois.readObject());
ois.close();
}
1
2
3
4
5
6
100
true
a
6.6
Hello,World
User{name='围巾', age=24}
  • 虽然是字节处理流,但经过包装后是可以正确处理中文的;
  • 如果类中成员变量是一个对象,该对象所属类也需实现序列化;
  • 对于 statictransient 修饰的变量,不会被序列化;
  • 如果某个类实现了序列化,它所有的子类默认也实现了序列化。

转换流

  • InputStreamReader:用于将字节输入流转换为字符输入流,属于字符流(Reader 的子类)。
  • InputStreamReader:用于将字节输出流转换为字符输出流,属于字符流(Writer 的子类)。

不同文件可能使用不同的字符集,使用之前的流进行读写可能导致乱码。可以通过转换流指定输入输出所使用的字符集,从而避免乱码。

1)InputStreamReader

1
2
3
4
5
6
7
8
public void trans() throws IOException {
FileInputStream fileInputStream = new FileInputStream("/Users/suweijin/Desktop/test.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String s = bufferedReader.readLine();
System.out.println(s);
bufferedReader.close();
}

2)InputStreamReader

1
2
3
4
5
6
public void trans() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("/Users/suweijin/Desktop/test.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);
outputStreamWriter.write("围巾");
outputStreamWriter.close();
}

标准输入/输出流

  • System.in:类型是 InputStream 字节输入流,具体使用 BufferedInputStream 处理流。
  • System.out:类型是 PrintStream 字节输出流。

打印流

  • PrintStream:字节输出流。
  • PrintWriter:字符输出流。

1)PrintStream

1
2
3
4
// 默认打印在显示器
PrintStream out = System.out;
out.print("hello");
out.close();
1
2
3
// 修改打印位置
System.setOut(new PrintStream("/Users/suweijin/Desktop/test.txt"));
System.out.print("hello");

2)PrintWriter

1
2
3
4
// 打印在显示器
PrintWriter printWriter = new PrintWriter(System.out);
printWriter.write("hello");
printWriter.close();
1
2
3
4
5
// 打印在文件中
FileWriter fileWriter = new FileWriter("/Users/suweijin/Desktop/test.txt");
PrintWriter printWriter = new PrintWriter(fileWriter);
printWriter.write("world");
printWriter.close();

读写配置文件

配置文件:

1
2
name=scarf
account=zero

传统方式:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("src/main/java/test.properties");
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
String[] split = line.split("=");
System.out.println("key: " + split[0] + " value: " + split[1]);
}
bufferedReader.close();
}
1
2
key: name value: scarf
key: account value: zero

使用 java.util.Properties 类:

常见方法:

  1. load:加载配置文件到 Properties 对象;
  2. list:可以传入打印流,将数据显示到指定位置(例如显示器或文件);
  3. getProperty(key):根据键获取值;
  4. setProperty(key, value):设置键值对到 Properties 对象;
  5. store:将 Properties 对象中的键值对存储到配置文件,如果含有中文,会存储为 unicode 码。
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws IOException {
String filepath = "src/main/java/test.properties";
FileReader fileReader = new FileReader(filepath);
Properties properties = new Properties();
properties.load(fileReader);
properties.list(System.out); // 显示在控制台
System.out.println(properties.get("name"));
properties.setProperty("account", "0"); // 修改配置
properties.setProperty("age", "66"); // 新增配置
properties.store(new FileWriter(filepath), null);
}