day09【字节流、字符流】
主要内容
教学目标
第一章 IO概述
1.1 什么是IO
目标
File类它只能操作文件或文件夹,并不能操作文件中的数据。而要操作文件中的数据,则必须使用Java中提供的IO流技术完成。
IO:它们的功能是用于操作文件中的数据的。
我们把这种数据读写的传输,看做是一种数据的流动,按照流动的方向,以内存为基准,分为
根据数据的类型又将流分为:
字节流 :以字节为单位,读写数据的流。
字符流 :以字符为单位,读写数据的流。
小结
在流中的顶层父类中,规定了输入流或输出流的基本操作行为。流的顶层父类:
第二章 字节流
一切皆为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制的形式保存,也就是以字节形式保存的,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制(字节)数据。
2.1 字节输出流【OutputStream】
目标
能够明确字节输出流的作用和常用方法的具体作用。
java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
成员方法:
在IO包下的大部分方法都存在异常,代码演示时一般为了方便程序阅读,都会直接声明异常不捕获。
2.2 FileOutputStream类
目标
构造方法
步骤
实现
public class Demo {
/*
void write(int b) 将指定的字节写入此文件输出流。
注意:
因为输出的是字节数据,而1个字节只能保存8个二进制数据
如果输出的数字过大,最终只会将当前数字的最低的8个二进制保写到文件中
*/
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
FileOutputStream fos = new FileOutputStream("e:\\demo\\1.txt");
fos.write(97);
// 超出一个字节,则只保存最低的8个二进制数位
fos.write(353);
// 关闭流
fos.close();
}
}
写出字节数组:write(byte[] b),每次可以写出数组中的数据,代码使用演示:
public class Demo {
/*
void write(byte[] b) 将 b.length字节从指定的字节数组写入此文件输出流。
将字节数组中的数据,写到指定的文件中
*/
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
FileOutputStream fos = new FileOutputStream("e:\\demo\\1.txt");
// 创建字节数组
byte[] buff = {97,98,99,100};
// 将数组中的所有数据写到文件中
fos.write(buff);
// 将字符串数据转为字节数组,写到指定文件中
fos.write( "明天又可以休息了,欧耶".getBytes() );
// 关闭流
fos.close();
}
}
写出指定长度字节数组:write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节,代码使用演示:
public class Demo {
/*
write(byte[] b, int off, int len)
byte[] b:需要写出数据的数据
int off:需要写出数据的起始下标
int len:需要写出的数据的个数
*/
public static void main(String[] args) throws IOException {
// 创建字节输出流对象
FileOutputStream fos = new FileOutputStream("e:\\demo\\1.txt");
// 创建字节数组
byte[] buff = {97,98,99,100};
// 将数组中的所有数据写到文件中
fos.write(buff,1,3);
// 关闭流
fos.close();
}
}
小结
FileOutputStream类是用于给指定文件中写字节数据的类。构造方法中指定需要操作的文件对象或文件路径。如果当前文件不存在则创建当前文件,如果当前文件存在则覆盖原来文件中的内容。因为是字节输出流,所以只能写出一个字节的数据,如果超过一个字节则只能写出最低的8个二进制数据。
2.2.1 数据追加续,写出换行
目标
经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢? 可以使用构造方法中重载的方法:
步骤
实现
public class Demo {
/*
FileOutputStream(File file, boolean append) 创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(String name, boolean append) 创建文件输出流以指定的名称写入文件。
boolean append: 如果结果为true,则给当前文件中追加内容,不会删除文件中原来的数据
默认值为false。
Windows系统中的换行符号为: \r\n
*/
public static void main(String[] args) throws IOException {
// 创建字节输出流对象 , 并将append参数置位true。
FileOutputStream fos = new FileOutputStream("e:\\demo\\1.txt", true );
// 创建字节数组
byte[] buff = {97,98,99,100,101,102};
// 将数组中的所有数据写到文件中,没写一个换一行
for( byte bu : buff ) {
fos.write(bu);
fos.write("\r\n".getBytes());
}
fos.write("床前明月光\r\n疑是地上霜\r\n举头望明月\r\n低头思故乡\r\n".getBytes());
// 关闭流
fos.close();
}
}
小结
字节输出流的构造方法中有一个boolean append参数,只要将值置位true,即可完成数据的追加。而使用write("\r\n")即可完成换行输出。
2.3 字节输入流【InputStream】
目标
小贴士:
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
2.4 FileInputStream类
目标
构造方法
步骤
实现
读取字节数据
读取字节:read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1,代码使用演示:
public class Demo {
/*
int read() 从该输入流读取一个字节的数据。
每次读取一个字节数据,提升为int类型,读取到文件末位返回 -1
*/
public static void main(String[] args) throws IOException {
// 使创建字节输入流对象
FileInputStream fis = new FileInputStream(“e:\demo\1.txt”);
int read1 = fis.read();
System.out.println((char)read1);
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());
System.out.println((char)fis.read());// 一个回车相当于两个字节
// 关闭流对象
fis.close();
}
}
一次读取一个字节模板代码演示:
public class Demo {
public static void main(String[] args) throws IOException {
// 使创建字节输入流对象
FileInputStream fis = new FileInputStream(“e:\demo\1.txt”);
/*
一次读取了两个字节数据
// 使用循环读取的方式
while( fis.read() != -1 ) {
System.out.println((char)fis.read());
}*/
// 定义变量,用来记录每次读取的字节数据
int ch = 0;
while( ( ch = fis.read() ) != -1 ) {
System.out.println( (char)ch );
}
// 关闭流对象
fis.close();
}
}
小贴士:
虽然读取了一个字节,但是会自动提升为int类型。
流操作完毕后,必须释放系统资源,调用close方法,千万记得。
使用字节数组一次读取多个字节:read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1 ,代码使用演示:
public class Demo {
/*
int read(byte[] b) 从该输入流读取最多 b.length个字节的数据到一个字节数组。
将读取到的字节数据保存到数组b中,并返回当前保存的具体有效字节个数
byte[] b:读取到的字节数据保存到此数组。 建议开辟的空间为1024的倍数
返回值int: 记录数组中真实读取到的字节个数
*/
public static void main(String[] args) throws IOException {
// 使创建字节输入流对象
FileInputStream fis = new FileInputStream(“e:\demo\1.txt”);
// 定义数组,用来保存读取到的字节个数
byte[] buff = new byte[1024];
// 定义变量,用来记录数组中真实读取到的字节个数
int len = 0;
while( (len = fis.read( buff ) ) != -1 ) {
/*
len = fis.read( buff ):将从文件中读取的有效字节个数赋值给len空间
new String(buff , 0 ,len):
buff :将buff数组中的数据转为字符串数据
0 :从buff数组中的0下标开始转换
len:len记录的是真实读取到的字节个数,只需要转换到len个即可。后续的都是默认数据
*/
System.out.println(new String(buff,0,len));
}
// 关闭流对象
fis.close();
}
}
小结
FileInputStream类主要是用于读取文件中数据的类。读取文件有模板代码。需要掌握两种读取的模板代码。
小贴士:
使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用。
2.5 字节流练习:图片复制
目标
步骤
复制原理图解
实现
复制图片文件,代码使用演示:
public class Demo {
/*
文件复制: 从硬盘上将指定的文件读取到程序中,然后将读取到的数据在写到指定的文件中
1、创建输入流对象,读取需要复制的文件
2、创建输出流对象,将读取到的数据写到指定的文件
*/
public static void main(String[] args) throws IOException {
// 使创建字节输入流对象 用来读取需要复制的文件
FileInputStream fis = new FileInputStream("e:\\demo\\1.jpg");
// 创建输出流对象,用来将读取到的数据写到指定的文件中
FileOutputStream fos = new FileOutputStream("e:\\demo\\11.jpg");
// 定义数组,用来保存读取到的字节个数
byte[] buff = new byte[1024];
// 定义变量,用来记录数组中真实读取到的字节个数
int len = 0;
while( (len = fis.read( buff ) ) != -1 ) {
// 将读取到的数据,写到指定文件中
fos.write( buff, 0 ,len );
}
// 关闭流对象
fis.close();
fos.close();
}
}
小结
文件的复制其实就是将一个文件读取到程序中,然后在写入到另外一个文件的过程。
小贴士:
流的关闭原则:先开后关,后开先关。
第三章 字符流
3.1 为什么使用字符流
目标
当使用字节流读取文本文件时,一次是读取一个字节数据。而在计算机中有时多个字节组合在一起会表示一个字符数据。所以在使用字节流读取文本数据时,如果读取到中文字符,可能不会显示完整的字符,那是因为一个中文字符需要多个字节存储。因此我们不应该一个一个字节的读取,而是应该把表示汉字的几个字节一起读取,然后把这些字节合并在一起表示一个汉字。
步骤
实现
public class Demo {
/*
字节流读取中文字符:在UTF-8编码表中,一个中文字符需要3个字节组合。
*/
public static void main(String[] args) throws IOException {
// 创建字节输入流对象,并指定文件路径。
FileInputStream fis = new FileInputStream("e:\\demo\\a.txt");
// 模板代码
int ch = 0;
while( (ch = fis.read()) != -1 ) {
// 将读取到的字节数据转换为字符数据。
System.out.println((char)ch);
}
// 关闭流对象
fis.close();
}
}
小结
我们使用字节流读取字符数据,发现读取到的是每个字符对应字节数据,而不是真正的字符内容。而我们更希望读取到的是具体字符数据。这样我们就需要把读取到的某几个字节合并在一起,拼成一个汉字。但是在文件中,有时一个字节就表示一个字符数据,比如英文字母。有时是多个字节表示一个汉字。那么到底是应该把一个字节转成字母,还是把多个字节转成汉字就无法确定了。
为了解决上述问题,Java中为我们提供了字符流,在它的底层会根据读取到的字节数据的特点,基于对应的编码表帮助我们把字节转成字符,最后我们就可以得到想要的字符数据。
3.2 字符流底层原理
目标
因为计算机中,是以二进制的形式保存文件中的数据的。所以当使用字符流去读写文件中的数据时,其实底层还是要以字节形式进行读写的操作。
小结
3.3 字符输入流【Reader】
目标
成员方法
3.4 FileReader类
目标
小贴士:字节缓冲区:一个字节数组,用来临时存储字节数据。
构造方法
步骤
实现
读取字符:read方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取,代码使用演示:
public class Demo {
/*
字符流读取文件的格式和字节流的格式是一样的。
int read() 读一个字符:将字符转换为对应的int类型
读取到文件最后,返回-1
*/
public static void main(String[] args) throws IOException {
// 创建字符输入流对象
FileReader fr = new FileReader("e:\\demo\\1.txt");
// 定义变量,记录读取到的字符数据
int ch = 0;
// 循环读取数据
while( ( ch = fr.read() ) != -1 ) {
System.out.println( (char)ch );
}
// 关闭流
fr.close();
}
}
使用字符数组读取:read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1 ,代码使用演示:
public class Demo {
/*
字符流读取文件的格式和字节流的格式是一样的。
int read(char[] cbuf) 将字符读入数组。 读取到文件最后,返回-1
返回值:记录读取到的有效字符个数
*/
public static void main(String[] args) throws IOException {
// 创建字符输入流对象
FileReader fr = new FileReader("e:\\demo\\1.txt");
// 定义char数组,保存读取到的字符数据
char[] buff = new char[1024];
// 定义变量,记录读取到的字符数据
int len = 0;
// 循环读取数据
while( ( len = fr.read(buff)) != -1 ) {
/*
len = fr.read( buff ):将从文件中读取的有效字节个数赋值给len空间
new String(buff , 0 ,len):
buff :将buff数组中的数据转为字符串数据
0 :从buff数组中的0下标开始转换
len:len记录的是真实读取到的字节个数,只需要转换到len个即可。后续的都是默认数据
*/
System.out.println(new String(buff,0,len));
}
// 关闭流
fr.close();
}
}
小结
FileReader类是用于读取文件中字符数据的类。有读取的模板代码,和字节输入流的操作方式基本一致,不同的此类操作的是字符数据,所以使用的数组为char[]数组。
3.5 字符输出流【Writer】
目标
成员方法:
3.6 FileWriter类
目标
在使用字符输出流的时候,底层都会先把当前的字符数据通过编码表查到对应的编码值,然后把这些编码值转成字节数据,把这些字节数据存储到自己的缓冲区中。然后通过字节流写到指定的文件中。
如果不刷新,程序运行完之后,内存中的数据就会消失。所以需要在内存释放之前刷新数据,close方法底层会自动刷新。
注意:使用字符输出流写出数据的时候一定要刷新或者关流。当数据输出的比较多的时候,一定要刷新。
构造方法:
步骤
实现
public class Demo {
/*
在使用字符输出流的时候,底层都会先把当前的字符数据通过编码表查到对应的编码值,
然后把这些编码值转成字节数据,把这些字节数据存储到自己的缓冲区中。
所以使用字符输出流写出数据的时候一定要刷新或者关流。当数据输出的比较多的时候,一定要刷新
void write(char[] cbuf) 写入一个字符数组。
abstract void write(char[] cbuf, int off, int len) 写入字符数组的一部分。
void write(int c) 写一个字符
void write(String str) 写一个字符串
void write(String str, int off, int len) 写一个字符串的一部分。
*/
public static void main(String[] args) throws IOException {
method03();
}
/*
void write(char[] cbuf) 写入一个字符数组。
abstract void write(char[] cbuf, int off, int len) 写入字符数组的一部分。
int off: 起始的下标
int len: 写入的个数
*/
private static void method03() throws IOException {
// 创建字符输出流对象
FileWriter fw = new FileWriter("e:\\demo\\1.txt");
// 定义字符数组
char[] ch = "我要敲代码".toCharArray();
fw.write(ch);
// 关闭流
fw.close();
}
/*
void write(String str) 写一个字符串
void write(String str, int off, int len) 写一个字符串的一部分。
int off: 起始的下标
int len: 写入的个数
*/
private static void method02() throws IOException {
// 创建字符输出流对象
FileWriter fw = new FileWriter("e:\\demo\\1.txt");
fw.write("明天休息....");
fw.write("但是后天要上课...",0,9);
// 关闭流
fw.close();
}
/*
void write(int c) 写一个字符:将指定int编码值对应的字符写入到文件中
*/
private static void method01() throws IOException {
// 创建字符输出流对象
FileWriter fw = new FileWriter("e:\\demo\\1.txt");
for( int i = 23456; i < 40000; i++ ) {
fw.write(i);
fw.flush();
}
// 关闭流
fw.close();
}
}
小结
FileWriter类是用于写出字符数据的类。写出因为底层有使用缓冲区,所以写出数据之后要刷新或关流,才能够将缓冲区中的数据写到文件中。
字符流,只能操作文本文件,不能操作图片,视频等非文本文件。
第四章 IO异常的处理
IO中的方法,大部分都存在编译时期的异常。而对异常的处理在某些场景下是不能声明的,因此只能捕获处理。而IO中的异常在捕获处理时有固定的模板代码。
4.1 JDK7前处理
目标
步骤
实现
public class Demo {
/*
异常处理的模板代码
*/
public static void main(String[] args) {
// 声明变量
FileWriter fw = null;
try {
//创建字符输出流对象
fw = new FileWriter("e:\\demo\\1.txt");
// 谢谢胡数据
fw.write("异常处理的模板代码!");
fw.flush();
} catch (IOException e) {
e.printStackTrace();
//关闭输入流的代码,不管程序有没有问题,最后都需要关闭,因此需要放在finally代码块中
} finally {
try {
//因为fw是个引用,如果引用为空,就会报空指针异常,如果fw不是空就需要关闭流
if( fw != null ) {
// 关闭流
fw.close();
}
} catch (IOException e ){
e.printStackTrace();
}
}
}
}
小结
对于IO的异常处理比较繁琐,但是幸运的是IO的异常处理有模板代码。通过代码的实现步骤多操作几次,熟练之后任何的IO代码在处理异常时都能够得心应手。
4.2 JDK7的处理
目标
因为IO的异常操作比较繁琐,在JDK7时对异常的处理进行了优化。通过try-with-resource 语句可以确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
格式:
try ( 创建流对象语句,如果多个,使用';'隔开 ) {
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
步骤
实现
public class Demo {
/*
try (创建流对象语句,如果多个,使用';'隔开) {
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
JDK7之后可以在try后面加一个(),而在()中书写创建流对象语句。
当前()就属于try语句的一部分,try中的代码执行完毕,会自动将
流关闭,而不用专门书写finally代码块
*/
public static void main(String[] args) {
// 创建字符输入流对象
try(FileReader fr = new FileReader("e:\\demo\\1.txt");
// 创建字符输出流对象
FileWriter fw = new FileWriter("e:\\demo\\1.txt");){
// 写出数据
fw.write("大家好!!");
// 刷新
fw.flush();
// 读取数据
int ch = 0;
while( (ch = fr.read() ) != -1 ) {
System.out.println((char)ch);
}
}catch (IOException e){
e.printStackTrace();
}
}
}
小结
try-with-resource语句优化的IO异常的处理,不需要手动调用方法释放资源。
4.3 JDK9的改进(了解内容)
目标
改进后格式:
// 流对象
输出流 sc = new 输出流("");
输入流 sr = new 输人流("");
// 引入方式:直接引入
try (sc; sr) {
// 使用对象
}
步骤
实现
public class Demo {
/*
// 创建对象
输出流 sc = new 输出流("");
输入流 sr = new 输人流("");
// 引入方式:直接引入
try (sc; sr) {
使用对象
}
*/
public static void main(String[] args) throws IOException {
// 创建字符输入流对象
FileReader fr = new FileReader("e:\\demo\\1.txt");
// 创建字符输出流对象
FileWriter fw = new FileWriter("e:\\demo\\1.txt");
try( fr;fw ){
// 写出数据
fw.write("大家好!!");
// 刷新
fw.flush();
// 读取数据
int ch = 0;
while( (ch = fr.read() ) != -1 ) {
System.out.println((char)ch);
}
} catch (IOException e ) {
System.out.println(e);
}
}
}
小结
JDK9中try-with-resource 的改进,对于引入对象的方式,支持的更加简洁。被引入的对象,同样可以自动关闭,无需手动close,我们来了解一下格式。
第五章 属性集
5.1 Properties类 概述
目标
在使用Map集合保存数据时,数据是被临时保存在内存中的,而当程序运行结束之后。集合本身和其中的数据都会被从内存中清除。而如果希望Map集合中的数据可以永久保存,可以使用Properties属性集完成。
构造方法
特有成员方法
步骤
实现
public class Demo {
/*
Properties() 创建一个没有默认值的空属性列表。 创建集合对象
- public Object setProperty(String key, String value) : 保存一对属性。
它其实对应的就是Map集合体系中的put()方法,只是key和value都要求必须是字符串数据
- public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。
它其实就是Map集合体系中的get()方法,根据key获取指定的value
- public Set stringPropertyNames() :所有键的名称的集合。
它其实就是Map集合体系中的keySet()方法,将所有的key值获取并保存到一个Set集合中
*/
public static void main(String[] args) {
// 创建属性集对象
Properties prop = new Properties();
// 给属性集中保存数据
prop.setProperty("孙悟空","神话人物");
prop.setProperty("黄继光","民族英雄");
prop.setProperty("曹操","大奸雄");
prop.setProperty("秦桧","大汉奸");
// 获取属性集中的所有key值
Set strs = prop.stringPropertyNames();
// 遍历属性集中的数据
for( String key :strs ) {
// 通过key值获取value值
String value = prop.getProperty(key);
System.out.println( key + "..." + value );
}
}
}
小结
Properties属性集本质就是一个Map集合,主要用于保存String类型的数据,并通过流将保存到属性集中的数据写到文件中,获取从文件中读取被保存的String类型的数据。集合本身的操作方式和Map集合一致。
5.2 与流相关的方法
目标
和流相关的方法:
注意:参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了。
步骤
实现
public class Demo {
public static void main(String[] args) throws IOException {
method02();
}
/*
void load(InputStream inStream) 从输入字节流读取属性列表(键和元素对)。
使用字节输入流,从持久设备上读取数据到属性集中
void load(Reader reader) 以简单的线性格式从输入字符流读取属性列表(关键字和元素对)。
使用字符输入流,从持久设备上读取数据到属性集中 JDK1.6特性
*/
private static void method02() throws IOException {
// 创建属性集对象
Properties prop = new Properties();
// 从持久设备上读取数据到属性集中
prop.load(new FileInputStream("e:\\demo\\1.txt"));
// 遍历集合
Set strs = prop.stringPropertyNames();
for( String key : strs ) {
// 通过key值获取value值
String value = prop.getProperty(key);
System.out.println( key + ".." + value );
}
}
/*
void store(OutputStream out, String comments) 。
使用字节输出流将属性集中的数据写到持久设备上
void store(Writer writer, String comments)
使用字符输出流将属性集中的数据写到持久设备上 JDK1.6特性
String comments : 注释内容,可以直接写""字符串
*/
private static void method01() throws IOException {
// 创建属性集对象
Properties prop = new Properties();
// 给属性集中保存数据
prop.setProperty("孙悟空","神话人物");
prop.setProperty("黄继光","民族英雄");
prop.setProperty("曹操","大奸雄");
prop.setProperty("秦桧","大汉奸");
// 使用store方法,将属性集合中的数据写到持久设备上
prop.store(new FileOutputStream("e:\\demo\\1.txt"),"");
}
}
小结
属性集中提供了和流相关的方法,可以将属性集中的数据写到文件中,然后再次从文件中读取到属性集中。
小贴士:文本中的数据,必须是键值对形式,可以使用空格、等号、冒号等符号分隔。