1、字节流
1.1 IO概述
硬盘:永久存储数据,不停地旋转使得硬盘的磁头可以读取到不同的文件。
内存:临时存储数据。
I/O流:
I:input 输入/读取 从硬盘到内存
O:output 输出/写入 从内存到硬盘
流:数据 分为字符和字节,1字符=2字节,1字节=8二进制位/比特位
字符输入流Reader、字符输出流Writer、字节输入流InputStream、字节输出流OutputStream
1.2 OutputStream类
计算机中所有数据都是以二进制形式存储的,以字节为单位
OutputStream是表示所有字节输出流的父类,是一个抽象类
方法:
void close();关闭此输出流并释放相关资源
void flush();刷新此输出流并强制写出所有缓冲的输出字节
void write(byte[] b);
void write(byte[] b, int off, int len);
abstract void write(int b);
子类:ByteArrayOutputStream FileOutputStream FilterOutputStream
ObjectOutputStream OutputStream PipedOutputStream
FileOutputStream 文件字节输出流 将内存中的数据写入到硬盘的文件中
构造方法:
FileOutputStream(String name);参数是写入文件的路径
FileOutputStream(File file);参数是写入文件
构造方法做了三件事:
(1)创建一个FileOutputStream对象
(2)根据构造方法传递的文件路径/文件,创建一个空文件
(3)将FileOutputStream对象指向创建好的文件
写入数据的原理:
Java程序 -> JVM(Java虚拟机) -> OS -> OS调用写数据的方法 -> 把数据写入文件中
字节输出流的使用步骤:
(1)创建FileOutputStream对象,在构造方法中传递数据写入的目的地
(2)调用write方法,将数据写入到文件中
(3)调用close方法,释放资源(流使用会占用一部分内存,影响效率)
以上三个方法都有异常 抛出IOException
测试类
public class Demo02OutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream os = new FileOutputStream(“day08-code\src\demo01IO\1.txt”);
os.write(97);//写入数据时会转换为二进制的97,以字节为单位存入文件。任意的文本编辑器在打开文件时都会查询编码表,将字节转换为字符显示。0-127查询ASCII码表,其它值查询系统默认码表(中文系统查询GBK码表) 97就是a,所以打开文件显示a
os.close();
FileOutputStream os2 = new FileOutputStream(new File(“day08-code\src\demo01IO\2.txt”));
//要在文件里显示100,需要写入几个字节?3个
os2.write(49);//1
os2.write(48);//0
os2.write(48);//0
os2.close();
//写入多个字节的方法
//如果写入的第一个字节是正数(0-127),会查询ASCII码表
//如果写入的第一个字节是负数,会和第二个字节组成一个中文显示,查询系统默认码表(中文GBK)
byte[] bytes = {65, 66, 67, 68, 69, 70};
byte[] bytes2 = {-65, -66, -67, 68, 69, 70};//-65和-66组成一个中文,-67和68组成一个中文,69、70还是大写字母E、F 文件显示烤紻EF
FileOutputStream os3 = new FileOutputStream(“day08-code\src\demo01IO\3.txt”);
os3.write(bytes2);//ABCDEF
os3.close();
//写字节数组的一部分
byte[] bytes3 = {65, 66, 67, 68, 69, 70};
FileOutputStream os4 = new FileOutputStream(“day08-code\src\demo01IO\4.txt”);
os4.write(bytes3, 1, 3);//从字节数组bytes3的索引1开始写入三个字节 文件显示BCD
os4.close();
//写入字符串 使用String类的方法getBytes将字符串转换为字节数组
String s = “我们 你们 他们”;
byte[] bytes4 = s.getBytes();
FileOutputStream os5 = new FileOutputStream(“day08-code\src\demo01IO\5.txt”);
os5.write(bytes4);//我们 你们 他们 UTF-8中一个中文3个字节,GBK中一个中文两个字节 6个中文2个空格一共20个字节
}
}
1.3 文件的续写和换行
续写使用FileOutputStream的带两个参数的构造方法
FileOutputStream(String name, boolean append);
FileOutputStream(File file,boolean append);
String name/File file 写入数据的目的地
boolean append 是否追加写
换行符号:Windows \r\n
Linux /n
Mac /r
测试类
public class Demo03ContinueWrite {
public static void main(String[] args) throws IOException {
//追加写
FileOutputStream fos = new FileOutputStream(“day08-code\src\demo01IO\1.txt”, true);
fos.write(65);//aA
//换行
FileOutputStream fos2 = new FileOutputStream(“day08-code\src\demo01IO\2.txt”, true);
for (int i = 0; i < 10; i++) {
fos2.write(“hello”.getBytes());
fos2.write("\r\n".getBytes());
}
//100hello
//hello
//hello
//hello
//hello
//hello
//hello
//hello
//hello
//hello
fos2.close();
}
}
1.4 InputStream类
InputStream是表示字节输入流的所有类的父类,是一个抽象类
方法:
void close();关闭此输入流并释放相关资源
void read();从输入流中读取数据的下一个字节,一次读取一个字节,读取到末尾返回-1
int read(byte[] b);从输入流中一次读取多个字节,并将其存储在缓冲区中数组b里
子类:AudioInputStream ByteArrayInputStream FileInputStream FilterInputStream
InputStream ObjectInputStream PipedInputStream
SequenceInputStream StringBufferInputStream
FileInputStream 文件字节输入流 将硬盘文件中的数据读取到内存中
构造方法:
FileInputStream(String name);//文件路径
FileInputStream(File file);//文件
构造方法做了两件事:
(1)创建一个FileOutputStream对象
(2)将FileOutputStream对象指向构造方法中要读取的文件
读取数据的原理:Java程序 -> JVM -> OS -> OS调用读取数据的方法 -> 读取文件
FileInputStream使用步骤:
(1)创建FileInputStream,构造方法中绑定要读取的数据源
(2)调用read方法,读取数据
(3)调用close方法,释放资源
测试类
public class Demo04InputStream {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(“day08-code\src\demo01IO\1.txt”);
int num = fis.read();
System.out.println(num);//1.txt中第一个字节为a 输出97
num = fis.read();
System.out.println(num);//1.txt中第二个字节为A 输出65
//使用while循环优化
int num1 = 0;
while((num1 = fis.read()) != -1) {
//分为三步:读取下一个字节;将读取到的字节赋值给num;判断num是否不等于-1,若等于-1就跳出循环
//read读取到一个字节后,会将指针后移一位
System.out.println((char)num1);
}
fis.close();
//读取原理:
// 创建FileInputStream,并指向要读取的文件的第一个字节
// 调用read方法,Java程序 -> JVM -> OS -> OS调用读取数据的方法读取一个字节 -> 返回给JVM ->返回给Java程序
// 读取完一个字节后指针后移一位
// 循环读取字节,直到OS读取到系统的结束标记(不可见)-> 返回给JVM结束标记 -> 返回给Java程序-1
// 读取后指针后移一位
}
}
1.5 一次读取多个字节
int read(byte[] b)
返回读取的有效字节个数
byte数组起到缓冲作用,存储每次读取到的多个字节 长度一般定义为1024或1024的整数倍
测试类
public class Demo05ReadMulti {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(“day08-code\src\demo01IO\6.txt”);
byte[] bytes = new byte[2];
int len = fis.read(bytes);
System.out.println(len);//2
System.out.println(new String(bytes));//AB 使用String类的构造方法将字节数组转换为字符串输出
len = fis.read(bytes);
System.out.println(len);//2
System.out.println(new String(bytes));//CD
len = fis.read(bytes);
System.out.println(len);//1
System.out.println(new String(bytes));//ED
len = fis.read(bytes);
System.out.println(len);//-1
System.out.println(new String(bytes));//ED
//读取原理:
//创建FileInputStream对象,将其指向要读取的文件,刚开始指向第一个字节
//创建大小为2的字节数组,默认值为 0 0(byte short int long默认值都是0)
//读取数据。Java程序 -> JVM -> OS -> OS调用读取数据的方法 -> 读取AB返回给OS -> 返回给JVM -> 返回给Java程序 字节数组的值变为A B len=2
//指针指向C 第二次读取:读取CD 字节数组的值变为C D len=2
//指针指向E 第三次读取:读取E 发现下一个是结束标记,结束读取 字节数组变为E D len=1
//指针指向结束标记 第四次读取:无数据可读取 字节数组还是E D len=-1
//使用循环优化
int len1 = 0;
byte[] bytes1 = new byte[1024];
while((len1 = fis.read(bytes1)) != -1) {
System.out.println(len1);
System.out.println(new String(bytes1));//直接将字节数组转换为字符串打印会打印出很多空格,因为数组长度为1024,而这里只读取了5个字节
//System.out.println(new String(bytes, 0, len));//将部分数组转换为字符串进行打印,只打印读取的部分
}
}
}
1.6 字节流练习:文件复制
public class Demo06FileCopy {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
//创建文件字节输入流,指向要复制的文件,用于读取文件
FileInputStream fis = new FileInputStream(“H:\图片\壁纸.jpeg”);
//创建文件字节输出流,指向要复制到的目的地文件,用于写入文件
FileOutputStream fos = new FileOutputStream(“G:\有的没的\壁纸.jpeg”);
//read方法返回值,用于判断是否读取到文件末尾
int len = 0;
//定义字节数组,用于存储每次读取的多个字节
byte[] bytes = new byte[1024];
//循环读取文件
while ((len = fis.read(bytes)) != -1) {
//将每次读取到的字节数组写入到目的地文件中
fos.write(bytes, 0, len);
}
//先关闭写的输出流,再关闭读的输入流,因为写完了肯定读完了
fos.close();
fis.close();
long end = System.currentTimeMillis();
long time = end - start;
System.out.println(“复制文件共耗时:” + time + “ms”);
}
}
1.7 使用字节流读取中文
UTF-8:一个中文占用三个字节
GBK:一个中文占用两个字节
使用字节流读取无法读取中文,需要使用字符流
测试类
public class Demo07ReadChinese {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(“day08-code\src\demo01IO\7.txt”);
int len = 0;
while ((len = fis.read()) != -1) {
System.out.println(len);
//228
//189
//160
//229
//165
//189 读取了两个中文字符对应的6个字节,前三个字节组成你,后三个字节组成好
System.out.println((char) len);//转为char类型会出现乱码
}
fis.close();
}
}
2、字符流
2.1 Reader类
Reader类是所有字符输入流的父类,是一个抽象类
方法:
int read();一次读取一个字符
int read(char[] cbuf);一次读取多个字符,放入数组
void close();释放资源
子类:BufferedReader CharArrayReader FilterReader InputStreamReader
PipedReader StringReader
FileReader是InputStreamReader的子类 读取字符文件 将硬盘中的文件以字符的形式读取到内存中
构造方法:
FileReader(String filename);文件路径
FileReader(File file);文件
构造方法做了两件事:
(1)创建FileReader对象
(2)将FileReader对象指向要读取的文件
使用步骤:
(1)创建FileReader对象,参数指向要读取的文件
(2)调用read方法以字形式读取文件
(3)调用close方法释放资源
测试类
public class Demo01Reader {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader(“day08-code\src\demo02Reader\1.txt”);
int len = 0;
//一次读取一个字符
while ((len = fr.read()) != -1) {
System.out.print((char) len);//123你好hellojava再见???###
}
//一次读取多个字符
char[] chars = new char[1024];
while ((len = fr.read(chars)) != -1) {
System.out.println(new String(chars, 0 ,len));//123你好hellojava再见???###
}
fr.close();
}
}
2.2 Writer类
与字节输出流的最大区别:会将数据写入到内存中,而不是直接写入到文件中
Writer是所有字符输出流的父类,是一个抽象类
方法:
void write(int c);
void write(char[] cbuf);
void write(String str);
void write(String str, int off, int len);
void flush();刷新该流的缓冲
void close();
子类:BufferedWriter CharArrayWriter FilterWriter OutputStreamWriter
PipedWriter PrintWriter StringWriter
FileWriter是OutputStreamWriter的子类 文件字符输出流 将内存中的字符数据写入硬盘文件中
FileWriter(String filename);文件路径
FileWriter(File file);文件
构造方法做了三件事:
(1)创建FileWriter对象
(2)根据参数传递的文件/文件路径创建一个文件
(3)将FileWriter对象指向创建的文件
使用步骤:
(1)创建FileWriter对象,参数指向要写入数据的文件
(2)调用write方法,将数据以字符形式写入内存缓冲区(在内存中有一个将字符转换为字节的过程)
(3)调用flush方法将内存缓冲区中的数据刷新到文件中
(4)调用close方法释放资源(会先将内存缓冲区中的数据刷新到文件中,可以不写flush方法,但关闭流后不能继续使用)
测试类
public class Demo02Writer {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter(“day08-code\src\demo02Reader\2.txt”);
//write(int c)
fw.write(97);//a
//write(char[] cbuf)
char[] chars = {‘c’, ‘d’, 49, 48, 48, ‘1’, ‘我’, ‘你’, ‘%’, ‘?’};
fw.write(chars);//acd1001我你%?
//write(String str, int off, int len)
fw.write(chars, 5, 3);//acd1001我你%?1我你
//write(String str)
fw.write(“hello java”);//acd1001我你%?1我你hello java
//write(String str, int off, int len)
fw.write(“hello world”, 2, 6);//acd1001我你%?1我你hello javallo wo
fw.close();
}
}
2.3 字符输出流的续写和换行
续写使用FileWriter的带两个参数的构造方法
FileWriter(String filename, boolean append);
FileWriter(File file, boolean append);
append = true 续写
换行
Windows \r\n
Linux /n
Mac /r
测试类
public class Demo03ContinueWrite {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter(“day08-code\src\demo02Reader\2.txt”,true);
for (int i = 0; i < 5; i++) {
//fw.write(“你好” + i);//acd1001我你%?1我你hello javallo wo你好0你好1你好2你好3你好4
fw.write(“你好” + i + “\r\n”);
//acd1001我你%?1我你hello javallo wo你好0你好1你好2你好3你好4你好0
//你好1
//你好2
//你好3
//你好4
}
fw.close();
}
}
2.4 使用try-catch-finally处理流中的异常
(1)将FileWriter对象指向要读取的文件和调用write方法将数据写入文件这两步放入try
(2)将调用close方法释放资源这一步放入finally,否则放入try前面出现异常的话就不会释放资源了
(3)创建FileWriter对象这一步要放在try-catch之前,否则放入try的话,finally中无法使用这个对象
(4)创建FileWriter对象时初始化为null,否则try中创建对象失败的话,finally中无法使用这个对象
(5)调用close方法也可能有异常,要在finally中嵌套一个try-catch
(6)创建FileWriter对象失败的话,fw默认为null,null无法调用方法,所以在调用close方法之前要先判断是否为null,否则会抛出空指针异常
(创建FileWriter对象失败,下面的代码不执行,所以不需要在调用write方法之前判断是否为null)
2.5 简化处理流异常的try-catch方法
JDK7的新特性:可以在try后加一个(),里面创建流对象,try结束后会自动释放流对象,不用写finally
JDK9的新特性:可以在try前面定义流对象,在try后的括号里引入对象名,try结束后会自动释放流对象,不用写finally,但在try前面定义流对象的语句也会有异常,所以还是需要throws抛出异常
测试类
public class Demo05trycatchSimplify {
public static void main(String[] args) throws IOException {
//JDK7
try(FileWriter fw = new FileWriter(“day08-code\src\demo02Reader\2.txt”,true)😉 {
for (int i = 0; i < 5; i++) {
fw.write(“你好” + i + “\r\n”);
}
} catch (IOException e) {
System.out.println(e);
}
//JDK9
FileWriter fw = new FileWriter(“day08-code\src\demo02Reader\2.txt”,true);
try(fw) {
for (int i = 0; i < 5; i++) {
fw.write(“你好” + i + “\r\n”);
}
} catch (IOException e) {
System.out.println(e);
}
}
}
3、Properties 属性集
3.1 Properties概述
Properties 属性集
唯一和I/O流结合的集合 可以保存在流中或从流中加载 每个键和值都是字符串,不用写泛型
Properties extends Hashtable<K,V>
Hashtable<K,V> implements Map<K,V>
方法:
void load(InputStream inStream);将硬盘中的文件(键值对)读取到集合中使用
void load(Reader reader);
void store(OutputStream out, String comments);字节输出流 不能写入中文
void store(Writer writer, String comments);字符输出流 可以写入中文
操作字符串的特有方法:
Object setProperty(String key, String value);底层调用Map的put方法
String getProperty(String key);底层调用Map的get方法
Set stringPropertyName();返回此属性列表中的键集 底层调用Map的keySet方法
测试类
public class Demo01Properties {
public static void main(String[] args) {
Properties p = new Properties();
p.setProperty(“01”, “张三”);
p.setProperty(“02”, “李四”);
p.setProperty(“03”, “王五”);
for (String key : p.stringPropertyNames()) {
System.out.println(key + “:” + p.getProperty(key));//01:张三
//02:李四
//03:王五
}
}
}
3.2 store方法
将集合中的临时数据持久化写入到硬盘中存储
void store(OutputStream out, String comments);字节输出流 不能写入中文
void store(Writer writer, String comments);字符输出流 可以写入中文
String comments 解释说明保存的文件是干什么的 不能使用中文,会产生乱码,因为默认是Unicode编码 而系统默认是GBK编码 一般使用空字符串""
使用步骤:
(1)创建Properties对象
(2)创建字节/字符输出流对象,绑定要输出到的文件
(3)调用store方法
(4)调用close方法释放资源
3.3 load方法
void load(InputStream inStream);字节输入流 不能读取含有中文的键值对
void load(Reader reader);字符输入流 可以读取含有中文的键值对
使用步骤:
(1)创建Properties类的对象
(2)调用load方法读取保存键值对的文件
(3)遍历Properties对象检查是否读取成功
注意:存储键值对的文件中,可以用=、空格或其他符号连接键值对;
可以用#注释键值对;
键值对已经是字符串,不需要加引号
public class Demo03PropertiesLoad {
public static void main(String[] args) throws IOException {
Properties p = new Properties();
//这里要读取的文件含有中文 使用字符输入流
FileReader fr = new FileReader(“day08-code\src\demo03Properties\1.txt”);
p.load(fr);
System.out.println§;//{01=张三, 02=李四, 03=王五}
for (String key : p.stringPropertyNames()) {
System.out.println(key + “=” + p.getProperty(key));//01=张三
//02=李四
//03=王五
}
}
}
4、缓冲流
4.1 BufferedOutputStream类 字节缓冲输出流
缓冲流
对基本流的增强
给基本流增加一个缓冲区(数组),一次读取/写入多个字节/字符,提高读写效率
BufferedOutputStream 字节缓冲输出流
构造方法:
BufferedOutputStream(OutputStream out);在字节输出流内部创建默认大小的缓冲区
BufferedOutputStream(OutputAtream out, int size);在字节输出流内部创建指定大小的缓冲区
使用步骤:
(1)创建OutputStream对象,绑定要输出到的文件
(2)创建BufferedOutputStream对象,参数传递OutputStream对象
(3)调用write方法,将数据写入BufferedOutputStream对象的内部缓冲区
(4)调用flush方法,将内部缓冲区的数据刷新到文件中
(5)调用close方法释放资源(会先调用flush方法刷新数据到文件中)
测试类
public class Demo01BufferedOutputStream {
public static void main(String[] args) throws IOException {
FileOutputStream fis = new FileOutputStream(“day08-code\src\demo04Buffered\1.txt”);
BufferedOutputStream bis = new BufferedOutputStream(fis);
bis.write(“写入数据到1.txt”.getBytes());
bis.close();//关闭缓冲流会自动关闭基本流
}
}
4.2 BufferedInputStream类 字节缓冲输入流
构造方法:
BufferedInputStream(InputStream in);在字节输入流内部创建默认大小的缓冲区
BufferedInputStream(InputAtream in, int size);在字节输入流内部创建指定大小的缓冲区
使用步骤:
(1)创建InputStream对象,绑定要读取的文件
(2)创建BufferedInputStream对象,参数传递InputStream对象
(3)调用read方法,将数据读取到BufferedInputStream对象的内部缓冲区
(4)调用close方法释放资源
测试类
public class Demo02BufferedInputStream {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream(“day08-code\src\demo04Buffered\1.txt”);
BufferedInputStream bis = new BufferedInputStream(fis);
//一次读取一个字节
int len = 0;//记录每次读取到的字节
while ((len = bis.read()) != -1) {
System.out.println(len);
}
//一次读取多个字节
int len1 = 0;//记录每次读取的有效字节个数
byte[] bytes = new byte[1024];//记录每次读取到的多个字节
while((len1 = bis.read(bytes)) != -1) {
System.out.println(new String(bytes, 0, len1));//写入数据到1.txt
}
bis.close();
}
}
4.3 使用字节缓冲流来复制文件
使用字节流耗时5ms
使用字节缓冲流耗时3ms
public class Demo03BufferedFileCopy {
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
FileInputStream fis = new FileInputStream(“G:\有的没的\FCN摘要.PNG”);
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream(“H:\图片\FCN摘要.PNG”);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int len = 0;
byte[] bytes = new byte[1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
bos.close();
bis.close();
// FileInputStream fis = new FileInputStream(“G:\有的没的\FCN摘要.PNG”);
// FileOutputStream fos = new FileOutputStream(“H:\图片\FCN摘要.PNG”);
// int len = 0;
// byte[] bytes = new byte[1024];
// while ((len = fis.read(bytes)) != -1) {
// fos.write(bytes, 0, len);
// }
// fos.close();
// fis.close();
long end = System.currentTimeMillis();
long time = end - start;
System.out.println(“耗时” + time + “ms”);
}
}
4.4 BufferedWriter类 字符缓冲输出流
构造方法:
BufferedWriter(Writer out);默认缓冲区大小
BufferedWriter(Writer out, int sz);指定缓冲区大小
参数传递字节输出流,缓冲流会给其创建一个缓冲区,提高其写入效率
特有方法:
void newLine();写入一个换行符,会自动根据不同的操作系统获取不同的换行符
println调用的就是newLine方法
public class Demo04BufferedWriter {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter(“day08-code\src\demo04Buffered\2.txt”));
bw.write(“我是一个粉刷匠”, 0, 4);
bw.newLine();//换行
bw.write(“卖报的”);
bw.close();//我是一个
//卖报的
}
}
4.5 BufferedReader类 字符缓冲输入流
构造方法:
BufferedReader(Reader in);默认缓冲区大小
BufferedReader(Reader in, int sz);指定缓冲区大小
参数传递字节输入流,缓冲流会给其创建一个缓冲区,提高其写入效率
特有方法:
String readLine();
读一行文本 根据行终止符号:换行\r 回车\n 换行后跟着回车\r\n来确定一行是否结束
返回一行文本内容,不包括换行符号,若已到达行末尾,返回null
测试类
public class Demo05BufferedReader {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(“day08-code\src\demo04Buffered\2.txt”));
int len = 0;
char[] chars = new char[1024];
while ((len = br.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));//我是一个
//卖报的
}
System.out.println(br.readLine());//我是一个
//使用循环来读取数据
String line= null;
while ((line = br.readLine()) != null) {
System.out.println(line);//我是一个
//卖报的
}
br.close();
}
}
4.6 练习:对文本进行排序
(1)创建HashMap对象,key存储每行文本序号,value存储每行文本内容
(2)创建字符缓冲输入流对象
(3)创建字符缓冲输出流对象
(4)调用readLine方法每次读取一行文本
(5)对读取到的文本进行切割,得到文本序号和文本内容
(6)将切割好的文本存入HashMap对象,其会自动排序
(7)遍历HashMao对象,获取每一个键值对
(8)将每一个键值对拼接为每一行文本
(9)调用write方法写入每一行文本
(10)释放资源
注意:字符输入流可以正常读取IDE默认的编码格式UTF-8编码的文本,但读取系统默认编码(中文系统是GBK)的文本就会出现乱码,所以这里要读取的文本可以直接在idea里创建并转换为UTF-8编码
出师表.txt
1.先帝创业未半而中道崩殂(cú),今天下三分,益州疲(pí)弊,此诚危急存亡之秋也。然侍卫之臣不懈(xiè)于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗(yí)德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞(sè)忠谏之路也。
9.今当远离,临表涕零,不知所云。
3.侍中、侍郎郭攸(yōu)之、费祎(yī)、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗(wèi)陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨(bì)补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎(jiù)。陛下亦宜自谋,以咨诹(zōu)善道,察纳雅言,深追先帝遗诏。臣不胜受恩感激!
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻(wén)达于诸侯。先帝不以臣卑(bēi)鄙(bǐ),猥(wěi)自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有(yòu)一年矣! 【“有”是通假字,通“又”,跟在数词后面表示约数。所以读yòu】
2.宫中府中,俱为一体,陟(zhì )罚臧(zāng)否(pǐ),不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙(sù)夜忧叹,恐托付不效,以伤先帝之明,故五月渡(dù)泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶(shù)竭驽(nú)钝,攘(rǎng)除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓(huán)、灵也。侍中、尚书、长(zhǎng)史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
4.将军向宠,性行(xíng)淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行(háng )阵和睦,优劣得所。
测试类
public class Demo06FileSort {
public static void main(String[] args) throws IOException {
//1、创建HashMap对象,key存储每行文本序号,value存储每行文本内容
HashMap<String, String> map = new HashMap<>();
//2、创建字符缓冲输入流对象
BufferedReader br = new BufferedReader(new FileReader(“day08-code\src\demo04Buffered\出师表.txt”));
//3、创建字符缓冲输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter(“day08-code\src\demo04Buffered\出师表排序后.txt”));
//4、调用readLine方法每次读取一行文本
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
//5、对读取到的文本进行切割,得到文本序号和文本内容
String[] linetext = line.split("\.");
System.out.println(linetext[0]);
//6、将切割好的文本存入HashMap对象,其会自动排序
map.put(linetext[0], linetext[1]);
}
//7、遍历HashMao对象,获取每一个键值对
for (String key : map.keySet()) {
//8、将每一个键值对拼接为每一行文本
String everyline = key + “.” + map.get(key);
//9、调用write方法写入每一行文本
//换行
bw.write(everyline);
bw.newLine();
}
//10、释放资源
bw.close();
br.close();
}
}
5、转换流
5.1 编码概述
编码:字符 -> 字节
解码:字节 -> 字符
字符编码:自然语言与二进制之间的对应规则
字符集/编码表:一个系统支持的所有字符的集合
一套字符集至少要有一套字符编码
ASCII:显示字母、数字和常用符号 整数7位表示一个字符 负数8位表示一个字符
ISO-8859-1:显示欧洲常用文字 单字节编码 兼容ASCII
GB2312:简体中文
GBK:双字节编码(任何字符占用两个字节) 兼容GB2312 繁体汉字 日韩汉字
GB18030:多字节编码(每个字符占用1/2/4个字节) 少数民族汉字
Unicode:万国码 最多使用4个字节表示字符 包括UTF-8 UTF-16 UTF-32
ASCII 1个字节
拉丁文 2个字节
大部分语言(含中文) 3个字节
极少使用的Unicode辅助字符 4个字节
5.2 OutputStreamWriter类
FileReader用来以字符的形式读取文件,其源码是调用了FileInputStream先以字节形式读取文件,再将读取到的字节转换为字符。但其只能查询IDE默认编码表UTF-8 只能正常读取UTF-8编码格式的文件 读取GBK格式的文件会出现乱码
其父类InputStreamReader 可以使用指定的字符集将字节转换为字符
可以查询指定的编码表
OutputStreamWriter 可以使用指定的字符集将字符转换为字节
可以查询指定的编码表
OutputStreamWriter
构造方法:
OutputStreamWriter(OutputStream out);默认字符集 将字节写入文件中
OutputStreamWriter(OutputStream out, String charsetName);String charsetName 指定字符集 不区分大小写
测试类
public class Demo02OutputStreamWriter {
public static void main(String[] args) throws IOException {
FileOutputStream fis = new FileOutputStream(“day08-code\src\demo05Convert\gbk.txt”);
OutputStreamWriter ow = new OutputStreamWriter(fis, “GBK”);
ow.write(“这是GBK编码格式的文件”);//写入了GBK编码格式的文件,打开文件会默认以UTF-8的格式查看,会出现乱码,需要设置为GBK格式
ow.close();
}
}
5.3 InputStreamReader类
构造方法:
InputStreamReader(InputStream in);默认字符集 InputStream in 读取文件中保存的字节
InputStreamReader(InputStream in, String charsetName);String charsetName 指定字符集 不区分大小写
public class Demo03InputStreamReader {
public static void main(String[] args) throws IOException {
InputStreamReader ir = new InputStreamReader(new FileInputStream(“day08-code\src\demo05Convert\gbk.txt”), “GBK”);
int len = 0;
char[] chars = new char[1024];
while ((len = ir.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));//这是GBK编码格式的文件
}
}
}
5.4 练习:转换文件编码格式 GBK -> UTF-8
使用绑定GBK的输入转换流,读取GBK编码格式的文件
使用绑定UTF-8的输出转换流,将读取到的数据写入UTF-8编码格式的文件
public class Demo04ConvertEncoding {
public static void main(String[] args) throws IOException {
InputStreamReader ir = new InputStreamReader(new FileInputStream(“day08-code\src\demo05Convert\gbk.txt”), “GBK”);
OutputStreamWriter ow = new OutputStreamWriter(new FileOutputStream(“day08-code\src\demo05Convert\utf8.txt”), “UTF-8”);
int len = 0;
char[] chars = new char[1024];
while ((len = ir.read(chars)) != -1) {
ow.write(chars, 0, len);
}
ow.close();
ir.close();
}
}
6、序列化
6.1 对象的序列化流/反序列化流
(1)将对象以流的形式写入文件,称为写对象/对象的序列化
对象中不仅仅包含字符,也包含字符,所以使用字节流
ObjectOutputStream 对象的序列化流
(2)将文件中的对象以流的形式读取出来,称为读对象/对象的反序列化
读取的文件保存数据的形式都是字节,所以使用字节流
ObjectInputStream 对象的反序列化流
6.2 ObjectOutputStream 对象的序列化
ObjectOutputStream 对象的序列化
构造方法:ObjectOutputStream(OutputStream out);参数传递字节输出流
特有方法:writeObject();
序列化和反序列化时,会抛出没有序列化异常NotSerializableException,要进行序列化的类必须实现Serializable接口以启用序列化功能
Serializable接口也被称为标记型接口,源码没有任何东西,会给实现该接口的类加上一个标记,用于序列化时判断其是否可以序列化
一个文件只能写入一个对象
Person类
public class Person implements Serializable {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return “Person{” +
“name=’” + name + ‘’’ +
“, age=” + age +
‘}’;
}
}
测试类
public class Demo02ObjectOutputStream {
public static void main(String[] args) throws IOException {
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(“day08-code\src\demo06Serialize\1.txt”));
Person p = new Person(“张三”, 19);
//Person p1 = new Person(“李四”, 16);
//oo.writeObject(p1);
oo.writeObject§;
oo.close();
}
}
6.3 ObjectInputStream 对象的反序列化
构造方法:ObjectInputStream(InputStream in);参数传递字节输入流
特有方法:readObject();会声明抛出class文件找不到异常ClassNotFoundException
序列化和反序列化时,会抛出没有序列化异常NotSerializableException,要进行序列化的类必须实现Serializable接口以启用序列化功能
Serializable接口也被称为标记型接口,源码没有任何东西,会给实现该接口的类加上一个标记,用于序列化时判断其是否可以序列化
反序列化前提:(1)要反序列化的类必须实现Serializable接口
(2)必须存在类对应的class文件
测试类
public class Demo03ObjectInputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream oi = new ObjectInputStream(new FileInputStream(“day08-code\src\demo06Serialize\1.txt”));
Object o = oi.readObject();
System.out.println(o);//Person{name=‘张三’, age=19}
Person p = (Person) o;
System.out.println(p.getName() + p.getAge());//张三19
oi.close();
}
}
7、
7.1 transient关键字
static关键字:静态优先于非静态加载到内存中(优先于对象进入到内存中)
被static修饰的成员变量是不能被序列化的 序列化的都是对象
transient关键字:被transient修饰的成员变量是不能被序列化的 不希望某个成员变量被序列化可以加上transient关键字
如:Person类的age加上transient 序列化再反序列化输出的age是默认值0,不是我们设置的初始值19
7.2 InvalidClassException异常
当JVM反序列化对象时,能找到class文件,但class文件在序列化对象之后发生了修改,反序列化操作也会失败并抛出异常InvalidClassException
如:序列化Person对象生成1.txt文件之后,修改age的类型 private变为public,对应的class文件也就发生了变化,再反序列化就会抛出异常,
原因是txt文件里的序列号和class文件中的序列号不一样,发生冲突
原理:
(1)编译器javac会把源文件Person.java编译生成字节码文件Person.class
(2)Person类实现了Serializable接口,会根据类的定义(定义的属性、方法)给Person.class文件里添加上一个序列号serialVersionUID
(3)序列化,将对象写入1.txt文件,txt文件中也会有序列号serialVersionUID
(4)反序列化,会对比txt文件和class文件中的序列号,一样则反序列化成功
(5)修改类的定义,会重新编译生成class文件,根据类的定义会更新序列号
(6)反序列化,两个文件的序列号不一样,反序列化失败,抛出异常InvalidClassException
问题:每次修改类的定义,都会生成新的序列号
解决:
无论是否修改类的定义,都不重新生成新的序列号,在类中手动添加一个不变的序列号
static final long serialVersionUID = ***L;
7.3 练习:序列化集合
当希望在文件中保存多个对象时,可以将多个对象存入集合,将集合序列化
Person类
public class Person implements Serializable {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return “Person{” +
“name=’” + name + ‘’’ +
“, age=” + age +
‘}’;
}
}
测试类
public class Demo03SerializeArray {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(“day08-code\src\demo07\1.txt”));
ObjectInputStream oi = new ObjectInputStream(new FileInputStream(“day08-code\src\demo07\1.txt”));
Person p1 = new Person(“张三”, 19);
Person p2 = new Person(“李四”, 29);
Person p3 = new Person(“王五”, 15);
ArrayList<Person> list = new ArrayList<>();
list.add(p1);
list.add(p2);
list.add(p3);
oo.writeObject(list);
Object obj = oi.readObject();
System.out.println(obj);//[Person{name=‘张三’, age=19}, Person{name=‘李四’, age=29}, Person{name=‘王五’, age=15}]
ArrayList list2 = (ArrayList) obj;
for (Person person : list) {
System.out.print(person);
System.out.println(person.getName() + person.getAge());
}
oo.close();
oi.close();
}
}
7.4 打印流PrintStream
打印流PrintStream extends OutputStream 为其它输出流提供方便地打印各种数据值的功能
System.out.println中的out就是一个打印流,println就是打印流中的方法
特点:
(1)只负责数据的输出,不负责读取
(2)永远不会抛出IOException(会抛出文件找不到异常)
特有方法:print println 可以输出任意类型的值
构造方法:
PrintStream(File file);
PrintStream(String filename);
PrintStream(OutputStream out);
注意:
(1)如果使用继承自父类的write方法写入数据,那么查看数据的时候会查询编码表 97就是a
(2)如果使用自己特有的方法print println写入数据,写的数据原样输出 97就是97
可以使用System类中的setOut方法设置输出语句的目的地,将目的地改为参数中传递的打印流的目的地
public class Demo04PrintStream {
public static void main(String[] args) throws FileNotFoundException {
PrintStream ps = new PrintStream(new FileOutputStream(“day08-code\src\demo07\print.txt”));
ps.write(97);
ps.print(97);
ps.close();//a97
//改变输出语句的目的地
System.out.println(“输出在控制台”);
PrintStream ps2 = new PrintStream(“day08-code\src\demo07\改变后的输出目的地.txt”);
System.setOut(ps2);
System.out.println(“输出在改变后的输出目的地文件中”);
}
}