File类
实例化一个File对象就可以表示某个“文件”或“目录”。在开发中,创建文件(目录)、删除文件(目录)、操作文件属性等操作就会使用File类,但是使用File无法操作文件中的内容(操作文件中的内容需要使用IO的技术)。
概念:代表物理盘符中的一个文件或者文件夹。
File类的常见构造方法:public File(String pathname)
public static void main(String[] args) throws IOException {
// 获取当前工作目录路径
System.out.println(System.getProperty("user.dir"));
// 1.相对路径创建File对象:默认放到user.dir目录下面
File file1 = new File("abc.txt");
// 创建文件
file1.createNewFile();
// 2.绝对路径创建File对象
File file2 = new File("D:/abc.txt");
// 创建文件
file2.createNewFile();
}
常用方法:
方法名 | 描述 |
createNewFile() | 创建一个新文件。 |
mkdir() | 创建一个新目录。 |
delete() | 删除文件或空目录。 |
exists() | 判断File对象所对象所代表的对象是否存在。 |
getAbsolutePath() | 获取文件的绝对路径。 |
getName() | 取得名字。 |
getParent() | 获取文件/目录所在的目录。 |
isDirectory() | 是否是目录。 |
isFile() | 是否是文件。 |
length() | 获得文件的长度。 |
listFiles() | 列出目录中的所有内容。 |
public class DemoIo001 {
public static void main(String[] args) {
// 创建 File 对象
File file = new File("D:\\LC工作文件\\lc-work\\java-basics-demo\\src\\com\\taiyuan\\javademo\\demoio\\testFile.txt");
// 创建目录
File dir = new File("D:\\LC工作文件\\lc-work\\java-basics-demo\\src\\com\\taiyuan\\javademo\\demoio\\testDirectory");
// 1. createNewFile() - 创建新文件
try {
if (file.createNewFile()) {
System.out.println("文件 " + file.getName() + " 已创建.");
} else {
System.out.println("文件已存在.");
}
} catch (IOException e) {
System.out.println("发生错误: " + e.getMessage());
}
// 2. mkdir() - 创建新目录
if (dir.mkdir()) {
System.out.println("目录 " + dir.getName() + " 已创建.");
} else {
System.out.println("目录创建失败或已存在.");
}
// 3. exists() - 判断文件或目录是否存在
System.out.println("文件是否存在: " + file.exists());
System.out.println("目录是否存在: " + dir.exists());
// 4. getAbsolutePath() - 获取文件的绝对路径
System.out.println("文件的绝对路径: " + file.getAbsolutePath());
System.out.println("目录的绝对路径: " + dir.getAbsolutePath());
// 5. getName() - 获取文件或目录的名字
System.out.println("文件名: " + file.getName());
System.out.println("目录名: " + dir.getName());
// 6. getParent() - 获取文件或目录的父目录
System.out.println("文件的父目录: " + file.getParent());
System.out.println("目录的父目录: " + dir.getParent());
// 7. isDirectory() - 判断是否为目录
System.out.println("文件是否是目录: " + file.isDirectory());
System.out.println("目录是否是目录: " + dir.isDirectory());
// 8. isFile() - 判断是否为文件
System.out.println("文件是否是文件: " + file.isFile());
System.out.println("目录是否是文件: " + dir.isFile());
// 9. length() - 获取文件的长度
System.out.println("文件大小: " + file.length() + " 字节");
// 10. listFiles() - 列出目录中的所有内容
if (dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null && files.length > 0) {
System.out.println("目录中的文件/目录:");
for (File f : files) {
System.out.println(f.getName());
}
} else {
System.out.println("目录为空或读取错误.");
}
}
// // 11. delete() - 删除文件或目录
// if (file.delete()) {
// System.out.println("文件 " + file.getName() + " 已删除.");
// } else {
// System.out.println("文件删除失败.");
// }
//
// if (dir.delete()) {
// System.out.println("目录 " + dir.getName() + " 已删除.");
// } else {
// System.out.println("目录删除失败.");
// }
}
}
FileFilter:文件过滤器接口
boolean accept(File pathname)。
当调用File类中的listFiles()方法时,支持传入FileFilter接口接口实现类,对获取文件进行过滤,只有满足条件的文件的才可出现在listFiles()的返回值中。
public class DemoIo002 {
public static void main(String[] args) {
// 创建一个目录对象
File dir = new File("D:\\LC工作文件\\lc-work\\java-basics-demo\\src\\com\\taiyuan\\javademo\\demoio");
// 检查目录是否存在
if (dir.exists() && dir.isDirectory()) {
// 使用匿名内部类创建一个 FileFilter 实现
FileFilter txtFileFilter = new FileFilter() {
@Override
public boolean accept(File file) {
String name = file.getName();
// 只接受以 .txt 结尾的文件
return file.isFile() && name.endsWith(".java");
}
};
// 获取目录中的所有 .txt 文件
File[] txtFiles = dir.listFiles(txtFileFilter);
// 输出所有符合条件的文件
if (txtFiles != null && txtFiles.length > 0) {
System.out.println("目录中的 .java 文件:");
for (File file : txtFiles) {
System.out.println(file.getName());
}
} else {
System.out.println("目录中没有 .txt 文件.");
}
} else {
System.out.println("指定的路径不是一个有效的目录.");
}
}
}
Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
IO分类
根据数据的流向分为:输入流和输出流。
输入流 :把数据从其他设备上读取到内存中的流。
输出流 :把数据从内存 中写出到其他设备上的流。
根据数据的类型分为:字节流和字符流。
字节流 :以字节为单位,读写数据的流。
字符流 :以字符为单位,读写数据的流。
输入流 | 输出流 |
字节输入流 InputStream | 字节输出流 OutputStream |
字符输入流 Reader | 字符输出流 Writer |
字节流和字符流的区别
字节流:以字节(8位)为单位进行数据传输,适用于处理任何类型的二进制数据,如图片、音频、视频等。Java中的InputStream和OutputStream是字节流的抽象基类。
字符流:以字符(16位Unicode)为单位进行数据传输,主要用于处理文本数据。Reader和Writer是字符流的抽象基类。
字节流
1. OutputStream类
java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
public abstract void write(int b) :将指定的字节输出流。
1.1.1. FileOutputStream类
构造方法:
public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。
1、写出字节:write(int b) 方法,每次可以写出一个字节数据
2、写出字节数组:write(byte[] b),每次可以写出数组中的数据
3、写出指定长度字节数组:write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节
// 使用File对象创建流对象
File file = new File("a.txt");
FileOutputStream fos = new FileOutputStream(file);
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("b.txt");
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 写出数据:虽然参数为int类型四个字节,但是只会保留一个字节的信息写出
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "你好中国".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b,2,2);
// 关闭资源
fos.close();
2. InputStream类
java.io.InputStream抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read(): 从输入流读取数据的下一个字节。
public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。
2.1. FileInputStream类
构造方法:
FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
1、读取字节:read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
2、使用字节数组读取:read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
// 使用File对象创建流对象
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file);
// 使用文件名称创建流对象
FileInputStream fos = new FileInputStream("b.txt");
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 读取数据,返回一个字节
int read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
// 读取到末尾,返回-1
read = fis.read();
System.out.println( read);
// 关闭资源
fis.close();
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("read.txt");
// 定义变量,保存数据
int b ;
// 循环读取
while ((b = fis.read())!=-1) {
System.out.println((char)b);
}
// 关闭资源
fis.close();
// 使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
// 定义变量,作为有效个数
int len ;
// 定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while (( len= fis.read(b))!=-1) {
// 每次读取后,把数组的有效字节部分,变成字符串打印
System.out.println(new String(b,0,len));// len 每次读取的有效字节个数
}
// 关闭资源
fis.close();
字符流
Reader类
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
: 从输入流读取一个字符。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
FileReader类
构造方法
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。
构造时使用系统默认的字符编码和默认字节缓冲区。
- 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
idea中UTF-8 - 字节缓冲区:一个字节数组,用来临时存储字节数据。
1、读取字符:read
方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1
,循环读取
2、使用字符数组读取:read(char[] cbuf)
,每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1
// 使用File对象创建流对象
File file = new File("a.txt");
FileReader fr = new FileReader(file);
// 使用文件名称创建流对象
FileReader fr = new FileReader("b.txt");
// 使用文件名称创建流对象
FileReader fr = new FileReader("read.txt");
// 定义变量,保存有效字符个数
int len ;
// 定义字符数组,作为装字符数据的容器
char[] cbuf = new char[2];
// 循环读取
while ((len = fr.read(cbuf))!=-1) {
System.out.println(new String(cbuf,0,len));
}
// 关闭资源
fr.close();
Writer类
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字符输出流的基本共性功能方法。
void write(int c)
写入单个字符。void write(char[] cbuf)
写入字符数组。abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off数组的开始索引,len写的字符个数。void write(String str)
写入字符串。void write(String str, int off, int len)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。void flush()
刷新该流的缓冲。void close()
关闭此流,但要先刷新它。
FileWriter类
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
构造时使用系统默认的字符编码和默认字节缓冲区。
1、写出字符:write(int b)
方法,每次可以写出一个字符数据
2、写出字符数组 :write(char[] cbuf)
和 write(char[] cbuf, int off, int len)
,每次可以写出字符数组中的数据,用法类似FileOutputStream
3、写出字符串:write(String str)
和 write(String str, int off, int len)
,每次可以写出字符串中的数据,更为方便
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush
方法了。
// 使用File对象创建流对象
File file = new File("fw.txt");
FileWriter fw = new FileWriter(file);
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据
fw.write(97); // 写出第1个字符
fw.write('b'); // 写出第2个字符
fw.write('C'); // 写出第3个字符
fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。
/*
【注意】关闭资源时,与FileOutputStream不同。
如果不关闭,数据只是保存到缓冲区,并未保存到文件。
*/
// fw.close();
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 写出数据,通过flush
fw.write('刷'); // 写出第1个字符
fw.flush();
fw.write('新'); // 继续写出第2个字符,写出成功
fw.flush();
// 写出数据,通过close
fw.write('关'); // 写出第1个字符
fw.close();
fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
fw.close();
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 字符串转换为字节数组
char[] chars = "你好中国".toCharArray();
// 写出字符数组
fw.write(chars);
// 写出从索引2开始,2个字节。索引2是'中',两个字节,也就是'中国'。
fw.write(b,2,2); //中国
// 关闭资源
fos.close();
// 使用文件名称创建流对象
FileWriter fw = new FileWriter("fw.txt");
// 字符串
String msg = "你好中国";
// 写出字符数组
fw.write(msg); //你好中国
// 写出从索引2开始,2个字节。索引2是'中',两个字节,也就是'中国'。
fw.write(msg,2,2); // 中国
// 关闭资源
fos.close();
缓冲流
缓冲流,也叫高效流,是对4个基本的FileXxx
流的增强,所以也是4个流,按照数据类型分类:
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
字节缓冲流
构造方法:
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
字符缓冲流
构造方法
public BufferedReader(Reader in)
:创建一个 新的缓冲输入流。public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。
转换流
InputStreamReader类
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
InputStreamReader(InputStream in)
: 创建一个使用默认字符集的字符流。InputStreamReader(InputStream in, String charsetName)
: 创建一个指定字符集的字符流。
OutputStreamWriter类
转换流java.io.OutputStreamWriter
,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
OutputStreamWriter(OutputStream in)
: 创建一个使用默认字符集的字符流。OutputStreamWriter(OutputStream in, String charsetName)
: 创建一个指定字符集的字符流。
扩展:
JAVA中汉字占几个字节?
在 Java 中,汉字占用的字节数取决于所使用的编码方式
- 在 Java 中的 char 类型(即 UTF-16 编码),每个汉字占用 2 个字节。
- 如果你使用 UTF-8 编码(例如,将字符串转换为字节数组),一个汉字通常占 3 个字节。
- 如果使用 GBK 编码,一个汉字占 2 个字节。
public class StringDemo {
public static void main(String[] args) throws Exception {
String str = "中";
byte[] utf8Bytes = str.getBytes("UTF-8");
System.out.println("UTF-8 编码下,汉字 '中' 占用字节数: " + utf8Bytes.length);
byte[] gbkBytes = str.getBytes("GBK");
System.out.println("GBK 编码下,汉字 '中' 占用字节数: " + gbkBytes.length);
byte[] bytes = str.getBytes();
System.out.println("默认编码下,汉字 '中' 占用字节数: " + bytes.length);
char chars = '中';
byte[] bytes1 = Character.toString(chars).getBytes("UTF-16BE");
System.out.println("字符 '中' 的 UTF-16 编码字节数: " + bytes1.length);
char chars2 = '中';
byte[] bytes2 = Character.toString(chars).getBytes("UTF-16");
System.out.println("字符 '中' 的 UTF-16 编码字节数: " + bytes2.length);
}
}
使用 getBytes("UTF-16") 时,结果会是 4 字节,因为 UTF-16 编码除了包含字符本身的字节外,还包含 BOM 字节。如果不希望包含 BOM,可以使用 getBytes("UTF-16BE") 或 getBytes("UTF-16LE")(大端或小端 UTF-16 编码),这些编码方式不包含 BOM。
BOM(Byte Order Mark,字节顺序标记)是一个特殊的字节序列,用于表示文本文件中字符编码的字节顺序。它的主要作用是在没有外部标识的情况下,指示如何解释文件中的字节流顺序。BOM 通常出现在文件的开头,并且在不同的编码方式下,它的具体字节值是不同的。
序列化(Serialization)
什么是序列化?
序列化 是指将 Java 对象转换成可存储或者可传输的字节流的过程。在网络编程中,通常会把对象序列化后传输,通过网络将字节流传输到接收方。接收方再将字节流反序列化为原始对象。在 Java 中,序列化的过程通过实现 Serializable 接口来实现。
反序列化 是将字节流转换回 Java 对象的过程。反序列化可以使用 ObjectInputStream 类来实现。
Serializable 是一个标记接口,它的作用是告诉 Java 的序列化机制,当前类可以进行序列化处理。如果一个类希望支持序列化,它必须实现这个接口。
序列化的过程
在 Java 中,可以通过 ObjectOutputStream
类来执行序列化操作,具体步骤如下:
- 创建一个输出流对象,例如
ObjectOutputStream
。 - 将要序列化的对象写入该输出流。
- 输出流会将对象的状态(成员变量的值)转换成字节流,保存或发送。
import java.io.*;
public class SerializationExample {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
out.writeObject(person); // 序列化对象
System.out.println("对象已序列化");
} catch (IOException e) {
e.printStackTrace();
}
}
}
反序列化的过程
反序列化是将存储或传输的字节流重新构造成对象的过程。反序列化使用 ObjectInputStream
类来实现。
import java.io.*;
public class DeserializationExample {
public static void main(String[] args) {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person person = (Person) in.readObject(); // 反序列化对象
System.out.println("反序列化后的对象: " + person.getName() + ", " + person.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
Java 的序列化机制不仅序列化对象的成员变量,还会序列化与该对象相关联的其他对象。具体工作原理如下:
- 基本数据类型:如
int
、double
、boolean
等,会直接序列化其值。 - 对象引用:对象引用会被序列化为引用的实际对象,而对象的状态(即它的实例字段)会被递归地序列化。
- 父类字段:如果一个类继承了父类,并且父类实现了
Serializable
接口,那么父类的字段也会被序列化。 - transient 关键字:有些时候,你不希望某个字段被序列化,可以使用
transient
关键字修饰它。被transient
修饰的字段会在序列化过程中被忽略。