IO流你了解多少
IO流你了解多少
🏠个人主页:shark-Gao
🧑个人简介:大家好,我是shark-Gao,一个想要与大家共同进步的男人😉😉
🎉目前状况:23届毕业生,目前在某公司实习👏👏
❤️欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,我亲爱的大佬😘
🖥️个人小站 :个人博客,欢迎大家访问
CSDN发布的文章同样会在个人小站折腾后记 个人博客进行同步,欢迎大家指点!!!
1 初始IO
IO
,即in和out,也就是输入和输出,指应用程序和外部设备之间的数据传递,常见的外部设备包括文件、管道、网络连接。
Java 中是通过流处理IO 的,那么什么是流?
流(Stream)
,是一个抽象的概念,是指一连串的数据(字符或字节),是以先进先出的方式发送信息的通道。
当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,或是网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。这时候你就可以想象数据好像在这其中“流”动一样。
一般来说关于流的特性有下面几点:
- 先进先出:最先写入输出流的数据最先被输入流读取到。
- 顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(
RandomAccessFile
除外) - 只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
1.1 为什么要学习IO流
- 通过变量,数组,或者集合存储数据
- 都是不能永久化存储 , 因为数据都是存储在内存中
- 只要代码运行结束,所有数据都会丢失
- 使用IO流
- 1,将数据写到文件中,实现数据永久化存储
- 2,把文件中的数据读取到内存中(Java程序)
1.2 什么是IO流
- I 表示intput ,是数据从硬盘进内存的过程,称之为读。
- O 表示output ,是数据从内存到硬盘的过程。称之为写
- IO的数据传输,可以看做是一种数据的流动,按照流动的方向,以内存为参照物,进行读写操作
- 简单来说:内存在读,内存在写
1.3 IO流的分类
- 按照流向区分
- 输入流 : 用来读取数据
- 输出流 : 用来写入数据
- 按照类型区分
- 字节流
- 字符流
- 注意 :
- 字节流可以操作任意文件
- 字符流只能操作纯文本文件
- 用windows记事本打开能读的懂,那么这样的文件就是纯文本文件。
1、输入流与输出流
输入与输出是相对于应用程序而言的,比如文件读写,读取文件是输入流,写文件是输出流,这点很容易搞反。
2、字节流与字符流
字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的单元是数据单元是8位的字节,字符流操作的是数据单元为16位的字符。
为什么要有字符流?
Java中字符是采用Unicode标准,Unicode 编码中,一个英文字母或一个中文汉字为两个字节。
而在UTF-8编码中,一个中文字符是3个字节。例如下面图中,“云深不知处”5个中文对应的是15个字节:-28-70-111-26-73-79-28-72-115-25-97-91-27-92-124
那么问题来了,如果使用字节流处理中文,如果一次读写一个字符对应的字节数就不会有问题,一旦将一个字符对应的字节分裂开来,就会出现乱码了。为了更方便地处理中文这些字符,Java就推出了字符流。
字节流和字符流的其他区别:
字节流一般用来处理图像、视频、音频、PPT、Word等类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,但不能处理图像视频等非文本文件。用一句话说就是:字节流可以处理一切文件,而字符流只能处理纯文本文件。
字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升就不是那么大了。
3、节点流和处理流
节点流:直接操作数据读写的流类,比如FileInputStream
处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如BufferedInputStream(缓冲字节流)
处理流和节点流应用了Java的装饰者设计模式。
下图就很形象地描绘了节点流和处理流,处理流是对节点流的封装,最终的数据处理还是由节点流完成的。
在诸多处理流中,有一个非常重要,那就是缓冲流。
我们知道,程序与磁盘的交互相对于内存运算是很慢的,容易成为程序的性能瓶颈。减少程序与磁盘的交互,是提升程序效率一种有效手段。缓冲流,就应用这种思路:普通流每次读写一个字节,而缓冲流在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。
2 字节流输出流
InputStream
类有很多的实现子类,下面列举了一些比较常用的:
2.1 字节输出流入门
-
FileOutputStream类 :
OutputStream
有很多子类,我们从最简单的一个子类开始。java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件
-
字节输出流
OutputStream
主要方法:- write(byte[] b) :将 b.length 个字节从指定 byte 数组写入此文件输出流中。
- write(byte[] b, int off, int len) :将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。
- write(int b) :将指定字节写入此文件输出流。
- close() :关闭此输入流并释放与该流关联的所有系统资源。
public class FileOutputStreamConstructor throws IOException {public static void main(String[] args) {// 使用File对象创建流对象File file = new File("a.txt");FileOutputStream fos = new FileOutputStream(file);// 使用文件名称创建流对象FileOutputStream fos = new FileOutputStream("b.txt");} }
-
字节输出流写数据快速入门
- 创建字节输出流对象。
- 写数据
- 释放资源
package com.itheima.outputstream_demo;import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;/*字节输出流写数据快速入门 :1 创建字节输出流对象。2 写数据3 释放资源*/ public class OutputStreamDemo1 {public static void main(String[] args) throws IOException {// 创建字节输出流对象// 如果指定的文件不存在 , 会自动创建文件// 如果文件存在 , 会把文件中的内容清空FileOutputStream fos = new FileOutputStream("day11_demo\\\\a.txt");// 写数据// 写到文件中就是以字节形式存在的// 只是文件帮我们把字节翻译成了对应的字符 , 方便查看fos.write(97);fos.write(98);fos.write(99);// 释放资源// while(true){}// 断开流与文件中间的关系fos.close();} }
2.2 字节输出流写数据的方法
-
字节流写数据的方法
- 1 void write(int b) 一次写一个字节数据
- 2 void write(byte[] b) 一次写一个字节数组数据
- 3 void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据
package com.itheima.outputstream_demo;import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;/*字节流写数据的3种方式1 void write(int b) 一次写一个字节数据2 void write(byte[] b) 一次写一个字节数组数据3 void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据*/ public class OutputStreamDemo2 {public static void main(String[] args) throws IOException {// 创建字节输出流对象FileOutputStream fos = new FileOutputStream("day11_demo\\\\a.txt");// 写数据 // 1 void write(int b) 一次写一个字节数据fos.write(97);fos.write(98);fos.write(99);// 2 void write(byte[] b) 一次写一个字节数组数据byte[] bys = {65, 66, 67, 68, 69};fos.write(bys);// 3 void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据fos.write(bys, 0, 3);// 释放资源fos.close();} }
2.3 写数据的换行和追加写入
package com.itheima.outputstream_demo;import java.io.FileOutputStream;
import java.io.IOException;/*字节流写数据的换行和追加写入1 字节流写数据如何实现换行呢?写完数据后,加换行符windows : \\r\\nlinux : \\nmac : \\r2 字节流写数据如何实现追加写入呢?通过构造方法 : public FileOutputStream(String name,boolean append)创建文件输出流以指定的名称写入文件。如果第二个参数为true ,不会清空文件里面的内容*/
public class OutputStreamDemo3 {public static void main(String[] args) throws IOException {// 创建字节输出流对象FileOutputStream fos = new FileOutputStream("day11_demo\\\\a.txt");// void write(int b) 一次写一个字节数据fos.write(97);// 因为字节流无法写入一个字符串 , 把字符串转成字节数组写入fos.write("\\r\\n".getBytes());fos.write(98);fos.write("\\r\\n".getBytes());fos.write(99);fos.write("\\r\\n".getBytes());// 释放资源fos.close();}
}
package com.itheima.outputstream_demo;import java.io.FileOutputStream;
import java.io.IOException;/*字节流写数据的换行和追加写入1 字节流写数据如何实现换行呢?写完数据后,加换行符windows : \\r\\nlinux : \\nmac : \\r2 字节流写数据如何实现追加写入呢?通过构造方法 : public FileOutputStream(String name,boolean append)创建文件输出流以指定的名称写入文件。如果第二个参数为true ,不会清空文件里面的内容*/
public class OutputStreamDemo3 {public static void main(String[] args) throws IOException {// 创建字节输出流对象// 追加写数据// 通过构造方法 : public FileOutputStream(String name,boolean append) : 追加写数据FileOutputStream fos = new FileOutputStream("day11_demo\\\\a.txt" , true);// void write(int b) 一次写一个字节数据fos.write(97);// 因为字节流无法写入一个字符串 , 把字符串转成字节数组写入fos.write("\\r\\n".getBytes());fos.write(98);fos.write("\\r\\n".getBytes());fos.write(99);fos.write("\\r\\n".getBytes());// 释放资源fos.close();}// 写完数据换行操作private static void method1() throws IOException {// 创建字节输出流对象FileOutputStream fos = new FileOutputStream("day11_demo\\\\a.txt");// void write(int b) 一次写一个字节数据fos.write(97);// 因为字节流无法写入一个字符串 , 把字符串转成字节数组写入fos.write("\\r\\n".getBytes());fos.write(98);fos.write("\\r\\n".getBytes());fos.write(99);fos.write("\\r\\n".getBytes());// 释放资源fos.close();}
}
3 字节输入流
3.1 字节输入流介绍
-
字节输入流类
- InputStream类 : 字节输入流最顶层的类 , 抽象类
— FileInputStream类 : FileInputStream extends InputStream
- InputStream类 : 字节输入流最顶层的类 , 抽象类
-
字节输入流
InputStream
主要方法:- read() :从此输入流中读取一个数据字节。
- read(byte[] b) :从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。
- read(byte[] b, int off, int len) :从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。
- close():关闭此输入流并释放与该流关联的所有系统资源。
-
步骤
- 创建输入流对象
- 读数据
- 释放资源
-
package com.itheima.inputstream_demo;import java.io.FileInputStream; import java.io.IOException;/*字节输入流写数据快速入门 : 一次读一个字节第一部分 : 字节输入流类InputStream类 : 字节输入流最顶层的类 , 抽象类--- FileInputStream类 : FileInputStream extends InputStream第二部分 : 构造方法public FileInputStream(File file) : 从file类型的路径中读取数据public FileInputStream(String name) : 从字符串路径中读取数据第三部分 : 字节输入流步骤1 创建输入流对象2 读数据3 释放资源*/ public class FileInputStreamDemo1 {public static void main(String[] args) throws IOException {// 创建字节输入流对象// 读取的文件必须存在 , 不存在则报错FileInputStream fis = new FileInputStream("day11_demo\\\\a.txt");// 读数据 , 从文件中读到一个字节// 返回的是一个int类型的字节// 如果想看字符, 需要强转int by = fis.read();System.out.println((char) by);// 释放资源fis.close();} }
3.2 字节输入流读多个字节
package com.itheima.inputstream_demo;import java.io.FileInputStream;
import java.io.IOException;/*字节输入流写数据快速入门 : 读多个字节第一部分 : 字节输入流类InputStream类 : 字节输入流最顶层的类 , 抽象类--- FileInputStream类 : FileInputStream extends InputStream第二部分 : 构造方法public FileInputStream(File file) : 从file类型的路径中读取数据public FileInputStream(String name) : 从字符串路径中读取数据第三部分 : 字节输入流步骤1 创建输入流对象2 读数据3 释放资源*/
public class FileInputStreamDemo2 {public static void main(String[] args) throws IOException {// 创建字节输入流对象// 读取的文件必须存在 , 不存在则报错FileInputStream fis = new FileInputStream("day11_demo\\\\a.txt");// 读数据 , 从文件中读到一个字节// 返回的是一个int类型的字节// 如果想看字符, 需要强转
// int by = fis.read();
// System.out.println(by);
// by = fis.read();
// System.out.println(by);
// by = fis.read();
// System.out.println(by);
//
// by = fis.read();
// System.out.println(by);
// by = fis.read();
// System.out.println(by);
// by = fis.read();
// System.out.println(by);// 循环改进int by;// 记录每次读到的字节while ((by = fis.read()) != -1) {System.out.print((char) by);}// 释放资源fis.close();}
}
3.3 图片的拷贝
package com.itheima.inputstream_demo;import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;/*需求 : 把 "图片路径\\xxx.jpg" 复制到当前模块下分析:复制文件,其实就把文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)数据源:xxx.jpg --- 读数据 --- FileInputStream目的地:模块名称\\copy.jpg --- 写数据 --- FileOutputStream*/
public class FileInputStreamDemo2 {public static void main(String[] args) throws IOException {// 创建字节输入流对象FileInputStream fis = new FileInputStream("D:\\\\传智播客\\\\安装包\\\\好看的图片\\\\liqin.jpg");// 创建字节输出流FileOutputStream fos = new FileOutputStream("day11_demo\\\\copy.jpg");// 一次读写一个字节int by;while ((by = fis.read()) != -1) {fos.write(by);}// 释放资源fis.close();fos.close();}
}
3.4 异常的捕获处理
-
JDK7版本之前处理方式 : 手动释放资源
package com.itheima.inputstream_demo;import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;/*需求 : 对上一个赋值图片的代码进行使用捕获方式处理*/ public class FileInputStreamDemo4 {public static void main(String[] args) {FileInputStream fis = null ;FileOutputStream fos = null;try {// 创建字节输入流对象fis = new FileInputStream("D:\\\\传智播客\\\\安装包\\\\好看的图片\\\\liqin.jpg");// 创建字节输出流fos = new FileOutputStream("day11_demo\\\\copy.jpg");// 一次读写一个字节int by;while ((by = fis.read()) != -1) {fos.write(by);}} catch (IOException e) {e.printStackTrace();} finally {// 释放资源if(fis != null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}// 释放资源if(fos != null){try {fos.close();} catch (IOException e) {e.printStackTrace();}}}} }
-
JDK7版本优化处理方式 : 自动释放资源
-
JDK7优化后可以使用 try-with-resource 语句 , 该语句确保了每个资源在语句结束时自动关闭。
简单理解 : 使用此语句,会自动释放资源 , 不需要自己在写finally代码块了 -
格式 :
格式 : try (创建流对象语句1 ; 创建流对象语句2 ...) {// 读写数据} catch (IOException e) {处理异常的代码...}举例 :try ( FileInputStream fis1 = new FileInputStream("day11_demo\\\\a.txt") ; FileInputStream fis2 = new FileInputStream("day11_demo\\\\b.txt") ) {// 读写数据} catch (IOException e) {处理异常的代码...}
-
-
代码实践
package com.itheima.inputstream_demo;import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException;/*JDK7版本优化处理方式需求 : 对上一个赋值图片的代码进行使用捕获方式处理*/ public class FileInputStreamDemo5 {public static void main(String[] args) {try (// 创建字节输入流对象FileInputStream fis = new FileInputStream("D:\\\\传智播客\\\\安装包\\\\好看的图片\\\\liqin.jpg");// 创建字节输出流FileOutputStream fos = new FileOutputStream("day11_demo\\\\copy.jpg")) {// 一次读写一个字节int by;while ((by = fis.read()) != -1) {fos.write(by);}// 释放资源 , 发现已经灰色 , 提示多余的代码 , 所以使用 try-with-resource 方式会自动关流// fis.close();// fos.close();} catch (IOException e) {e.printStackTrace();}} }
3.4 字节输入流一次读一个字节数组
-
FileInputStream类 :
- public int read(byte[] b) : 从输入流读取最多b.length个字节的数据, 返回的是真实读到的数据个数
package com.itheima.inputstream_demo;import javax.sound.midi.Soundbank; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException;/*FileInputStream类 :public int read(byte[] b):1 从输入流读取最多b.length个字节的数据2 返回的是真实读到的数据个数*/ public class FileInputStreamDemo6 {public static void main(String[] args) throws IOException {// 创建字节输入流对象FileInputStream fis = new FileInputStream("day11_demo\\\\a.txt");// public int read(byte[] b): // 1 从输入流读取最多b.length个字节的数据 // 2 返回的是真实读到的数据个数byte[] bys = new byte[3];// int len = fis.read(bys); // System.out.println(len);// 3 // System.out.println(new String(bys));// abc // // len = fis.read(bys); // System.out.println(len);// 2 // System.out.println(new String(bys));// efcSystem.out.println("==========代码改进===============");// int len = fis.read(bys); // System.out.println(len);// 3 // System.out.println(new String(bys, 0, len));// abc // // len = fis.read(bys); // System.out.println(len);// 2 // System.out.println(new String(bys, 0, len));// efSystem.out.println("==========代码改进===============");int len;while ((len = fis.read(bys)) != -1) {System.out.print(new String(bys , 0 , len));}fis.close();} }
-
对复制图片的代码进行使用一次读写一个字节数组的方式进行改进
package com.itheima.inputstream_demo;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;/*需求 : 对复制图片的代码进行使用一次读写一个字节数组的方式进行改进FileInputStream类 :public int read(byte[] b):1 从输入流读取最多b.length个字节的数据2 返回的是真实读到的数据个数*/ public class FileInputStreamDemo7 {public static void main(String[] args) throws IOException {// 创建字节输入流对象FileInputStream fis = new FileInputStream("D:\\\\传智播客\\\\安装包\\\\好看的图片\\\\liqin.jpg");// 创建字节输出流FileOutputStream fos = new FileOutputStream("day11_demo\\\\copy.jpg");byte[] bys = new byte[1024];int len;// 每次真实读到数据的个数int by;while ((len = fis.read(bys)) != -1) {fos.write(bys, 0, len);}// 释放资源fis.close();fos.close();} }
4 字符流
与字节流类似,字符流也有两个抽象基类,分别是Reader
和Writer
。其他的字符流实现类都是继承了这两个类。
以Reader
为例,它的主要实现子类如下图:
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流。
4.4.1 字符输入流【Reader】
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public int read()
: 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为int类型。返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次最多读取cbuf.length个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。public int read(char[] cbuf,int off,int len)
:从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储。每次最多读取len个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。public void close()
:关闭此流并释放与此流相关联的任何系统资源。
小贴士:close方法,当完成流的操作时,必须调用此方法,释放系统资源。
4.4.2 FileReader类
java.io.FileReader
类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。如果该文件不存在,则报FileNotFoundException。如果传入的是一个目录,则会报IOException异常。
package com.atguigu.fileio;import org.junit.Test;import java.io.FileReader;
import java.io.IOException;public class FRRead {@Testpublic void test01() throws IOException {// 使用文件名称创建流对象FileReader fr = new FileReader("read.txt");// 定义变量,保存数据int b;// 循环读取while ((b = fr.read())!=-1) {System.out.println((char)b);}// 关闭资源fr.close();
/*输出结果:尚硅谷*/}@Testpublic void test02()throws IOException {// 使用文件名称创建流对象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));}// 关闭资源fr.close();/*输出结果:尚硅谷硅最后错误数据硅,是因为最后一次流中只有一个字符“谷”,读取一个字符没有覆盖char[]数组cbuf的所有元素*/}@Testpublic void test03() throws IOException {// 使用文件名称创建流对象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();/*输出结果:尚硅谷*/}}
4.4.3 字符输出流【Writer】
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int c)
写入单个字符。public void write(char[] cbuf)
写入字符数组。public void write(char[] cbuf, int off, int len)
写入字符数组的某一部分,off数组的开始索引,len写的字符个数。public void write(String str)
写入字符串。public void write(String str, int off, int len)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。public void flush()
刷新该流的缓冲。public void close()
关闭此流,但要先刷新它。
4.4.4 FileWriter类
java.io.FileWriter
类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。如果文件不存在,则会自动创建。如果文件已经存在,则会清空文件内容,写入新的内容。
1、写出字符数据
package com.atguigu.fileio;import org.junit.Test;import java.io.FileWriter;
import java.io.IOException;public class FWWrite {@Testpublic void test01()throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter("fw.txt");// 写出数据fw.write(97); // 写出第1个字符fw.write('b'); // 写出第2个字符fw.write('C'); // 写出第3个字符fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。/*【注意】FileWriter与FileOutputStream不同。如果不关闭,数据只是保存到缓冲区,并未保存到文件。*/// fw.close();}@Testpublic void test02()throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter("fw.txt");// 字符串转换为字节数组char[] chars = "尚硅谷".toCharArray();// 写出字符数组fw.write(chars); // 尚硅谷// 写出从索引1开始,2个字符。fw.write(chars,1,2); // 硅谷// 关闭资源fw.close();}@Testpublic void test03()throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter("fw.txt");// 字符串String msg = "尚硅谷";// 写出字符数组fw.write(msg); //尚硅谷// 写出从索引1开始,2个字符。fw.write(msg,1,2); // 硅谷// 关闭资源fw.close();}
}
2、续写
public FileWriter(File file,boolean append)
: 创建文件输出流以写入由指定的 File对象表示的文件。public FileWriter(String fileName,boolean append)
: 创建文件输出流以指定的名称写入文件。
这两个构造方法,参数中都需要传入一个boolean类型的值,true
表示追加数据,false
表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:
操作类似于FileOutputStream。
package com.atguigu.fileio;import org.junit.Test;import java.io.FileWriter;
import java.io.IOException;public class FWWriteAppend {@Testpublic void test01()throws IOException {// 使用文件名称创建流对象,可以续写数据FileWriter fw = new FileWriter("fw.txt",true);// 写出字符串fw.write("尚硅谷真棒");// 关闭资源fw.close();}
}
3、换行
package com.atguigu.fileio;import java.io.FileWriter;
import java.io.IOException;public class FWWriteNewLine {public static void main(String[] args) throws IOException {// 使用文件名称创建流对象,可以续写数据FileWriter fw = new FileWriter("fw.txt");// 写出字符串fw.write("尚");// 写出换行fw.write("\\r\\n");// 写出字符串fw.write("硅谷");// 关闭资源fw.close();}
}
4、关闭和刷新
【注意】FileWriter与FileOutputStream不同。因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush
方法了。
flush
:刷新缓冲区,流对象可以继续使用。close
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
代码使用演示:
package com.atguigu.fileio;import java.io.FileWriter;
import java.io.IOException;public class FWWriteFlush {public static void main(String[] args)throws IOException {// 使用文件名称创建流对象FileWriter fw = new FileWriter("fw.txt");// 写出数据,通过flushfw.write('刷'); // 写出第1个字符fw.flush();fw.write('新'); // 继续写出第2个字符,写出成功fw.flush();// 写出数据,通过closefw.write('关'); // 写出第1个字符fw.close();fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closedfw.close();}
}
小贴士:即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。
5 缓冲流
缓冲流,也叫高效流,按照数据类型分类:
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
5.1 构造方法
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
public BufferedReader(Reader in)
:创建一个 新的缓冲输入流。public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
5.2 效率测试
查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(375MB),测试它的效率。
package com.atguigu.buffer;import org.junit.Test;import java.io.*;public class BufferedIO {@Testpublic void testNoBuffer() throws IOException {// 记录开始时间long start = System.currentTimeMillis();// 创建流对象FileInputStream fis = new FileInputStream("jdk8.exe");FileOutputStream fos = new FileOutputStream("copy.exe");// 读写数据byte[] data = new byte[1024];int len;while ((len = fis.read(data)) != -1) {fos.write(data,0,len);}fos.close();fis.close();// 记录结束时间long end = System.currentTimeMillis();System.out.println("普通流复制时间:"+(end - start)+" 毫秒");}@Testpublic void testUseBuffer() throws IOException {// 记录开始时间long start = System.currentTimeMillis();// 创建流对象BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk8.exe"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));// 读写数据int len;byte[] data = new byte[1024];while ((len = bis.read(data)) != -1) {bos.write(data, 0 , len);}bos.close();bis.close();// 记录结束时间long end = System.currentTimeMillis();System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");}
}
5.3 字符缓冲流特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
- BufferedReader:
public String readLine()
: 读一行文字。 - BufferedWriter:
public void newLine()
: 写一行行分隔符,由系统属性定义符号。
package com.atguigu.buffer;import org.junit.Test;import java.io.*;public class BufferedIOLine {@Testpublic void testReadLine()throws IOException {// 创建流对象BufferedReader br = new BufferedReader(new FileReader("in.txt"));// 定义字符串,保存读取的一行文字String line;// 循环读取,读取到最后返回nullwhile ((line = br.readLine())!=null) {System.out.println(line);}// 释放资源br.close();}@Testpublic void testNewLine()throws IOException{// 创建流对象BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));// 写出数据bw.write("尚");// 写出换行bw.newLine();bw.write("硅");bw.newLine();bw.write("谷");bw.newLine();// 释放资源bw.close();}
}
5.4 流的关闭顺序
package com.atguigu.buffer;import org.junit.Test;import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;public class IOClose {@Testpublic void test01() throws IOException {FileWriter fw = new FileWriter("d:/1.txt");BufferedWriter bw = new BufferedWriter(fw);bw.write("hello");fw.close();bw.close();//java.io.IOException: Stream closed/*缓冲流BufferedWriter,把数据先写到缓冲区,默认情况下是当缓冲区满,或调用close,或调用flush这些情况才会把缓冲区的数据输出。bw.close()时,需要把数据从缓冲区的数据输出。数据的流向: 写到bw(缓冲区)-->fw ->"d:/1.txt"此时,我先把fw关闭了,bw的数据无法输出了*/}@Testpublic void test02() throws IOException {FileWriter fw = new FileWriter("d:/1.txt");BufferedWriter bw = new BufferedWriter(fw);bw.write("hello");bw.close();fw.close();/*原则:先关外面的,再关里面的。例如:FileWriter fw = new FileWriter("d:/1.txt"); //里面 穿内衣BufferedWriter bw = new BufferedWriter(fw); //外面 穿外套关闭bw.close(); //先关外面的 先脱外套fw.close(); //再关里面的 再脱内衣*/}
}
6 其他内容
6.1 位、字节、字符
字节(Byte)是计量单位,表示数据量多少,是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节等于八位。
字符(Character)计算机中使用的字母、数字、字和符号,比如’A’、‘B’、’$’、’&'等。
一般在英文状态下一个字母或字符占用一个字节,一个汉字用两个字节表示。
字节与字符:
- ASCII 码中,一个英文字母(不分大小写)为一个字节,一个中文汉字为两个字节。
- UTF-8 编码中,一个英文字为一个字节,一个中文为三个字节。
- Unicode 编码中,一个英文为一个字节,一个中文为两个字节。
- 符号:英文标点为一个字节,中文标点为两个字节。例如:英文句号 . 占1个字节的大小,中文句号 。占2个字节的大小。
- UTF-16 编码中,一个英文字母字符或一个汉字字符存储都需要 2 个字节(Unicode 扩展区的一些汉字存储需要 4 个字节)。
- UTF-32 编码中,世界上任何字符的存储都需要 4 个字节。
6.2 IO流效率对比
首先,对比下普通字节流和缓冲字节流的效率:
public class MyTest {public static void main(String[] args) throws IOException {File file = new File("C:/Mu/test.txt");StringBuilder sb = new StringBuilder();for (int i = 0; i < 3000000; i++) {sb.append("abcdefghigklmnopqrstuvwsyz");}byte[] bytes = sb.toString().getBytes();long start = System.currentTimeMillis();write(file, bytes);long end = System.currentTimeMillis();long start2 = System.currentTimeMillis();bufferedWrite(file, bytes);long end2 = System.currentTimeMillis();System.out.println("普通字节流耗时:" + (end - start) + " ms");System.out.println("缓冲字节流耗时:" + (end2 - start2) + " ms");}// 普通字节流public static void write(File file, byte[] bytes) throws IOException {OutputStream os = new FileOutputStream(file);os.write(bytes);os.close();}// 缓冲字节流public static void bufferedWrite(File file, byte[] bytes) throws IOException {BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(file));bo.write(bytes);bo.close();}
}
运行结果:
普通字节流耗时:250 ms
缓冲字节流耗时:268 ms
这个结果让我大跌眼镜,不是说好缓冲流效率很高么?要知道为什么,只能去源码里找答案了。翻看字节缓冲流的write方法:
public synchronized void write(byte b[], int off, int len) throws IOException {if (len >= buf.length) {/* If the request length exceeds the size of the output buffer,flush the output buffer and then write the data directly.In this way buffered streams will cascade harmlessly. */flushBuffer();out.write(b, off, len);return;}if (len > buf.length - count) {flushBuffer();}System.arraycopy(b, off, buf, count, len);count += len;
}
注释里说得很明白:如果请求长度超过输出缓冲区的大小,刷新输出缓冲区,然后直接写入数据。这样,缓冲流将无害地级联。
但是,至于为什么这么设计,我没有想明白,有哪位明白的大佬可以留言指点一下。
基于上面的情形,要想对比普通字节流和缓冲字节流的效率差距,就要避免直接读写较长的字符串,于是,设计了下面这个对比案例:用字节流和缓冲字节流分别复制文件。
public class MyTest {public static void main(String[] args) throws IOException {File data = new File("C:/Mu/data.zip");File a = new File("C:/Mu/a.zip");File b = new File("C:/Mu/b.zip");StringBuilder sb = new StringBuilder();long start = System.currentTimeMillis();copy(data, a);long end = System.currentTimeMillis();long start2 = System.currentTimeMillis();bufferedCopy(data, b);long end2 = System.currentTimeMillis();System.out.println("普通字节流耗时:" + (end - start) + " ms");System.out.println("缓冲字节流耗时:" + (end2 - start2) + " ms");}// 普通字节流public static void copy(File in, File out) throws IOException {// 封装数据源InputStream is = new FileInputStream(in);// 封装目的地OutputStream os = new FileOutputStream(out);int by = 0;while ((by = is.read()) != -1) {os.write(by);}is.close();os.close();}// 缓冲字节流public static void bufferedCopy(File in, File out) throws IOException {// 封装数据源BufferedInputStream bi = new BufferedInputStream(new FileInputStream(in));// 封装目的地BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(out));int by = 0;while ((by = bi.read()) != -1) {bo.write(by);}bo.close();bi.close();}
}
运行结果:
普通字节流耗时:184867 ms
缓冲字节流耗时:752 ms
这次,普通字节流和缓冲字节流的效率差异就很明显了,达到了245倍。
再看看字符流和缓冲字符流的效率对比:
public class IOTest {public static void main(String[] args) throws IOException {// 数据准备dataReady();File data = new File("C:/Mu/data.txt");File a = new File("C:/Mu/a.txt");File b = new File("C:/Mu/b.txt");File c = new File("C:/Mu/c.txt");long start = System.currentTimeMillis();copy(data, a);long end = System.currentTimeMillis();long start2 = System.currentTimeMillis();copyChars(data, b);long end2 = System.currentTimeMillis();long start3 = System.currentTimeMillis();bufferedCopy(data, c);long end3 = System.currentTimeMillis();System.out.println("普通字节流1耗时:" + (end - start) + " ms,文件大小:" + a.length() / 1024 + " kb");System.out.println("普通字节流2耗时:" + (end2 - start2) + " ms,文件大小:" + b.length() / 1024 + " kb");System.out.println("缓冲字节流耗时:" + (end3 - start3) + " ms,文件大小:" + c.length() / 1024 + " kb");}// 普通字符流不使用数组public static void copy(File in, File out) throws IOException {Reader reader = new FileReader(in);Writer writer = new FileWriter(out);int ch = 0;while ((ch = reader.read()) != -1) {writer.write((char) ch);}reader.close();writer.close();}// 普通字符流使用字符流public static void copyChars(File in, File out) throws IOException {Reader reader = new FileReader(in);Writer writer = new FileWriter(out);char[] chs = new char[1024];while ((reader.read(chs)) != -1) {writer.write(chs);}reader.close();writer.close();}// 缓冲字符流public static void bufferedCopy(File in, File out) throws IOException {BufferedReader br = new BufferedReader(new FileReader(in));BufferedWriter bw = new BufferedWriter(new FileWriter(out));String line = null;while ((line = br.readLine()) != null) {bw.write(line);bw.newLine();bw.flush();}// 释放资源bw.close();br.close();}// 数据准备public static void dataReady() throws IOException {StringBuilder sb = new StringBuilder();for (int i = 0; i < 600000; i++) {sb.append("abcdefghijklmnopqrstuvwxyz");}OutputStream os = new FileOutputStream(new File("C:/Mu/data.txt"));os.write(sb.toString().getBytes());os.close();System.out.println("完毕");}
}
运行结果:
普通字符流1耗时:1337 ms,文件大小:15234 kb
普通字符流2耗时:82 ms,文件大小:15235 kb
缓冲字符流耗时:205 ms,文件大小:15234 kb
测试多次,结果差不多,可见字符缓冲流效率上并没有明显提高,我们更多的是要使用它的readLine()和newLine()方法。
7、重新认识System.out和Scanner
7.1 PrintStream类
我们每天都在用的System.out对象是PrintStream类型的。它也是IO流对象。
PrintStream
为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream
永远不会抛出 IOException
;另外,PrintStream
可以设置自动刷新。
- PrintStream(File file) :创建具有指定文件且不带自动行刷新的新打印流。
- PrintStream(File file, String csn):创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
- PrintStream(OutputStream out) :创建新的打印流。
- PrintStream(OutputStream out, boolean autoFlush):创建新的打印流。 autoFlush如果为 true,则每当写入 byte 数组、调用其中一个 println 方法或写入换行符或字节 (‘\\n’) 时都会刷新输出缓冲区。
- PrintStream(OutputStream out, boolean autoFlush, String encoding) :创建新的打印流。
- PrintStream(String fileName):创建具有指定文件名称且不带自动行刷新的新打印流。
- PrintStream(String fileName, String csn) :创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
package com.atguigu.systemio;import java.io.FileNotFoundException;
import java.io.PrintStream;public class TestPrintStream {public static void main(String[] args) throws FileNotFoundException {PrintStream ps = new PrintStream("io.txt");ps.println("hello");ps.println(1);ps.println(1.5);ps.close();}
}
72 Scanner类
构造方法
- Scanner(File source) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
- Scanner(File source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定文件扫描的。
- Scanner(InputStream source) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
- Scanner(InputStream source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的。
常用方法:
- boolean hasNextXxx(): 如果通过使用nextXxx()方法,此扫描器输入信息中的下一个标记可以解释为默认基数中的一个 Xxx 值,则返回 true。
- Xxx nextXxx(): 将输入信息的下一个标记扫描为一个Xxx
package com.atguigu.systemio;import org.junit.Test;import java.io.*;
import java.util.Scanner;public class TestScanner {@Testpublic void test01() throws IOException {Scanner input = new Scanner(System.in);PrintStream ps = new PrintStream("1.txt");while(true){System.out.print("请输入一个单词:");String str = input.nextLine();if("stop".equals(str)){break;}ps.println(str);}input.close();ps.close();}@Testpublic void test2() throws IOException {Scanner input = new Scanner(new FileInputStream("1.txt"));while(input.hasNextLine()){String str = input.nextLine();System.out.println(str);}input.close();}
}
7.3 System类的三个IO流对象
System类中有三个常量对象:
- System.out
- System.in
- System.err
查看System类中这三个常量对象的声明:
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
奇怪的是,
- 这三个常量对象有final声明,但是却初始化为null。final声明的常量一旦赋值就不能修改,那么null不会空指针异常吗?
- 这三个常量对象为什么要小写?final声明的常量按照命名规范不是应该大写吗?
- 这三个常量的对象有set方法?final声明的常量不是不能修改值吗?set方法是如何修改它们的值的?
final声明的常量,表示在Java的语法体系中它们的值是不能修改的,而这三个常量对象的值是由C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有set方法。
public static void setOut(PrintStream out) {checkIO();setOut0(out);}public static void setErr(PrintStream err) {checkIO();setErr0(err);}public static void setIn(InputStream in) {checkIO();setIn0(in);}private static void checkIO() {SecurityManager sm = getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("setIO"));}}private static native void setIn0(InputStream in);private static native void setOut0(PrintStream out);private static native void setErr0(PrintStream err);