IO流
1 IO流的概述和分类
1.1学习IO流的目的?
1,将数据写到文件中,实现数据永久化存储
2,读取文件中已经存在的数据
1.2 IO流概述
其中:I表示intput,是数据从硬盘进内存的过程,称之为读。
O表示output,是数据从内存到硬盘的过程。称之为写。
1.3 思考一个问题?
在数据传输的过程中,是谁在读?是谁在写?这个参照物是谁?
IO的数据传输,可以看做是一种数据的流动,按照流动的方向,以内存为参照物,进行读写操作。
简单来说:内存在读,内存在写。
1.4 IO流的分类
1.按流向分—输入流 输出流
2.按数据类型分–字节流 字符流
字节流 操作所有类型的文件 (包括音频视频图片等)
字符流 只能操作纯文本文件 (包括java文件,txt文件等)
一般来说,IO流的分类是按照数据类型来分的
1.5 IO流的技术选型
那我们在用流读取内容的时候,应该如何选择 流的格式呢?
那我们不得不提到一个知识
什么是纯文本文件?
用windows记事本打开能读的懂,那么这样的文件就是纯文本文件。
思考:office文件可以用字符流操作吗? no! 因为不是纯文本文件
思考:下面这些文件分别可以用什么流操作? 字符 字节 字节 字节
1.6 总结:IO流具体分类和使用场景
-
按照数据的流向
- 输入流:读数据
- 输出流:写数据
-
按照数据类型来分
- 字节流
- 字节输入流
- 字节输出流
- 字符流
- 字符输入流
- 字符输出流
- 字节流
-
IO流的使用场景
- 如果操作的是纯文本文件(可以用记事本打开),优先使用字符流
- 如果操作的是图片、视频、音频等二进制文件,优先使用字节流
- 如果不确定文件类型,优先使用字节流.字节流是万能的流
2.字节流
操作所有类型的文件 包括音频视频图片等
2.1 字节流的创建
字节流抽象基类
- InputStream:这个抽象类是表示字节输入流的所有类的超类。
- OutputStream:这个抽象类是表示字节输出流的所有类的超类。
- 子类名特点:子类名称都是以其父类名作为子类名的后缀。
字节输入/出流
- FileInputStream(String name):创建文件输入流以指定的名称读取文件。
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件。
案例引入:案例 往images文件夹中的a.txt文件中写入数据
2.2 字节流写数据步骤
1.创建字节输出流对象
注意事项:
如果文件不存在,就创建。
2.写数据
注意事项:
写出的整数,实际上写到文件中,是在ASCII码表中那个字符。
3.释放资源
注意事项:
每次使用完流必须要释放资源。
注意点:
- 1.如果文件不存在,会帮我们创建2.如果文件存在,会把文件覆盖。
- 传递一个整数,实际上写到文件中,是在ASCII码表中那个字符。
- 每次使用完流必须要释放资源。
代码展示:
FileOutputStream fos = new FileOutputStream("E:/file/image/abc.txt");
fos.write(97);//写的是 char中的类型
fos.close();
FileInputStream fis = new FileInputStream("E:/file/image/a.txt");
int read = fis.read();
System.out.println(read);
fos.close();
2.3 字节流写数据的3种方式
方法名 | 说明 |
---|---|
void write(int b) | 一次写一个字节数据 |
void write(byte[] b) | 一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 一次写一个字节数组的部分数据 |
不需要记住 百度直接搜索ASCALL码表即可
代码演示
// fos.write(97);//写的是 char中的类型
// fos.write(98);
// fos.write(99);
// fos.write(100);
// fos.write(101);//abcde
//字节数组
byte[]bytes = {97,98,99,100,101};
// fos.write(bytes);//abcde
//void write(byte[] b, int off, int len):将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流
fos.write(bytes,2,2);//off 可以认为索引 从0开始 截取长度为len的内容输出到文件中
fos.close();
2.4 字节流写数据的两个小问题
字节流写数据如何实现换行呢?
写完数据后,加换行符
windows:\r\n 单写\r \n 也可以
linux:\n
mac:\r
字节流写数据如何实现追加写入呢?
public FileOutputStream(String name,boolean append)
创建文件输出流以指定的名称写入文件。如果第二个参数为true ,不会清空文件里面的内容
FileOutputStream fos = new FileOutputStream("E:/file/image/abc.txt",true);
fos.write(97);//写的是 char中的类型
fos.write("\r\n".getBytes());//换行
fos.write(98);
fos.write("\r\n".getBytes());//换行
fos.write(99);
fos.write("\r\n".getBytes());//换行
fos.write(100);
fos.write("\r\n".getBytes());//换行
fos.write(101);//abcde
fos.write("\r\n".getBytes());//换行
fos.close();
2.5 字节流写数据加try…catch异常处理
思考:那么我们如何操作才能让close方法一定执行呢?
finally:在异常处理时提供finally块来执行所有清除操作。比如说IO流中的释放资源
特点:被finally控制的语句一定会执行,除非JVM退出
异常处理标准格式:try….catch…finally
代码展示:
FileOutputStream fos = null;
try {
fos = new FileOutputStream("E:/file/image/abc.txt",true);
fos.write(97);//写的是 char中的类型
fos.write("\r\n".getBytes());//换行
fos.write(98);
fos.write("\r\n".getBytes());//换行
fos.write(99);
fos.write("\r\n".getBytes());//换行
fos.write(100);
fos.write("\r\n".getBytes());//换行
fos.write(101);//abcde
fos.write("\r\n".getBytes());//换行
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2.5 小结:字节流读数据 一次读一个字节
步骤:
1. 创建字节输出流对象
文件不存在,就创建。
文件存在就清空。如果不想被清空则加true
2. 写数据
可以写一个字节,写一个字节数组,写一个字节数组的一部分
写一个回车换行:\r\n
3. 释放资源
注意事项:
如果文件不存在,就直接报错。
注意事项:
读出来的是文件中数据的码表值。 a 97
注意事项:
每次使用完流必须要释放资源。
2.6 案例 往images文件夹中的a.txt文件中写入数据
往images文件夹中的a.txt文件中写入数据(只能写英文,中文乱码) I love you HuiHui
- 创建一个文件对象
- 判断一下这个文件存在否,不存在创建
- 创建一个输出流
- 创建数据,写数据
- 关闭输出流
代码:
public static void main(String[] args) {
FileOutputStream fos = null;
try {
File file = new File("E:/file/image/a.txt");
if (file != null && file.length() > 0) {
fos = new FileOutputStream(file);
String s1 = " I love you HuiHui";
byte[] bytes = s1.getBytes();
fos.write(bytes);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.8 字节流读取数据
2.9 字节流读取数据步骤
案例引入:读取images文件夹中的a.txt文件中数据
如何读取呢?
1.创建字节输入流对象。
注意事项:
如果文件存在则读取。
2.读取数据
注意事项:
读取的是字节的ASCII码需要转换为char类型
读取中文会乱码。
3.释放资源
注意事项:
每次使用完流必须要释放资源。
2.10 字节流读取数据的2种方式
方法名 | 说明 |
---|---|
int read() | 一次读取一个字节数据 |
int read(byte[] b) | 一次读一个字节组数据,把数据封装到参数b中,返回值为本次读取到的字节个数 |
读取到最后,返回值为-1
注意:read()方法在一次使用里面最好只调用一次
示例代码:
public static void main(String[] args) {
FileInputStream fis = null;
byte[]bytes = {97,98,99};
try {
fis = new FileInputStream("E:/file/image/a.txt");
// 读一个字节
//int read = fis.read();
//System.out.println(read);
//向下继续读取字节 读取多个
//int read1 = fis.read();
// System.out.println(read1);
// 循环读取
int b;
//fis.read() 读到结果返回,读不到就返回一个-1.
// b = 正常值 如果为-1 -1占了两个字节
while ((b = fis.read()) != -1) {//fis.read(bytes,0,4)
System.out.println((char)b);
}
// System.out.println(fis.read(bytes));//3 可以设置字节数组 每次读取字节数组的字节个数
// System.out.println(fis.read(bytes));//3
// System.out.println(fis.read(bytes));//3
// while ((b=fis.read(bytes))!=-1){
// System.out.println((char) b);//乱码
// }
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
英文和数字占两个字节
2.11 案例 读取images文件夹中的a.txt文件中数据
**需求:**读取images文件夹中的a.txt文件中的数据(只能写英文,中文乱码)
步骤:
- 创建一个文件对象
- 判断一下这个文件存在否
- 创建一个输入流
- 读取数据
- 关闭输入流
代码展示:
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("E:/file/image/a.txt");
int b ;
while ((b = fis.read() )!= -1) {
System.out.println((char) b);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.12 案例:复制文件
需求:把“E:\images\a.png”复制到当前模块下
分析:
复制文件,其实就把文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)
数据源:
E:\images\a.png ---- 读数据 — FileInputStream
目的地:
模块名称\copy.png — 写数据 — FileOutputStream
代码实现:
public static void main(String[] args) {
//创建输入流
FileInputStream fis = null;
//创建输出流
FileOutputStream fos = null;
try {
fis = new FileInputStream("E:\\file\\image\\a.txt");
fos = new FileOutputStream("src\\com\\itgaohe\\123.txt");
//读取 本地的 文件
int b;
while ((b = fis.read()) != -1) {
//写出到 当前模块中
fos.write(b);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.13 思考问题
思考:如果操作的文件过大,那么速度会不会有影响?
一个字:慢!!!
问题:在读取的时候 一次只能 一个字节字节的读取;写出的时候也只能一次写一个字节数据的写出。
为了改变这个问题
提高拷贝速度的解决方案
为了解决速度问题,
1.字节流通过创建字节数组,可以一次读写多个数据。
2.一次读一个字节数组的方法:public int read(byte[] b):从输入流读取最多b.length个字节的数据
返回的是读入缓冲区的总字节数,也就是实际的读取字节个数
代码展示:
//读取 本地的 文件
int b = -1;
byte[]bytes = new byte[1024];
while ((b = fis.read(bytes)) != -1) {
//写出到 当前模块中
fos.write(bytes,0,b);
}
2.14 字节流缓冲流
字节缓冲流:
BufferOutputStream:缓冲输出流
BufferedInputStream:缓冲输入流
构造方法:
字节缓冲输出流:BufferedOutputStream(OutputStream out)
字节缓冲输入流:BufferedInputStream(InputStream in)
为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作
private static int DEFAULT_BUFFER_SIZE = 8192; 默认创建长度为8192的字符缓冲数组
完善
代码演示:
public static void main(String[] args) throws IOException {
//ppt doc txt jpg png mp4 都可以读
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:/file/image/IO.pptx"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E:/file/image/IO2.pptx"));
int len = -1;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes,0,len);
}
bis.close();
bos.close();
}
2.15 案例:复制视频
需求:把“E:\itgaohe\a.avi”复制到模块目录下的“b.avi”
思路:
根据数据源创建字节输入流对象
根据目的地创建字节输出流对象
读写数据,复制视频
释放资源
代码展示:
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:/file/image/guangzhou.mp4"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/com/itgaohe/guangzhou2.mp4"));
int len = -1;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes,0,len);
}
bis.close();
bos.close();
}
2.16 小结
方法名 | 说明 |
---|---|
void write(int b) | 一次写一个字节数据 |
void write(byte[] b) | 一次写一个字节数组数据 |
void write(byte[] b, int off, int len) | 一次写一个字节数组的部分数据 |
int read() | 一次读取一个字节数据 |
int read(byte[] b) | 一次读一个字节组数据,把数据封装到参数b中,返回值为本次读取到的字节个数 |
操作所有类型的文件 没有读取到位-1 字节缓冲流:可以提高读写效率 |
3.字符流
3.1 思考:为什么要学习字符流
把文件中的数据读取到内存时,如果此时文件中出现了中文,那么字节流就会出现乱码现象。所以纯文本的文件,我们就需要使用字符流来进行操作。
为什么字节流读取纯文本文件,可能会出现乱码?
其实这个跟 计算机编码规则有关
public static void main(String[] args) throws IOException {
//创建输入流
FileInputStream fis = null;
fis = new FileInputStream("E:\\file\\123.txt");
//读取 本地的 文件
int b = -1;
while ((b = fis.read()) != -1) {
//写出到 当前模块中
// System.out.println(b);//打印12个字节
System.out.println((char) b);// 文件中加入中文 出现中文乱码
}
fis.close();
}
那具体是遵循什么规则呢?
3.2 编码表
基础知识:
计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果
按照某种规则,将字符存储到计算机中,称为编码。
按照同样的规则,将存储在计算机中的二进制数解析显示出来,称为解码 。
编码和解码的方式必须一致,否则会导致乱码。
简单理解:
存储一个字符a,首先需在码表中查到对应的数字是97,然后按照转换成二进制的规则进行存储。
读取的时候,先把二进制解析出来,再转成97,通过97查找到对应的字符是a。
ASCII字符集:
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字,大小写字符和一些常见的标点符号。
注意:ASCII码表中是没有中文的。
GBK:window系统默认的码表。兼容ASCII码表,也包含了21003个汉字,并支持繁体汉字以及部分日韩文字。
注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。
Unicode码表:
由国际组织ISO 制定,是统一的万国码,计算机科学领域里的一项业界标准,容纳世界上大多数国家的所有常见文字和符号。
但是因为表示的字符太多,所以Unicode码表中的数字不是直接以二进制的形式存储到计算机的,会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及 UTF-32的编码方式再存储到计算机,其中最为常见的就是UTF-8。
注意: Unicode是万国码,以UTF-8编码后一个中文以三个字节的形式存储
编码表小结
-
什么是字符集
是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
l计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等
-
常见的字符集
-
ASCII字符集:
lASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
基本的ASCII字符集,使用7位表示一个字符,共128字符。ASCII的扩展字符集使用8位表示一个字符,共256字符,方便支持欧洲常用字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
-
GBXXX字符集:
GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
-
Unicode字符集:
UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码
编码规则:
128个US-ASCII字符,只需一个字节编码
拉丁文等字符,需要二个字节编码
大部分常用字(含中文),使用三个字节编码
其他极少使用的Unicode辅助字符,使用四字节编码
-
3.3 汉字存储和展示过程解析
我们先来了解一下 汉字的存储过程 再去看为什么字节流读取的时候可能会出现乱码
重点:windows默认使用码表为:GBK,一个字符两个字节。
idea和以后工作默认使用Unicode的UTF-8编解码格式,一个中文三个字节。
3.4 字符串中的编码解码问题
那对于汉字/字符串读取的内容 他是如何进行存储的呢?
编码:
byte[] getBytes():使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes(String charsetName):使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中
解码:
String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的 String
String(byte[] bytes, String charsetName):通过指定的字符集解码指定的字节数组来构造新的 String
代码展示:
String s = "山东高合";
byte[] bytes = s.getBytes();//UTF-8默认
System.out.println(Arrays.toString(bytes));
byte[] bytes1 = s.getBytes("UTF-8");默认为UTF-8 一个中文3个字节
System.out.println(Arrays.toString(bytes1));
byte[] bytes2 = s.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));//一个中文2个字节
byte[]bytes3 = {-27, -79, -79, -28, -72, -100, -23, -85, -104, -27, -112, -120};
String s1 = new String(bytes3);
System.out.println(s1);
String s2 = new String(bytes3,"GBK");//灞变笢楂樺悎 乱码 原因:编码和解码格式不一致!
System.out.println(s2);
byte[]bytes4 = {-55, -67, -74, -85, -72, -33, -70, -49};
String s4 = new String(bytes4,"GBK");
System.out.println(s4);
结论:不管是那种流,只要支持中文,以相同的编码格式读取、解析就能得到中文。
//编码和解码 - 码表不一致。乱码。
例如:GBK 读取 GBK 输出
UTF-8读取 UTF-8输出
3.5 为什么字节流读取纯文本文件,可能会出现乱码?
因为字节流一次读一个字节,而不管GBK还是UTF-8一个中文都是多个字节,用字节流每次只能读其中的一部分,所以就会出现乱码问题。
3.6 字符流读取中文的过程
为了解决字节流读取纯文本文件,可能会出现乱码的问题。我们提供字符流来读取中文 我们来看一下他具体是怎么存储的
字符流 = 字节流 + 编码表
基础知识: 不管是在哪张码表中,中文的第一个字节一定是负数。
3.7 字符流写数据步骤:
1.创建字符输出流对象
注意事项:
如果文件不存在,就创建。但是要保证父级路径存在。
如果文件存在就清空。
2.写数据
注意事项:
1,写出int类型的整数,实际写出的是整数在码表上对应的字母。
2,写出字符串数据,是把字符串本身原样写出。
fw.write(97);
3.释放资源
注意事项:
每次使用完流必须要释放资源。
3.8 字符流的创建
接下来我们就用字符流 来解决中文乱码的问题。
1. FileWriter字符输出流
构造器 | 说明 |
---|---|
public FileWriter(File file) | 创建字符输出流管道与源文件对象接通 |
public FileWriter(File file,boolean append) | 创建字符输出流管道与源文件对象接通,可追加数据 |
public FileWriter(String filepath) | 创建字符输出流管道与源文件路径接通 |
public FileWriter(String filepath,boolean append) | 创建字符输出流管道与源文件路径接通,可追加数据 |
2.FileReader字符输入流
构造器 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流管道与源文件对象接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
代码:
FileReader fr = new FileReader("src/abc.txt");
FileWriter fw = new FileWriter("src/cde.txt");
int len ;
while ((len=fr.read())!=-1){
fw.write(len);
}
fw.close();
fr.close();
}
3.9 字符流写数据的5种方式
方法名 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[] cbuf) | 写入一个字符数组 |
void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
3.10 字符流读和关闭流刷新流
方法名 | 说明 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
int read() | 一次读一个字符数据 |
int read(char[] cbuf) | 一次读一个字符数组数据 |
flush() 方法的使用原因:flush本意是冲刷,这个方法大概取自它引申义冲马桶的意思,马桶有个池子,你往里面扔东西,会暂时保存在池子里,只有你放水冲下去,东西才会进入下水道。
专业术语叫缓冲区 当你print或者write的时候,会暂时保存在缓冲区 当你直接调用close()方法关闭流的时候,在流的通道中 还有缓存 没有清理掉,刷新一下,方便下一次的使用。就像刷马桶一样。所以应该在关闭读写流之前先flush()。
代码展示: 用字符流读写文件
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
//读
fr = new FileReader("E:/file/dogdaily.txt");
//写
fw = new FileWriter("src/com/itgaohe/my.txt");
int c = -1;
while ((c = fr.read()) != -1) {
fw.write(c);
}
//如果读取的字符数量很多 则用如下格式
// char[] chars = new char[1024];
// int c = -1;
// while ((c = fr.read(chars)) != -1) {
// fw.write(chars);
// }
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.11 案例:使用字符流保存键盘录入的数据
需求:将用户键盘录入的用户名和密码保存到本地实现永久化存储。
步骤:
用户键盘录入用户名
将用户名和密码写到本地文件中
代码演示:
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
FileWriter fw = null;
try {
fw = new FileWriter("E:/file/userPass.txt");
System.out.println("请输入用户名:");
String username = sc.next();//灰灰
System.out.println("请输入密码:");
String password = sc.next();
fw.write("username:" + username);
fw.write("\r\n");
fw.write("password:" + password);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("存储成功!");
}
}
3.12 字符缓冲流
字符缓冲流:
BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
构造方法:
BufferedWriter(Writer out)
BufferedReader(Reader in)
3.13 字符缓冲流特有功能
BufferedWriter:
void newLine():写一行行分隔符,行分隔符字符串由系统属性定义
BufferedReader:
public String readLine() :读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符,如果流的结尾已经到达,则为null
public static void main(String[] args) throws IOException {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader("E:/file/userPass.txt"));
bw = new BufferedWriter(new FileWriter("src/com/itgaohe/123.txt"));
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
br.close();
bw.close();
}
}
3.14 案例:读取文件中的数据排序后再次写到本地
需求:读取文件中的数据,排序后再次写到本地文件
步骤:
读取数据
将数据排序
写回本地
代码展示:
userPass
username:huihui
password:123
public static void main(String[] args) {
BufferedReader br = null;
BufferedWriter bw = null;
ArrayList<String> list = new ArrayList<>();
try {
br = new BufferedReader(new FileReader("E:/file/userPass.txt"));
bw = new BufferedWriter(new FileWriter("src/com/itgaohe/123.txt"));
String line = null;
while ((line = br.readLine()) != null) {
list.add(line);
}
//倒序排序
Collections.sort(list, (o1, o2) -> o2.hashCode() - o1.hashCode());
for (String s : list) {
bw.write(s);
bw.newLine();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.15 案例:使用字符缓冲流读取user.txt中用户名和密码
需求:读取用户名和密码,判断用户输入数据是否正确
步骤:
用户通过控制台输入数据
读取文件内容,进行数据撕裂获取username, password
比较用户输入数据是否正确
代码展示:
public static void main(String[] args) {
BufferedReader br = null;
ArrayList<String> list = new ArrayList<>();
String username = null;
String password = null;
int time = 3;
Scanner sc = new Scanner(System.in);
try {
br = new BufferedReader(new FileReader("E:/file/userPass.txt"));
String line = null;
while ((line = br.readLine())!=null){
list.add(line);
}
for (String s : list) {
if (s.startsWith("username")){
//说明是账号
String[] split = s.split(":");
username = split[1];
System.out.println(username);
}
if (s.startsWith("password")){
String[] split = s.split(":");
password = split[1];
System.out.println(password);
}
}
//输入值进行比较
while (true){
System.out.println("请输入用户名:");
String username1 = sc.next();
System.out.println("请输入密码:");
String password1 = sc.next();
if (username1.equals(username)&&password1.equals(password)){
System.out.println("登陆成功!!!");
break;
}else {
System.out.println("比对失败!!!请重新输入:你还有" +(--time)+"次机会!");
if (time <=0){
System.out.println("你账号被锁定!");
break;
}
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.16 IO流小结
- 学会字节流完成文件的读取操作!!!
- 学会用缓冲流完成文件读取操作!!!
- 字节流打印中文为什么乱码?字节流复制文件为什么不会乱码?
- 字符串转字节 、 字节转字符串!!!
- 转码的概念。什么情况下会乱码!!!
能力目标
-
把一个照片文件用 IO流(以字节为单位去搬运) 拷贝到项目模块目录下。
-
把一个照片文件用 IO流(以数组为单位去搬运) 拷贝到项目模块目录下。
-
把一个照片文件用 缓冲流 拷贝到项目模块目录下。
-
把一个文件夹目录 拷贝到项目模块目录下。(要求:文件夹及其子文件全部拷贝)
尝试使用缓冲流完成这个copy操作。
4.转换流
4.1 案例引入
用转换流往a.txt数据文件写入GBK编码中文数据,再用转换流读取a.txt,解码为GBK编码
输入流----读取文件
输出流----将内容输入到文件
代码展示:
public static void main(String[] args) throws IOException {
File file1 = new File("E:\\file\\aa\\image\\a.txt");
File file2 = new File("src/com/itgaohe/123.txt");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file2), "GBK");
InputStreamReader isr = new InputStreamReader(new FileInputStream(file1), "GBK");
int len = -1;
while ((len = isr.read()) != -1) {
osw.write(len);
}
osw.close();
isr.close();
}
4.2 查看本地文件的编码格式
那我们如何才能知道本地文件的编码格式是什么样的呢?
4.3 查看idea中的编码格式
在我们查看输入到idea中 如果代码出现乱码的问题 该怎么解决?
查看idea默认的项目编码格式 我们发现 项目指定的编码格式是UTF-8 而我们展出
4.4 转换流模型图
4.5 转换流读写数据
构造方法
IDEA中默认字符编码格式为:UTF-8
方法名 | 说明 |
---|---|
InputStreamReader(InputStream in) | 使用默认字符编码创建InputStreamReader对象 |
InputStreamReader(InputStream in,String chatset) | 使用指定的字符编码创建InputStreamReader对象 |
OutputStreamWriter(OutputStream out) | 使用默认字符编码创建OutputStreamWriter对象 |
OutputStreamWriter(OutputStream out, String charset) | 使用指定的字符编码创建OutputStreamWriter对象 |
代码展示:
public static void main(String[] args) throws IOException {
File file1 = new File("E:\\file\\aa\\image\\a.txt");
File file2 = new File("src/com/itgaohe/123.txt");
// InputStreamReader isr = new InputStreamReader(new FileInputStream(file1));//utf-8
InputStreamReader isr = new InputStreamReader(new FileInputStream(file1), "GBK");
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file2));//UTF-8
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(file2),"GBK");
int len = -1;
while ((len = isr.read()) != -1) {
osw.write(len);
}
osw.close();
isr.close();
}
4.6 转换流的使用场景
在JDK11之前,指定编码读写
JDK11之后,
FileReader(File file, Charset charset):创建一个新的FileReader,给出File读取和创建charset
FileReader(String fileName, Charset charset):创建一个给定文件名称的FileReader,给出File读取和创建charset
【注意】:
charset需要通过Charset.forName(“”)获取
4.7 小结
转换流就是来进行字节流和字符流之间转换的
InputStreamReader是从字节流到字符流的桥梁
它读取字节,并使用指定的编码将其解码为字符。
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
OutputStreamWriter是从字符流到字节流的桥梁
是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节。
它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
5.对象操作流
5.1 案例引入
案例:用对象操作流读写多个对象
需求:创建多个Javabean类对象写到文件中,再次读取到内存。
思路:
创建学生对象
利用对象操作输出流写到本地
利用对象操作输入流读到内存
代码展示:
/**
* read():
* 读取到文件末尾返回值是 -1
* readLine():
* 读取到文件的末尾返回值 null
* readObject():
* 读取到文件的末尾 直接抛出异常java.io.EOFException
* 如果要序列化的对象有多个,不建议直接将多个对象序列化到文件中,因为反序列化时容易出异常
* 建议: 将要序列化的多个对象存储到集合中,然后将集合序列化到文件中
*/
public class ObjectStream2 {
public static void main(String[] args) throws Exception {
// 序列化
//1.创建序列化流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\com\\itgaohe\\123.txt"));
ArrayList<Student> arrayList = new ArrayList<>();
//2.创建多个学生对象
Student s01 = new Student("佟丽娅",30,"女");
Student s02 = new Student("王宝强",30,"男");
//3.将学生对象添加到集合中
arrayList.add(s01);
arrayList.add(s02);
//4.将集合对象序列化到文件中
oos.writeObject(arrayList);
oos.close();
// 反序列化
//5.创建反序列化流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\com\\itgaohe\\123.txt"));
//6.将文件中的对象数据,读取到内存中
Object obj = ois.readObject();
ArrayList<Student> list = (ArrayList<Student>)obj;
ois.close();
for (Student s : list) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
5.2 对象操作流的特点
可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中。
5.3 了解对象操作流
5.4 对象操作流模型图
5.5 对象操作流概述
对象操作流分为两类:对象操作输入流和对象操作输出流
对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象
对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
5.6 对象序列化反序列化概述
什么是对象的序列化和反序列化?
对象序列化介绍
- 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
- 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
- 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
- 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
对象序列化流: ObjectOutputStream
- 将Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象 。
- 只有支持java.io.Serializable接口的对象才能写入流。
- writeObject方法用于将对象写入流。
5.7 对象操作输出流
对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象
构造方法
方法名 | 说明 |
---|---|
ObjectOutputStream(OutputStream out) | 创建一个写入指定的OutputStream的ObjectOutputStream |
序列化对象的方法
方法名 | 说明 |
---|---|
void writeObject(Object obj) | 将指定的对象写入ObjectOutputStream |
注意事项
- 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
- Serializable是一个标记接口,实现该接口,不需要重写任何方法
5.8 对象操作输入流
对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象
构造方法
方法名 | 说明 |
---|---|
ObjectInputStream(InputStream in) | 创建从指定的InputStream读取的ObjectInputStream |
反序列化对象的方法
方法名 | 说明 |
---|---|
Object readObject() | 从ObjectInputStream读取一个对象 |
代码展示:
Student
public class Student implements Serializable {
private String name;
private int age;
private String sex;
//getset 有参无参 toStirng 实现序列化接口
}
Test
public static void main(String[] args) throws IOException, ClassNotFoundException {
//3.创建对象
Student student = new Student("杨金辉",18,"男");
//1.创建对象字节输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src\\com\\itgaohe\\123.txt"));
//2.将对象输出到 文件中 //存储的是对象在txt文件中
oos.writeObject(student);
//1.创建对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src\\com\\itgaohe\\123.txt"));
//2.读取txt文件中的对象
Object object = ois.readObject();
//3.输出对象
System.out.println(object.toString());
ois.close();
oos.close();
}
5.9 对象操作流注意事项
注意:
- 对象流不仅可以读写对象,还可以读写基本数据类型。
- 使用对象流读写对象时,该对象必须序列化与反序列化。
- 系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。
代码展示:
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
oos.writeInt(123);
oos.writeObject("灰灰真帅!");
oos.writeObject(new Date());
oos.writeObject(new Student());//java.io.NotSerializableException
oos.close();
}
5.10 对象操作流问题
问题引入:
用对象序列化流序列化了一个对象后,我们再去修改对象所属的Javabean类,比如添加一个属性,读取数据会不会出问题呢?
会出问题,会抛出InvalidClassException异常
代码展示–先做如下操作:
1.对学生对象进行序列化存储
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
oos.writeObject(new Student("灰灰",18,"nan"));
oos.close();
2.将 序列化存储隐掉,在学生类中添加一个新的字段 同时打开反序列化存储
public class Student implements Serializable {
private String name;
private int age;
private String sex;
private String number;//新加字段
...
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
// oos.writeObject(new Student("灰灰",18,"nan"));//java.io.NotSerializableException
// oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/com/itgaohe/123.txt"));
System.out.println(ois.readObject());
ois.close();
}
3.控制台报错 java.io.InvalidClassException
Exception in thread "main" java.io.InvalidClassException: com.itgaohe.test07.Student; local class incompatible: stream classdesc serialVersionUID = -3749205021108360658, local class serialVersionUID = -1984186709931597183
解决方案:
1.给对象所属的类加一个serialVersionUID
private static final long serialVersionUID = 42L;
2.自动生成
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
6.Properties
6.1 Properties概述:
是一个Map体系的集合类 继承了Hashtable implements Map接口
Properties中有跟IO相关的方法
作用:只存字符串!!!
6.2 Properties方法
Properties作为集合的特有方法:
方法名 | 说明 |
---|---|
Object setProperty(String key, String value) | 设置集合的键和值,都是String类型,底层调用Hashtable方法 put |
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
SetstringPropertyNames() | 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 |
6.3练习: Properties作为Map集合的使用(增删改查)
public static void main(String[] args) {
//增
Properties properties = new Properties();
properties.setProperty("大哥","123");
System.out.println(properties);
//删
p.remove("username");
//改
p.setProperty("username","lisi");
//查-
System.out.println(p.getProperty("username"));
//遍历 -- 获取keys
Set<Object> keySet = p.keySet();
for (Object key : keySet) {
Object value = p.get(key);
System.out.println(key + "---" + value);
}
System.out.println("==========");
//stringPropertyNames() 获取keys string类型
Set<String> names = p.stringPropertyNames();
for (String key : names) {
String property = p.getProperty(key);
System.out.println(key+"--"+property);
}
}
6.4 Properties和IO流结合的方法
方法名 | 说明 |
---|---|
void load(InputStream inStream) | 从输入字节流读取属性列表(键和元素对),+p集合中存入的内容 |
void load(Reader reader) | 从输入字符流读取属性列表(键和元素对) |
void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此 Properties表中,以适合使用load(Reader)方法的格式写入输出字符流 |
代码展示:
a.properties
jdbc.username=root
jdbc.password=1234
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.setProperty("gaoheday01", "张飞");
properties.setProperty("gaoheday02", "张飞2");
properties.setProperty("gaoheday03", "张飞3");
// FileReader fr = new FileReader("src/com/itgaohe/a.properties");
// properties.load(fr);
// properties.store(new FileWriter("src/com/itgaohe/b.properties"),"备注:");
InputStream is = new FileInputStream("src/com/itgaohe/a.properties");
properties.load(is);
properties.store(new FileOutputStream("src/com/itgaohe/b.properties"),"");
Set<String> set = properties.stringPropertyNames();
for (String s : set) {
System.out.println(s+":"+properties.getProperty(s));
}
}
6.5 案例
案例需求
在Properties文件中手动写上用户名密码,读取到集合中,将该数据封装成用户对象,写到本地文件.
- 实现步骤
- 创建Properties集合,将本地文件中的数据加载到集合中.
- 获取集合中的键值对数据,封装到用户对象中.
- 创建序列化流对象,将用户对象序列化到本地文件中.
代码演示:
a.properties
jdbc.username=root
jdbc.password=1234
test
public static void main(String[] args) throws IOException {
//1.创建Properties集合 将本地文件中的数据加载到集合中
Properties prop = new Properties();
//2.创建字符输入流 读取配置文件中的信息
FileReader fr = new FileReader("src/com/itgaohe/a.properties");
//3.用集合对象 加载 配置文件
prop.load(fr);
fr.close();
//4.获取Properties集合中的键值对数据 封装到对象中
String username = prop.getProperty("jdbc.username");
String password = prop.getProperty("jdbc.password");
User user = new User(username, Integer.parseInt(password));
//3.创建序列化输出流 输出到本地中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
oos.writeObject(user);
oos.close();
}
etProperty(“gaoheday03”, “张飞3”);
// FileReader fr = new FileReader(“src/com/itgaohe/a.properties”);
// properties.load(fr);
// properties.store(new FileWriter(“src/com/itgaohe/b.properties”),“备注:”);
InputStream is = new FileInputStream("src/com/itgaohe/a.properties");
properties.load(is);
properties.store(new FileOutputStream("src/com/itgaohe/b.properties"),"");
Set<String> set = properties.stringPropertyNames();
for (String s : set) {
System.out.println(s+":"+properties.getProperty(s));
}
}
## 6.5 案例
**案例需求**
在Properties文件中手动写上用户名密码,读取到集合中,将该数据封装成用户对象,写到本地文件.
- 实现步骤
- 创建Properties集合,将本地文件中的数据加载到集合中.
- 获取集合中的键值对数据,封装到用户对象中.
- 创建序列化流对象,将用户对象序列化到本地文件中.
**代码演示:**
**a.properties**
```java
jdbc.username=root
jdbc.password=1234
test
public static void main(String[] args) throws IOException {
//1.创建Properties集合 将本地文件中的数据加载到集合中
Properties prop = new Properties();
//2.创建字符输入流 读取配置文件中的信息
FileReader fr = new FileReader("src/com/itgaohe/a.properties");
//3.用集合对象 加载 配置文件
prop.load(fr);
fr.close();
//4.获取Properties集合中的键值对数据 封装到对象中
String username = prop.getProperty("jdbc.username");
String password = prop.getProperty("jdbc.password");
User user = new User(username, Integer.parseInt(password));
//3.创建序列化输出流 输出到本地中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/com/itgaohe/123.txt"));
oos.writeObject(user);
oos.close();
}