第一章 缓冲流
缓冲流
,
也叫高效流,是对
4
个基本的
FileXxx
流的增强,所以也是
4
个流,按照数据类型分类:
字节缓冲流
:
BufferedInputStream
,
BufferedOutputStream
字符缓冲流
:
BufferedReader
,
BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统
IO
次数,从而提高读写的效率。
1.1
字节缓冲流
构造方法
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。
public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
//
创建字节缓冲输入流
BufferedInputStream bis
=
new
BufferedInputStream
(
new
FileInputStream
(
"a.txt"
));
//
创建字节缓冲输出流
BufferedOutputStream bos
=
new
BufferedOutputStream
(
new
FileOutputStream
(
"b.txt"
));
缓冲流,代码如下:
//
记录开始时间
long
start
=
System
.
currentTimeMillis
();
//
创建流对象
try
(
BufferedInputStream bis
=
new
BufferedInputStream
(
new
FileInputStream
(
"jdk8.exe"
));
BufferedOutputStream bos
=
new
BufferedOutputStream
(
new
FileOutputStream
(
"copy.exe"
));
){
//
读写数据
int
b
;
while
((
b
=
bis
.
read
())
!= -
1
) {
bos
.
write
(
b
);
}
}
catch
(
IOException e
) {
e
.
printStackTrace
();
}
//
记录结束时间
long
end
=
System
.
currentTimeMillis
();
System
.
out
.
println
(
"
缓冲流复制时间
:"
+
(
end
-
start
)
+
"
毫秒
"
);
如何更快,使用数组的方式,代码如下:
//
记录开始时间
long
start
=
System
.
currentTimeMillis
();
//
创建流对象
try
(
BufferedInputStream bis
=
new
BufferedInputStream
(
new
FileInputStream
(
"jdk8.exe"
));
BufferedOutputStream bos
=
new
BufferedOutputStream
(
new
FileOutputStream
(
"copy.exe"
));
){
//
读写数据
int
len
;
byte
[]
bytes
=
new
byte
[
1024*10
];
while
((
len
=
bis
.
read
(
bytes
))
!= -
1
) {
bos
.
write
(
bytes
,
0
,
len
);
}
}
catch
(
IOException e
) {
e
.
printStackTrace
();
}
//
记录结束时间
long
end
=
System
.
currentTimeMillis
();
System
.
out
.
println
(
"
缓冲流使用数组复制时间
:"
+
(
end
-
start
)
+
"
毫秒
"
);
1.2
字符缓冲流
构造方法
public BufferedReader(Reader in)
:创建一个 新的缓冲输入流。
public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
//
创建字符缓冲输入流
BufferedReader br
=
new
BufferedReader
(
new
FileReader
(
"br.txt"
));
//
创建字符缓冲输出流
BufferedWriter bw
=
new
BufferedWriter
(
new
FileWriter
(
"bw.txt"
));
特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
BufffferedReader
:
public String readLine()
:
读一行文字。
BufffferedWriter
:
public void newLine()
:
写一行行分隔符
,
由系统属性定义符号。
readLine
方法演示,代码如下:
//
创建流对象
BufferedReader br
=
new
BufferedReader
(
new
FileReader
(
"a.txt"
));
//
定义字符串
,
保存读取的一行文字
String
line
=
null
;
//
循环读取
,
读取到最后返回
null
while
((
line
=
br
.
readLine
())
!=
null
) {
System
.
out
.
print
(
line
);
System
.
out
.
println
(
"------"
);
}
//
释放资源
br
.
close
();
newLine
方法演示,代码如下:
//
创建流对象
BufferedWriter bw
=
new
BufferedWriter
(
new
FileWriter
(
"a.txt"
));
//
写出数据
bw
.
write
(
"硅谷
"
);
//
写出换行
bw
.
newLine
();
bw
.
write
(
"
程序
"
);
bw
.
newLine
();
bw
.
write
(
"
员
"
);
bw
.
newLine
();
//
释放资源
bw
.
close
();
第二章 转换流
2.1
字符编码和字符集
字符编码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制
数转换之后的结果。按照某种规则,将字符存储到计算机中,称为
编码
。反之,将存储在计算机中的二进制数按照
某种规则解析显示出来,称为
解码
。比如说,按照
A
规则存储,同样按照
A
规则解析,那么就能显示正确的文本
f
符
号。反之,按照
A
规则存储,再按照
B
规则解析,就会导致乱码现象。
编码
:
字符转换为字节
解码
:
字节转换为字符
字符编码
Character Encoding
:
就是一套自然语言的字符与二进制数之间的对应规则。
生活中的文字和计算机文字的对应关系
a-->97-->1100001
中
-->20013--100111000101101
字符集
字符集
Charset
:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符
号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符
集有
ASCII
字符集、
GBK
字符集、
Unicode
字符集等。
当指定了
编码
,它所对应的
字符集
自然就指定了,所以
编码
才是我们最终要关心的。
ASCII
字符集
:
ASCII
(
American Standard Code for Information Interchange
,美国信息交换标准代码)是基于拉丁
字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显
示字符(英文大小写字符、阿拉伯数字和西文符号)。
基本的
ASCII
字符集,使用
7
位(
bits
)表示一个字符,共
128
字符。
ASCII
的扩展字符集使用
8
位(
bits
)
表示一个字符,共
256
字符,方便支持欧洲常用字符。
ISO-8859-1
字符集
:
拉丁码表,别名
Latin-1
,用于显示欧洲使用的语言,包括荷兰、丹麦、德语、意大利语、西班牙语等。
ISO-5559-1
使用单字节编码,兼容
ASCII
编码。
GBxxx
字符集
:
GB
就是国标的意思,是为了显示中文而设计的一套字符集。
GB2312
:简体中文码表。一个小于
127
的字符的意义与原来相同。但两个大于
127
的字符连在一起时,
就表示一个汉字,这样大约可以组合了包含
7000
多个简体汉字,此外数学符号、罗马希腊的字母、日文
的假名们都编进去了,连在
ASCII
里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这
就是常说的
"
全角
"
字符,而原来在
127
号以下的那些就叫
"
半角
"
字符了。
GBK
:最常用的中文码表。是在
GB2312
标准基础上的扩展规范,使用了双字节编码方案,共收录了
21003
个汉字,完全兼容
GB2312
标准,同时支持繁体汉字以及日韩汉字等。
GB18030
:最新的中文码表。收录汉字
70244
个,采用多字节编码,每个字可以由
1
个、
2
个或
4
个字节
组成。支持中国国内少数民族的文字,同时支持繁体汉字以及日韩汉字等。
Unicode
字符集
:
Unicode
编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国
码。
它最多使用
4
个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,
UTF-8
、
UTF-16
和
UTF-
32
。最为常用的
UTF-8
编码。
UTF-8
编码,可以用来表示
Unicode
标准中任何字符,它是电子邮件、网页及其他存储或传送文字的应用
中,优先采用的编码。互联网工程工作小组(
IETF
)要求所有互联网协议都必须支持
UTF-8
编码。所以,
我们开发
Web
应用,也要使用
UTF-8
编码。它使用一至四个字节为每个字符编码,编码规则:
1. 128
个
US-ASCII
字符,只需一个字节编码。
2.
拉丁文等字符,需要二个字节编码。
3.
大部分常用字(含中文),使用三个字节编码。
4.
其他极少使用的
Unicode
辅助字符,使用四字节编码。
2.2
编码引出的问题
在
IDEA
中,使用
FileReader
读取项目中的文本文件。由于
IDEA
的设置,都是默认的
UTF
-
8
编码,所以没有任何
问题。但是,当读取
Windows
系统中创建的文本文件时,由于
Windows
系统的默认是
GBK
编码,就会出现乱码。
FileReader fileReader
=
new
FileReader
(
"D:\\a.txt"
);
int
read
;
while
((
read
=
fileReader
.
read
())
!= -
1
) {
System
.
out
.
print
((
char
)
read
);
}
fileReader
.
close
()
输出结果:
���
2.3 InputStreamReader
类
转换流
java.io.InputStreamReader
,是
Reader
的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定
的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
InputStreamReader(InputStream in)
:
创建一个使用默认字符集的字符流。
InputStreamReader(InputStream in, String charsetName)
:
创建一个指定字符集的字符流。
构造举例,代码如下
InputStreamReader isr1
=
new
InputStreamReader
(
new
FileInputStream
(
"a.txt"
));
InputStreamReader isr2
=
new
InputStreamReader
(
new
FileInputStream
(
"a.txt"
) ,
"GBK"
);
指定编码读取
//
定义文件路径
,
文件为
gbk
编码
String
FileName
=
"D:\\a.txt"
;
//
创建流对象
,
默认
UTF8
编码
InputStreamReader isr1
=
new
InputStreamReader
(
new
FileInputStream
(
FileName
));
//
创建流对象
,
指定
GBK
编码
InputStreamReader isr2
=
new
InputStreamReader
(
new
FileInputStream
(
FileName
) ,
"GBK"
);
//
定义变量
,
保存字符
int
read
;
//
使用默认编码字符流读取
,
乱码
while
((
read
=
isr1
.
read
())
!= -
1
) {
System
.
out
.
print
((
char
)
read
);
//
��
�
}
isr1
.
close
();
//
使用指定编码字符流读取
,
正常解析
while
((
read
=
isr2
.
read
())
!= -
1
) {
System
.
out
.
print
((
char
)
read
);
//
你好
}
isr2
.
close
();
2.4 OutputStreamWriter
类
转换流
java.io.OutputStreamWriter
,是
Writer
的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符
编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法
OutputStreamWriter(OutputStream in)
:
创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in, String charsetName)
:
创建一个指定字符集的字符流。
构造举例,代码如下:
OutputStreamWriter isr
=
new
OutputStreamWriter
(
new
FileOutputStream
(
"a.txt"
));
OutputStreamWriter isr2
=
new
OutputStreamWriter
(
new
FileOutputStream
(
"a.txt"
) ,
"GBK"
);
指定编码写出
//
定义文件路径
String
FileName
=
"D:\\a.txt"
;
//
创建流对象
,
默认
UTF8
编码
OutputStreamWriter osw
=
new
OutputStreamWriter
(
new
FileOutputStream
(
FileName
));
//
写出数据
osw
.
write
(
"
你好
"
);
//
保存为
6
个字节
osw
.
close
();
//
定义文件路径
String
FileName2
=
"D:\\b.txt"
;
//
创建流对象
,
指定
GBK
编码
OutputStreamWriter osw2
=
new
OutputStreamWriter
(
new
FileOutputStream
(
FileName2
),
"GBK"
);
//
写出数据
osw2
.
write
(
"
你好
"
);
//
保存为
4
个字节
osw2
.
close
();
转换流理解图解
第三章 序列化
Java
提供了一种对象
序列化
的机制。用一个字节序列可以表示一个对象,该字节序列包含该
对象的数据
、
对象的
类型
和
对象中存储的属性
等信息。字节序列写出到文件之后,相当于文件中
持久保存
了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行
反序列化
。
对象的数据
、
对象的类型
和
对象中
存储的数据
信息,都可以用来在内存中创建对象。
3.1 ObjectOutputStream
类
java.io.ObjectOutputStream
类,将
Java
对象的原始数据类型写出到文件
,
实现对象的持久存储。
构造方法
public ObjectOutputStream(OutputStream out)
: 创建一个指定
OutputStream
的
ObjectOutputStream
。
构造举例,代码如下:
FileOutputStream fileOut
=
new
FileOutputStream
(
"a.txt"
);
ObjectOutputStream out
=
new
ObjectOutputStream
(
fileOut
);
序列化操作
1.
一个对象要想序列化,必须满足两个条件
:
该类必须实现
java.io.Serializable
接口,
Serializable
是一个标记接口,不实现此接口的类将不会使任
何状态序列化或反序列化,会抛出
NotSerializableException
。
该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰。
public class
Employee
implements
Serializable
{
public
String
name
;
public
String
address
;
public transient
int
age
;
// transient
瞬态修饰成员
,
不会被序列化
public
void
addressCheck
() {
System
.
out
.
println
(
"Address check : "
+
name
+
" -- "
+
address
);
}
}
2.
写出对象方法
public final void writeObject (Object obj)
:
将指定的对象写出。
public class
SerializeDemo
{
public static
void
main
(
String
[]
args
) {
Employee e
=
new
Employee
();
e
.
name
=
"lisi"
e
.
address
=
"renminlu"
;
e
.
age
=
20
;
try
{
//
创建序列化流对象
ObjectOutputStream out
=
new
ObjectOutputStream
(
new
FileOutputStream
(
"employee.txt"
));
//
写出对象
out
.
writeObject
(
e
);
//
释放资源
out
.
close
();
fileOut
.
close
();
System
.
out
.
println
(
"Serialized data is saved"
);
//
姓名,地址被序列化,年龄没有被序列
化。
}
catch
(
IOException i
) {
i
.
printStackTrace
();
}
}
3.3 ObjectInputStream
类
ObjectInputStream
反序列化流,将之前使用
ObjectOutputStream
序列化的原始数据恢复为对象。
构造方法
public ObjectInputStream(InputStream in)
: 创建一个指定
InputStream
的
ObjectInputStream
。
反序列化操作
1
如果能找到一个对象的
class
文件,我们可以进行反序列化操作,调用
ObjectInputStream
读取对象的方法:
public final Object readObject ()
:
读取一个对象。
Employee e
=
null
;
try
{
//
创建反序列化流
FileInputStream fileIn
=
new
FileInputStream
(
"employee.txt"
);
ObjectInputStream in
=
new
ObjectInputStream
(
fileIn
);
//
读取一个对象
e
=
(
Employee
)
in
.
readObject
();
//
释放资源
in
.
close
();
fileIn
.
close
();
}
catch
(
IOException i
) {
//
捕获其他异常
i
.
printStackTrace
();
return
;
}
catch
(
ClassNotFoundException c
) {
//
捕获类找不到异常
System
.
out
.
println
(
"Employee class not found"
);
c
.
printStackTrace
();
return
;
}
//
无异常
,
直接打印输出
System
.
out
.
println
(
"Name: "
+
e
.
name
);
System
.
out
.
println
(
"Address: "
+
e
.
address
);
System
.
out
.
println
(
"age: "
+
e
.
age
);
}
对于
JVM
可以反序列化对象,它必须是能够找到
class
文件的类。如果找不到该类的
class
文件,则抛出一个
ClassNotFoundException
异常。
反序列化操作
2
另外,当
JVM
反序列化对象时,能找到
class
文件,但是
class
文件在序列化对象之后发生了修改,那么反序列化操
作也会失败,抛出一个
InvalidClassException
异常。
发生这个异常的原因如下:
该类的序列版本号与从流中读取的类描述符的版本号不匹配
该类包含未知数据类型
该类没有可访问的无参数构造方法
Serializable
接口给需要序列化的类,提供了一个序列版本号。
serialVersionUID
该版本号的目的在于验证序
列化的对象和对应类是否版本匹配。
public class
Employee
implements
Serializable
{
//
加入序列版本号
private static final
long
serialVersionUID
=
1L
;
public
String
name
;
public
String
address
;
//
添加新的属性
,
重新编译
,
可以反序列化
,
该属性赋为默认值
.
public
int
eid
;
public
void
addressCheck
() {
System
.
out
.
println
(
"Address check : "
+
name
+
" -- "
+
address
);
}
}
3.4
序列化集合
1.
将存有多个自定义对象的集合序列化操作,保存到
list.txt
文件中。
2.
反序列化
list.txt
,并遍历集合,打印对象信息。
案例分析
1.
把若干学生对象 ,保存到集合中。
2.
把集合序列化。
3.
反序列化读取时,只需要读取一次,转换为集合类型。
4.
遍历集合,可以打印所有的学生信息
案例实现
public class
SerTest
{
public static
void
main
(
String
[]
args
)
throws
Exception
{
//
创建 学生对象
Student student
=
new
Student
(
"
老王
"
,
"laow"
);
Student student2
=
new
Student
(
"
老张
"
,
"laoz"
);
Student student3
=
new
Student
(
"
老李
"
,
"laol"
);
ArrayList
<
Student
>
arrayList
=
new
ArrayList
<>
();
arrayList
.
add
(
student
);
arrayList
.
add
(
student2
);
arrayList
.
add
(
student3
);
//
序列化操作
// serializ(arrayList);
//
反序列化
ObjectInputStream ois
=
new
ObjectInputStream
(
new
FileInputStream
(
"list.txt"
));
//
读取对象
,
强转为
ArrayList
类型
ArrayList
<
Student
>
list
=
(
ArrayList
<
Student
>
)
ois
.
readObject
();
for
(
int
i
=
0
;
i
<
list
.
size
();
i
++
){
Student s
=
list
.
get
(
i
);
System
.
out
.
println
(
s
.
getName
()
+
"--"
+
s
.
getPwd
());
}
}
private static
void
serializ
(
ArrayList
<
Student
>
arrayList
)
throws
Exception
{
//
创建 序列化流
ObjectOutputStream oos
=
new
ObjectOutputStream
(
new
FileOutputStream
(
"list.txt"
));
//
写出对象
oos
.
writeObject
(
arrayList
);
//
释放资源
oos
.
close
();
}
}
第四章 打印流
平时我们在控制台打印输出,是调用
print
方法和
println
方法完成的,这两个方法都来自于
java.io.PrintStream
类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
4.1 PrintStream
类
public PrintStream(String fileName)
: 使用指定的文件名创建一个新的打印流。
构造举例,代码如下:
PrintStream ps
=
new
PrintStream
(
"ps.txt"
)
;
System.out
就是
PrintStream
类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,
我们就可以
将数据输出到指定文本文件中。
//
调用系统的打印流
,
控制台直接输出
99
System
.
out
.
println
(
97
);
//
创建打印流
,
指定文件的名称
PrintStream ps
=
new
PrintStream
(
"ps.txt"
);
//
设置系统的打印流流向
,
输出到
ps.txt
System
.
setOut
(
ps
);
//
调用系统的打印流
,ps.txt
中输出
99
System
.
out
.
println
(
99
);