文章目录
- 前言 IO流
- 一、 IO流的概述
- 二、字节流系列(FileOutputStream和FileInputStream两个类)
-
- 1 FileOutputStream(操作本地文件的字节输出流:写出)
-
- (1)打开文件、操作文件、关闭文件三步套路
-
- ---- 构造方法:FileOutputStream 提供了几种常见的构造方法,用于打开文件输出流:
-
- FileOutputStream(String name):覆盖模式**,**通过文件路径名创建输出流,覆盖已有的文件内容。
- FileOutputStream(String name, boolean append):追加模式,通过文件路径名创建输出流,如果 append 为 true,则在文件末尾追加数据;否则覆盖文件内容。
- FileOutputStream(File file):通过 File 对象创建输出流,覆盖文件内容。
- FileOutputStream(File file, boolean append):通过 File 对象创建输出流,若 append 为 true,则在文件末尾追加数据。
- ---- 写入方法 void write(int b)、void write(byte[] b)、void write(byte[] b, int off, int len)
- ---- 关闭文件(通道)close()方法
- (2)try-with-resources语法:不用手动调用close方法关闭文件了
- (3)覆盖模式
- (4)追加模式
- (5)指定写入编码方式:String的getBytes("utf-8")接口要传入编码方式:write("66667777hejuzs\n你好世界".getBytes("UTF-8"))
- 2 FileInputStream(操作本地文件的字节输入流:读入)
- 3 文件拷贝:FileOutputStream和FileInputStream的一个典型应用
- 4 完整的try...catch...finally异常处理在文件操作上的使用以及简化
- 三 字符集详解
- 四、字符流系列(FileReader和FileWriter两个类)
-
- 1 FileReader: 字符输入流(JDK11前无法指定解码方式,只能默认采用平台的编码方式进行解码,但是JDK11后可以指定编码方式了)
- 2 FileWriter: 字符输出流(JDK11前无法指定解码方式,只能默认采用平台的编码方式进行解码,但是JDK11后可以指定编码方式了)
-
- (1)构造方法:FileWriter提供了几种常见的构造方法,用于打开文件输出流(包括覆盖和追加两种模式)
-
- ---- public Filewriter(File file):创建字符输出流关联本地文件(覆盖模式)
- ---- public Filewriter(File file,boolean append):创建字符输出流关联本地文件(覆盖模式)
- ---- public Filewriter(string pathname,boolean append):创建字符输出流关联本地文件,续写(追加模式)
- ---- public Filewriter(string pathname,boolean append):创建字符输出流关联本地文件,续写(追加模式)
- ---- public Filewriter(File file,Charset charset):创建字符输出流关联本地文件,指定编码方式、覆盖写入
- ---- public Filewriter(string pathname,Charset charset):创建字符输出流关联本地文件,指定编码方式、覆盖写入
- ---- public Filewriter(string pathname,Charset charset,boolean append):创建字符输出流关联本地文件,指定编码方式、追加写入
- ---- public Filewriter(File file,Charset charset,boolean append):创建字符输出流关联本地文件,指定编码方式、追加写入
- (2)写出方法:
- (3)释放资源(关流):public int close()
- (4)代码示例:指定编码格式覆盖写入
- 3 FileReader: 字符输入流和FileWriter: 字符输出流的底层原理解析
- 五 字节流和字符流综合练习
- 六 基本流总结
前言 IO流
本节我们先来介绍IO流的基本体系,并且先来学习基本流
一、 IO流的概述
- IO流:文件的读写和存储解决方案
- IO流的分类:
- 根据流的方向分为输入流(读取)、输出流(写出)
- 根据操作文件的类型分为字节流(以字节为基本单位进行数据的传输)、字符流(以字符char为单位进行数据的传输);
字节流(8 bit),字符流(16 bit)
字节流:可以操作所有类型的文件(视频、音频、纯文本等)
字符流:只能操作纯文本文件(txt文件和md文件、xml文件等)
什么是文件?文件就是保存数据的地方
纯文本文件:利用Windows自带的记事本能打开且能读得懂(不乱码)的就是纯文本文件,例如:txt文件和md文件、xml文件,注意的是word文件和excel文件不是纯文本文件,不信你去用记事本打开,一堆乱码。
下面就是关于IO流的按照操作文件类型的分类:
理解操作对象是当前的执行程序(内存)就能理解什么叫输入(读进脑子里)和输出(写出去)
上面最下面四个是抽象类,我们的关注点是他下面的子类是我们要学习的,由于IO流里面的类很多,下面我们会按照上面这个分类依次学习各个子类的用法以及适用场景。
从下到上依次都要在上层的基础上使用。本节我们的目标就是学一下访问文件、缓冲流这两个类别下的。
二、字节流系列(FileOutputStream和FileInputStream两个类)
- 字节流能操作所有类型数据,包括视频、音频、文本等。当然主要是用来操作非文本文件用这个用的多。
1 FileOutputStream(操作本地文件的字节输出流:写出)
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中。
(1)打开文件、操作文件、关闭文件三步套路
---- 构造方法:FileOutputStream 提供了几种常见的构造方法,用于打开文件输出流:
FileOutputStream(String name):覆盖模式**,**通过文件路径名创建输出流,覆盖已有的文件内容。
FileOutputStream(String name, boolean append):追加模式,通过文件路径名创建输出流,如果 append 为 true,则在文件末尾追加数据;否则覆盖文件内容。
FileOutputStream(File file):通过 File 对象创建输出流,覆盖文件内容。
FileOutputStream(File file, boolean append):通过 File 对象创建输出流,若 append 为 true,则在文件末尾追加数据。
关于上面构造方法有几个细节需要我们注意:
注意细节:
细节1: 参数是字符串表示的路径或者是File对象都是可以的
细节2: 如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
细节3: 如果不是采用最加模式文件已经存在,则会清空文件
关于构造方法我们通常用前两个,后两个了解下就行,知道也可以这么用就行了。(我们使用File凭借path后还是File对象会用到后两种)
---- 写入方法 void write(int b)、void write(byte[] b)、void write(byte[] b, int off, int len)
- 基本写入方法:FileOutputStream 支持以字节或字节数组的方式写入数据:
void write(int b):写入单个字节(每次只能写入单个字节)。该字节为 b 的低8位,范围为 0-255。
void write(byte[] b):写入字节数组中的所有字节数据。
void write(byte[] b, int off, int len):从字节数组 b 中的偏移量 off 开始,写入长度为 len 的数据。
这个方法文件拷贝哪里会用到,这里我们先演示前两个。
很显然这两个写入的方法都极其SB,所以我们通常会采用和,String里面的 .getBytes()方法将String转换成字节数组来使用
【注】:下面演示部分用到了IDEA中Java的当前工作目录和相对路径的知识参考博客一般情况下面,我们不会直接用当前工作目录去找config的相对路径,而是会像Python一样先调用接口获取当前运行的java文件的绝对路径再不断往上找父级,这样才安全。但这里只是演示一下,就直接采用IDEA默认的当前工作目录来确定相对路径了(但这是不安全的,打包后src这个文件夹就没了,所以没有先调用接口获取当前运行的java文件的绝对路径再不断往上找父级安全)
【注】:每个字节数写入的对应的就是ASCII码表对应的数字,推广就是UTF-8这些编码 ;
例如:UTF-8 编码中 “你” 的字节十六进制编码是 E4 B8 AD,这三个字节值在十进制中是 228 184 173。但是在 Java 中存储为 byte 类型时,值会被解释为 -28=228-256, -72=184-256, -83=173-256(因为它们超出了 127,而在java中byte是-128到127,但是编码转成十进制一般都会是0-256,做了一下转换)
在演示这个方法我们会演示文件操作的三步走:打开文件(创建FileOutputStream对象)、操作文件、关闭文件
write(int b):写入当字节
public class Test {
public static void main(String[] args) throws IOException {
FileOutputStream f = new FileOutputStream(".\\src\\cn.hjblogs\\demo\\a.txt");
f.write(97); // 写入一个字节 97--->a ASCII码表
f.close();
}
}
write(byte[] b):写入字节数组里面所有字节数据
public class Test {
public static void main(String[] args) throws IOException {
FileOutputStream f = new FileOutputStream(".\\src\\cn\\hjblogs\\demo\\a.txt");
byte[] bytes = {
97,98,99,100,101}; // 97-a 98-b 99-c 100-d 101-e ASCII码
f.write(bytes); // 这样应该理解写入字节数组里面的数字是什么意思了!
f.close();
}
}
---- 关闭文件(通道)close()方法
- 文件关闭:在写入数据完成后,通常需要手动调用 close() 方法关闭文件流,以释放资源并确保数据写入完成。
(2)try-with-resources语法:不用手动调用close方法关闭文件了
基本格式如下:
try (ResourceType resource = new ResourceType()) {
// 使用资源的代码
} catch (异常类型 e) {
// 异常处理代码
}
- ResourceType resource = new ResourceType():这里 ResourceType 是一个实现了 AutoCloseable 或 Closeable 接口的类,比如 FileInputStream、BufferedReader 等。这个资源会在 try 块结束时自动关闭,无需手动调用 close() 方法。
只有实现了 AutoCloseable 或 Closeable 接口的类才能使用这种语法自动close.(注意不要与异常处理混淆) - catch 块:异常处理部分是可选的。如果资源在使用过程中抛出了异常,程序会自动进入 catch 块处理异常。
这里的catch是可以不要,采用方法签名抛出异常也可以;文件操作这里这些方法都要手动抛出异常处理编译时异常才行
下面的例子都会采用这种简洁语法进行处理。
(3)覆盖模式
【注】:输出流的覆盖模式构造方法在打开流的时候就会将文件清空,所以如果输出流和输入流是操作同一个文件,一定要考虑创建流对象的位置和顺序,不然输入流就会读入的是被输出流清空的文件然后读到一个空指针null
方法签名:
public class Test {
public static void main(String[] args) throws IOException {
// 写入 hejuzs66667777
try(FileOutputStream fos = new FileOutputStream(".\\src\\cn\\hjblogs\\demo\\a.txt")){
// 使用try-with-resources语句,不用手动关闭流
// 使用String .getBytes()方法,将字符串转换为字节数组在将字节写入文件
fos.write("hejuzs66667777".getBytes());
}
}
}
catch:
public class Test {
public static void main(String[] args){
// 写入 66667777hejuzs
try(FileOutputStream fos = new FileOutputStream(".\\src\\cn\\hjblogs\\demo\\a.txt")){
// 使用try-with-resources语句,不用手动关闭流
// 使用String .getBytes()方法,将字符串转换为字节数组在将字节写入文件
fos.write("66667777hejuzs".getBytes());
}
catch (IOException e){
e.printStackTrace();
}
}
}
(4)追加模式
如果想要续写,打开续写开关即可开关位置:创建对象的第二个参数
默认false:表示关闭续写,此时创建对象会清空文件
手动传递true:表示打开续写,此时创建对象不会清空文件
public class Test {
public static void main(String[] args){
// 文件中已有 66667777hejuzs , 追加写入 \nhello world
try(FileOutputStream fos = new FileOutputStream(".\\src\\cn\\hjblogs\\demo\\a.txt", true)){
// 使用try-with-resources语句,不用手动关闭流
// 使用String .getBytes()方法,将字符串转换为字节数组在将字节写入文件
// 注,换行符用 \n, \r\n都行在windows系统中,另外这里的单斜杠是转义字符,所以一个斜杠就可;不需要两个变成真正的斜杠
fos.write("\nhello world".getBytes());
}
catch (IOException e){
e.printStackTrace();
}
}
}
(5)指定写入编码方式:String的getBytes(“utf-8”)接口要传入编码方式:write(“66667777hejuzs\n你好世界”.getBytes(“UTF-8”))
public class Test {
public static void main(String[] args) {
// 写入 66667777hejuzs\n你好世界
try(FileOutputStream fos = new FileOutputStream(".\\a.txt")){
// 使用try-with-resources语句,不用手动关闭流
// 使用String .getBytes()方法,将字符串转换为字节数组在将字节写入文件
fos.write("66667777hejuzs\n你好世界".getBytes("UTF-8"));
// 由于我们的文件是默认是UTF-8编码的,所以我们这里我们不指定编码格式,也可以正常写入中文,但是为了保险起见,还是指定编码格式
}
catch (IOException e){
e.printStackTrace();
}
}
}
---- 关于不同操作系统中换行符的说明
- windows: \r\n
- Linux: \n
- Mac: \r
细节:在windows操作系统当中. java对回车换行进行了优化。虽然完整的是\r\n.但是我们写其中一个\r或者\n。java也可以实现换行,因为java在底层会补全。
建议: 不要省略,还是写全了(代码规范)。
2 FileInputStream(操作本地文件的字节输入流:读入)
操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来。
(1)打开文件、操作文件、关闭文件三步套路
---- 构造方法:FileIutputStream 提供了几种常见的构造方法,用于创建文件输入流
FileInputStream(String name): 通过文件路径名创建 FileInputStream 对象。
FileInputStream(File file): 通过 File 对象创建 FileInputStream。
注:如果文件不存在,直接报错。
---- 读入方法 int read()、int read(byte[] b)
int read():一次读取一个字节,读取文件中的一个字节,返回一个0-255的整数表示字节的值,若已到达文件末尾,则返回 -1。(每次只能读取一个字节数字)
通常要结合char强转使用。
特别注意(在while循环读取整个文本时要注意这个),这个方法和迭代器的next有点像,每次调用不光返回值还会将指针向后移动一位(就算再while上面调用还是会这样)
int read(byte[] b):一次读取多个字节(返回值表示本次读取到的字节的个数,不是值),从文件中读取字节并将其存储到字节数组 b 中(每次读取会尽可能把数组装满),返回读取的字节数,如果到达文件末尾返回 -1。
通常结合String(字节数组,start,end)构造方法来使用,start、end分别是字节数组起始索引
---- int read(byte[] bytes, int off, int len):多个多个字节的读,返回实际读取的字节数,读到末尾返回-1
int read()
public class Test {
public static void main(String[] args) throws IOException {
// a.txt文件里面内容 : abcdefg
try(FileInputStream f = new FileInputStream("a.txt")){
int b1 = f.read();
System.out.println(b1); // 97
System.out.println((char)b1); // a
int b2 = f.read();
System.out.println(b2); // 98
System.out.println((char)b2); // b
int b3 = f.read();
System.out.println(b3); // 99
System.out.println((char)b3); // c
int b4 = f.read();
System.out.println(b4); // 100
System.out.println((char) b4); // d
int b5 = f.read();
System.out.println(b5); // 101
System.out.println((char)b5); // e
int b6 = f.read();
System.out.println(b6); // 102
System.out.println((char)b6); // f
int b7 = f.read();
System.out.println(b7); // 103
System.out.println((char)b7); // g
// 此时已经读到文件末尾,再读就是-1
int b8 = f.read();
System.out.println(b8); // -1
}
}
}
细节1: 一次读一个字节,读出来的是数据在ASCII码上对应的数字(中文另说,因为一个中文字符在不同编码方式下,需要占用的字节个数不一样)
细节2: 读到文件末尾了. read方法返回-1。
int read(byte[] b)
直接使用String(字节数组)的构造方法,读取到后面会发生覆盖现象
public class Test {
public static void main(String[] args) throws IOException {
// a.t