🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (91平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀Java EE(94平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- 2.2 数据流-----文件内容读写
- 2.2.1 字节流
- 2.2.1.1 InputStream概述
- 2.2.1.2 FileInputStream概述
- 2.2.1.3 利用Scanner进行字符读取
- 2.2.1.4 OutputStream概述
- 2.2.1.5 FileOutputStream概述
- 2.2.2 字符流
- 2.2.2.1 Reader
- 2.2.2.2 Writer
2.2 数据流-----文件内容读写
文件内容读写的操作中常见的包含以下操作:读文件,写文件,打开文件,关闭文件.
Java中通过"流"这样的一组类进行上述的文件操作.
Java中常见的流分为一下两类:字节流和字符流
- 字节流:以字节为单位进行读写,包括以下两个类,InputStream和OutputStream.
- 字符流:以字符为单位进行读写,包括以下两个类,Reader和Writer.
字节流和字符流后面的用法基本上一样,核心操作就是read读,write写.close关闭.
举例说明:水流
接水和放水的方式有好几种,可以一次性全部放(取)完,也可以分为多次放(取),文件的读写操作也是同样的道理.
文件的读写可以一次性读取完全部的内容,也可以规定一次性读取的字节数.
2.2.1 字节流
字节流这里读取的文件可以是二进制文件,也可以是文本文件
2.2.1.1 InputStream概述
方法:
修饰符及 | 返回值类型 | ⽅法签名说明 |
---|---|---|
int | read() | 读取⼀个字节的数据,返回-1代表已经完全读完了 |
int | read(byte[] b) | 最多读取b.length字节的数据到b中,返回实际读到的数量;-1代表以及读完了 |
int | read(byte[] b,int off,int len) | 最多读取len-off字节的数据到b中,放在从off开始,返回实际读到的数量;-1代表以及读完了 |
void | close() | 关闭字节流 |
[注意事项]
- 第二个read方法中给出了一个字节数组,代表的是,我们需要先准备好一个空数组,方法执行完毕之后,就会把读取到的数据填写到byte数组中,其中初始化byte数组的时候,可以指定一次性读取的字节数.也就是说,一次性读取字节数的多少取决于数组的长度,read会尽可能填满数组,如果填不满,能填多少填多少.
举例说明:取食堂打饭
取食堂打饭的时候,我们会先拿上一个空盘子,也就是固定大小的空数组,之后我们把这个盘子递给食堂的阿姨,也就是把空数组递给read方法,但是这里的食堂的阿姨不一样,这里的食堂阿姨不会手抖,会尽可能地把整个盘子都给你打满,如果剩下的饭不多了,食堂阿姨也没有办法,只能把盆子里剩下的所有的饭都给你打上,你也只能吃锅底了.(填不满,能填多少填多少)
- 我们看到,前三个方法读取的时候,读取的应该是字节流,但是我们返回值的类型却是int类型,这是为什么呢?
- 首先,在我们读到文件结束位置的时候,会返回-1,为了有余地表示-1,所以用int.
- 可以确保杜处的字节,都是正数,都是按"无符号"的方式处理的.
- 需要注意的是,我们在最后必须要关闭流,否则出现不可预知的bug.
- InputStream只是一个抽象类,要使用的时候,还得有具体的类去实现.关于InputStream的实现类有很多,基本可以认为不同的输⼊设备都可以对应⼀个InputStream类,(包括蓝牙输入,网卡输入,硬盘输入)我们现在只关⼼从硬盘中读取,所以使用FileInputStream.
2.2.1.2 FileInputStream概述
构造方法:
签名 | 说明 |
---|---|
FileInputStream(File file) | 利⽤File构造⽂件输⼊流 |
FileInputStream(String name) | 利⽤⽂件路径构造⽂件输⼊流 |
第一个构造方法传入的是File对象,也就是通过File类提前创建好的文件对象来构造文件输入流.
第二个方法是通过传入文件的绝对路径或者是相对路径来构造文件输入流.
代码示例:输入效率问题
public static void main(String[] args) throws IOException {File file = new File("./test.txt");file.createNewFile();try(OutputStream outputStream = new FileOutputStream("./test.txt")){byte[] array = {(byte) 'a',(byte)'b',(byte)'c',(byte)'d'};//注意强制类型转换outputStream.write(array);outputStream.flush();}try(InputStream inputStream = new FileInputStream("./test.txt")){//使用try()的方式来保证最后数据流一定会被关闭byte[] array = new byte[10];//一次读取10个字节while (true){int a = inputStream.read(array);//返回实际读取到的数量if (a == -1){//读取到文件的末尾-1,就停止读取break;}for (int i = 0; i < a; i++) {//这里之所以不写array.length是因为读到文件末尾的时候//最后实际读到的字节数不一定读慢10个字节System.out.println(array[i]);}}}}
public static void main(String[] args) throws IOException {File file = new File("./test.txt");file.createNewFile();try(OutputStream outputStream = new FileOutputStream("./test.txt")){byte[] array = {(byte) 'a',(byte)'b',(byte)'c',(byte)'d'};//注意强制类型转换outputStream.write(array);outputStream.flush();}try(InputStream inputStream = new FileInputStream("./test.txt")){//使用try()的方式来保证最后数据流一定会被关闭while (true){int a = inputStream.read();//返回实际读取到的数量if (a == -1){//读取到文件的末尾-1,就停止读取break;}System.out.println((char) a);}}}
注:OutputStream先不用管,我们先看InputStream的方法.
- 第一种模式读取数据是每次读取10个字节的数据,但是第二个就是每次读取1个字节的数据.这两种方法相比,第一种方法的效率相对较高,是应为相对第一种方法,第二种方法读取硬盘的频率相对较高,读取硬盘的操作本来就是一个非常低效的操作,所以第二种方法效率相对第一种肯定低一些.
- 我们知道,在读取完数据之后,输入流一定要被关闭,但是为了把我们的代码写得"优雅"一些,也就是不调用close方法即可关闭输入流,我们就可以把InputStream的创建放入try()中,但是可以这样做有着一定的前提条件,就是InputStream这个类实现了Closeable接口.
public abstract class InputStream implements Closeable
2.2.1.3 利用Scanner进行字符读取
上述例子中,我们看到了对字符类型直接使用InputStream进行读取是非常麻烦且困难的,所以,我
们使用⼀种我们之前比较熟悉的类来完成该⼯作,就是Scanner类
构造⽅法 | 说明 |
---|---|
Scanner(InputStream is, String charset) | 使⽤charset字符集进行is的扫描读取 |
从上述Scanner的构造方法中,我们看到,Scanner的其中一个构造方法中,有一个参数就是输入流类型的参数.也就是说,我们在之前使用idea的控制台输入的一些内容,其实本质上就是一种输入流类型的数据.
public static void main7(String[] args) throws IOException {try(InputStream inputStream = new FileInputStream("./test.txt")){//之所以把Scanner放在try中,是因为最后它也要关闭try(Scanner scanner = new Scanner(inputStream)) {//这里Scanner传入的对象是一个输入流对象while (scanner.hasNext()){System.out.println(scanner.next());}}}}
2.2.1.4 OutputStream概述
方法:
修饰符及返回值类型 | ⽅法签名 | 说明 |
---|---|---|
void | write(int b) | 写⼊要给字节的数据 |
void | write(byte[] b) | 将b这个字符数组中的数据全部写⼊os中 |
int | write(byte[] b,int off,int len) | 将b这个字符数组中从off开始的数据写⼊os中,⼀共写len个 |
void | close() | 关闭字节流 |
void | flush() | 重要:我们知道I/O的速度是很慢的,所以,⼤多的OutputStream为了减少设备操作的次数,在写数据的时候都会将数据先暂时写⼊内存的⼀个指定区域⾥,直到该区域满了或者其他指定条件时才真正将数据写⼊设备中,这个区域⼀般称为缓冲区。但造成⼀个结果,就是我们写的数据,很可能会遗留⼀部分在缓冲区中。需要在最后或者合适的位置,调⽤flush(刷新)操作,将数据刷到设备中。 |
[注意事项]
- 上面方法的原理和InputStream类的方法相似,这里不再赘述.
- OutputStream是一个抽象类,要使用具体的类来实现,现在还是只关心写的内容,所以使用FileOutStream
2.2.1.5 FileOutputStream概述
构造方法:
签名 | 说明 |
---|---|
FileOutputStream(File file) | 利⽤File构造⽂件输出流 |
FileOutputStream(String name) | 利⽤⽂件路径构造⽂件输出流 |
示例1:
public static void main(String[] args) throws IOException {try(OutputStream outputStream = new FileOutputStream("./test.txt"){byte[] bytes = {(byte)'a',(byte)'b',(byte)'c'};outputStream.write(bytes);outputStream.flush();}}
[注意事项]
- 按照写方式打开文件之后,会把==文件中的内容清空,==如果不想把文件中的内容清空,这时候我们就需要给输出流的构造方法中加上一个true,表示以"追加写"的方式打开.
public static void main(String[] args) throws IOException {try(OutputStream outputStream = new FileOutputStream("./test.txt",true){byte[] bytes = {(byte)'a',(byte)'b',(byte)'c'};outputStream.write(bytes);outputStream.flush();}}
- 如果我们在输出流中的write方法中写入的是数字,也会被ascii转化为字符.
2.2.2 字符流
字符流这里只可以读取文本文件.也就是读取的内容都是可以经过编码表编码为合法字符的.
2.2.2.1 Reader
构造方法:
Reader的构造方法和上述两个类的构造方法使用方法相同,这里不再赘述.
常用方法:
常用方法有两个,一个是read(),这个方法一次读取一个字符.一个是read(char[] cbuf),一次读取指定大小字符的字符串.
[注意事项]
- 因为这里读取的是文本中的字符,这里常用的第二个read方法数组的类型是char[]类型,由于之前的字节流读取的是字节,所以是byte[].
- 问题:写在文本文件中的中的如果是中文字符串,中文字符串的每一个字符是3字节,而char[]类型数组中的每一个位置是2字节,那么read(char[] cbuf)是如何做到精准读取的呢?
解答:实际上,因为Java对上述的代码进行了编码转码,read操作在读取的时候,可以识别出String类型的每一个字符是utf-8编码格式(3字节/字),之后编译器就可以把utf-8格式的字符转码为Unicode(2字节/字)编码方式的字符.
代码演示:
public static void main10(String[] args) throws IOException {try(Reader reader = new FileReader("./test2.txt")){while (true){char[] chars = new char[10];int a = reader.read(chars);//在读取的时候,编译器会自动把汉字String中utf8的编码方式转为char中Unicode的编码方式//其中String中utf8编码方式是3字节,Unicode编码是2字节,char类型正好可以存下2字节if (a == -1){break;}for (int i = 0; i < a; i++) {System.out.println(chars[i]);}}}}
2.2.2.2 Writer
构造方法:略
常用方法:
方法 | 用法 |
---|---|
write() | 一次写一个字符 |
write(char[] cbuf) | 一次写多个 |
write(String str) | 一次写一个字符串 |
代码实例:
public static void main9(String[] args) throws IOException {try(Writer writer = new FileWriter("./test2.txt")){char[] chars = {'你','好','世','界'};writer.write(chars);writer.flush();}}
和OutputStream一样,它们在写内容的时候一样会清空文件内容,我们如果我们想重复写入,那么就要在构造方法后面加上true.
public static void main9(String[] args) throws IOException {try(Writer writer = new FileWriter("./test2.txt",true)){char[] chars = {'你','好','世','界'};writer.write(chars);writer.flush();}}