File 类,Java 中用于表示文件或目录的类
传入路径是文件则表示一个文件,是目录则代表目录
new File(String path)
创建、删除、重命名文件
创建文件
// 定义 File 对象
File file = new File("/home/a.txt");
// 文件不存在
if (!file.exists()) {
File parent = file.getParentFile();
// 创建目录
if (!parent.exists() && !parent.mkdirs()) {
System.out.println("目录创建失败");
return;
}
try {
if (file.createNewFile()) {
System.out.println("文件创建成功");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
删除文件,如果是非空文件夹,则无法删除
// 立即删除
file.delete();
// 虚拟机关闭时删除
file.deleteOnExit();
重命名文件
// 需要保证 newFile 的父目录存在
File newFile = new File("/home/txt/b.txt");
if (file.renameTo(newFile)) {
System.out.println("文件重命名成功");
}
File 类常用 API
exists() 判断是否存在
getName() 获取名称
getPath() 获取路径
isDirectory() 是否是目录
isFile() 是否是文件
createNewFile() 创建新文件
delete() 删除
renameTo() 重命名
mkdir() 创建一层目录
mkdirs() 创建多层目录
listFiles() 获取该目录下所有文件的集合
以字节为单位读写数据的流
常用的字节输入流
ByteArrayInputStream 字节数组输入流
FileInputStream 文件字节输入流
FilterInputStream 过滤器字节输入流
BufferedInputStream 带缓冲区字节输入流
PipedInputStream 管道字节输入流
文件字节输入流
创建文件输入流
// 指定文件路径
FileInputStream(String path)
// 传入 File 对象
FileInputStream(File file)
读取一个字节
File file = new File("/home/a.txt");
try {
FileInputStream inputStream = new FileInputStream(file);
List<Byte> bs = new ArrayList<>();
int b;
while ((b = inputStream.read()) != -1) {
bs.add((byte) b);
}
// List<Integer> 转 byte[]
byte[] bytes = new byte[bs.size()];
for (int i = 0; i < bs.size(); i++) {
bytes[i] = bs.get(i);
}
// 打印字符串,new String(byte[] bytes)
System.out.println(new String(bytes));
} catch (IOException e) {
throw new RuntimeException(e);
}
read() 方法返回每次读到的字节,但返回值是 int 类型,是为了将 -1 作为读取结束的标识,并且可以和字节 -1 区分开来,使用 int 的低 8 位来表示 byte
一个负数的二进制表示为反码加一,即补码,如果返回 byte 则无法区分 -1 是标识还是数据
批量读取字节
FileInputStream inputStream = new FileInputStream(file);
List<Byte> bs = new ArrayList<>();
// 创建一个用于接收的临时字节数组
byte[] buf = new byte[4];
int len;
while ((len = inputStream.read(buf)) != -1) {
// 遍历数组,加入到 Byte List 中保存
// 防止最后一次读取没有占满 buf,每次按读取的字节数遍历
for (int i = 0; i < len; i++) {
bs.add(buf[i]);
}
}
// List<Byte> 转 byte[]
byte[] bytes = new byte[bs.size()];
for (int i = 0; i < bs.size(); i++) {
bytes[i] = bs.get(i);
}
System.out.println(new String(bytes));
read(byte[] bytes) 每次读取一个字节数组,返回实际读取的长度
常用的字节输出流
ByteArrayOutputStream 字节数组输出流
FileOutputStream 文件字节输出流
FilterOutputStream 过滤器字节输出流
BufferedOutputStream 带缓冲区字节输出流
PipedOutputStream 管道字节输出流
文件字节输出流
创建文件输出流
// 指定文件路径
FileOutputStream(String path)
// 传入 File 对象
FileOutputStream(File file)
// 传入 append 方式,false = 覆盖,true = 追加
FileOutputStream(File file, boolean append)
FileOutputStream 默认为覆盖模式
以覆盖模式打开输出流,及时没有写入任何内容,文件也会被清空
批量写入字节数组
File file = new File("/home/a.txt");
try {
FileOutputStream outputStream = new FileOutputStream(file);
String str = "123我😂";
outputStream.write(str.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
flush 方法
部分 Outputstream 子类实现了缓存机制,如 BufferedOutputStream,为了提高效率可能先会缓存数据等待一起发,flush 的作用是强制将缓存中的数据发出去
也有些 Outputstream 子类在 close 方法中调用了 flush 方法,如 FileOutputStream、FilterOutputStrea
复制文件
File file = new File("/home/a.txt");
File file2 = new File("/home/b.txt");
try {
FileInputStream inputStream = new FileInputStream(file);
FileOutputStream outputStream = new FileOutputStream(file2);
byte[] buf = new byte[1024];
int len;
// 边读边写
while ((len = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, len);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
为什么需要字符编码?
计算机只能存储二进制数字,所以每种字符都需要使用二进制表示,才能在计算机中存储
而字符和二进制的映射关系称为编码
编码的本质:
将一个字符表示为一串二进制数,即字节数组,并且可以完成从字节数组到字符的转换
难点在于当一个字符需要多个字节表示时,如何判断字节是否是字符的开始以及字符的字节数
码点
在一定的编码规则下,一个码点映射一个字符
常见的字符编码
ASCII,128 个字符,32 不可打印字符,10 数字,26 大小字母,26 小写字母,34 标点符号
GB2312,中国发布的中文字符集,总字符数 7445
Unicode,统一码联盟提出,被称为万国码
Unicode 字符集
Unicode 13.0 版本中已经收录了 14 个个字符
Unicode 编码以 U+ 开头,汉字区间 4E00-9FFF,第一个汉字是 ‘一’,U+4E00
Unicode 只负责对字符进行编码,该编码具体如何存储为二进制,由编码方案决定
常见的 Unicode 编码方案有 UTF8、UTF16、UTF32
UTF16
Java 语言内部存储字符串使用了 UTF16 编码,该编码方案使用两个或四个字节表示字符
Java 中一个 char 占两个字节,是一个代码单元,Unicode 编码表中靠后的字符需要两个 char,即两个代码单元,四个字节。需要两个字节表示的字符称为基本字符,范围是 U+0000 到 U+FFFF,需要四个字节表示的字符称为辅助字符,范围是 U+10000 到 U+10FFFF
辅助字符,如 emoji 表情符号,是不可以赋值给 char 的,只能使用 String 来存放,String 底层会使用两个 char 去存放
如何正确获取字符串的字符数?
// String s = ...
// 字符数,码点数
int num = s.codePointCount(0, s.length());
// 码点数组
int[] points = s.codePoints().toArray();
如何判断辅助字符?
// 判断码点是不是辅助字符
Character.isSupplementaryCodePoint(int point);
// 判断 char 是不是表示辅助字符的一部分
Character.isSurrogate(char c)
Java 9 String 底层使用 byte 数组代替 char 数组
在纯拉丁字母的字符串使用 latin1 字符集来表示,latin1 每个字符使用一个字节,更加节约空间
String 增加了一个新的 coder 字段来标识编码方式,复杂字符串依然使用 UTF16 编码
UTF8
UTF8 中英文占 1 字节,拉丁文占 2 字节,中文占 3 字节,其他象形文字占 4 字节
UTF8 编码特点
在多字节表示的字符中,第一个字节有多少个连续的 1,就代表有多少个字节
在多字节表示的字符中,除了第一个字节外,其余字节都以 10 开头
例如,汉字‘一’的编码方式:
Unicode 码 U+4E00,转二进制 01001110 00000000
UTF-8 编码的二进制:111-00100 10-111000 10-000000
根据 UTF8 编码的特点,遍历一个字节数组,读到每一个字节都可以根据规则找到属于该字符的所有字节,那么就可以完成字节数组到字符的转换
UTF-8 对不同字符使用了不同数量的字节去存储,相比 UTF16 更加节约空间,但是 Java 为什么不使用 UTF8 作为内置编码呢?
因为 UTF8 分了多种情况编码,在随机访问时处理逻辑复杂,影响效率
常用的字符输入流
CharArrayReader 字符数组输入流
InputStreamReader 字节输入流转字符输入流
FileReader 文件字符输入流
FilterReader 过滤器字符输入流
BufferedReader 带缓冲区字符输入流
PipedReader 管道字符输入流
文件字符输入流
创建文件输入流
// 指定文件路径
FileReader(String path)
// 传入 File 对象
FileReader(File file)
// JDK 11 支持传入字符集编码方式
// 指定文件路径、编码
FileReader(String path, Charset charset)
// 传入 File 对象、编码
FileReader(File file, Charset charset)
// JDK 8 可以使用 InputStreamReader 指定字符集编码方式
InputStreamReader(InputStream inputStream, Charset charset);
读取一个字节
File file = new File("/home/a.txt");
try {
FileReader reader = new FileReader(file);
List<Character> bs = new ArrayList<>();
// read 返回 -1 表示结束
int b;
while ((b = reader.read()) != -1) {
bs.add((char) b);
}
// List<Character> 转 char[]
char[] chars = new char[bs.size()];
for (int i = 0; i < bs.size(); i++) {
chars[i] = bs.get(i);
}
// 打印字符串,char[] chars
System.out.println(new String(chars));
} catch (IOException e) {
throw new RuntimeException(e);
}
也可以使用 read(char cbuf[], int off, int len) 系列方法来一次读取多个字符
常用的字符输出流
CharArrayWriter 字符数组输出流
OutputStreamWriter 字节输出流转字符输出流
FileWriter 文件字符输出流
FilterWriter 过滤器字符输出流
BufferedWriter 带缓冲区字符输出流
PipedWriter 管道字符输出流
PrintWriter 打印输出流
文件字符输出流
创建字符输出流
// 指定文件路径
FileWriter(String path)
// 传入 File 对象
FileWriter(File file)
// JDK 11 支持传入字符集编码方式
// 指定文件路径、编码
FileWriter(String path, Charset charset)
// 传入 File 对象、编码
FileWriter(File file, Charset charset)
// JDK 8 可以使用 OutputStreamWriter 指定字符集编码方式
OutputStreamWriter(OutputStream OutputStream, Charset charset);
写入一个字符串
File file = new File("/home/a.txt");
try {
FileWriter writer = new FileWriter(file);
writer.write("你好");
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
// 可以不写 flush 方法,因为 OutputStreamWriter 关闭时会完成 flush 操作
也可以使用 write(char cbuf[], int off, int len) 系列方法来一次写入多个字符