1,对象的序列化
1.1,什么叫序列化?什么叫反序列化?
序列化:将对象的状态信息转换为可以存储或传输的形式的过程。只能序列化堆内存中的数据,无法序列化其他
反序列化:它将流转换为对象
1.2,对象序列化的目的?
1、以某种存储形式使自定义
对象持久化;
2、将对象从一个地方传递到另一个地方。
3、使程序更具维护性。
1.3,读取
ObjectOutputStream;将 Java 对象的基本数据类型和图形写入 OutputStream
---------只能将支持 java.io.Serializable 接口的对象写入流中
ObjectInputStream:
Serializable接口没有任何抽象方法,这种没有抽象发发的接口通常叫做标记接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import
java.io.*;
class
ObjectStreamDemo {
public
static
void
main(String[] args)
throws
Exception{
//writeObj();
readObj();
}
//从文件把序列化的对象读出来
public
static
void
readObj()
throws
Exception{
ObjectInputStream ois =
new
ObjectInputStream(
new
FileInputStream(
"obj.txt"
));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
//将对象序列化,存入文件
//通常同文件存成person.object
public
static
void
writeObj()
throws
IOException{
ObjectOutputStream oos =
new
ObjectOutputStream(
new
FileOutputStream(
"obj.txt"
));
oos.writeObject(
new
Person(
"lisi0"
,
399
,
"kr"
));
oos.close();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
import
java.io.*;
class
Person
implements
Serializable{
//给类定义一个固定标识,
//如果未定义,接口会根据类成员自己算一个
public
static
final
long
serialVersionUID = 42L;
private
String name;
//非静态也不希望序列化,用此关键字,
//保证其值在对堆内存中存在而不再文本文件中存在
transient
int
age;
static
String country =
"cn"
;
//静态不能被序列化,静态在方法区内
Person(String name,
int
age,String country){
this
.name = name;
this
.age = age;
this
.country = country;
}
public
String toString(){
return
name+
":"
+age+
":"
+country;
}
}
|
注意:1,对象的静态成员不能被序列化
2,被关键字
transient
修饰的不能序列化
3,序列化的类要实现接口Serializable
4,序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,
该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。
2,管道流
PipedInputStream
PipedOutputStream--------输入输出可以直接连接,通过结合线程使用
通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。
不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程
代码演示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
import
java.io.*;
class
Read
implements
Runnable{
private
PipedInputStream in;
Read(PipedInputStream in){
this
.in = in;
}
public
void
run(){
try
{
byte
[] buf =
new
byte
[
1024
];
System.out.println(
"读取前。。没有数据阻塞"
);
int
len = in.read(buf);
System.out.println(
"读到数据。。阻塞结束"
);
String s=
new
String(buf,
0
,len);
System.out.println(s);
in.close();
}
catch
(IOException e){
throw
new
RuntimeException(
"管道读取流失败"
);
}
}
}
class
Write
implements
Runnable{
private
PipedOutputStream out;
Write(PipedOutputStream out){
this
.out = out;
}
public
void
run(){
try
{
System.out.println(
"开始写入数据,等待6秒后。"
);
Thread.sleep(
6000
);
out.write(
"piped lai la"
.getBytes());
out.close();
}
catch
(Exception e){
throw
new
RuntimeException(
"管道输出流失败"
);
}
}
}
class
PipedStreamDemo{
public
static
void
main(String[] args)
throws
IOException{
PipedInputStream in =
new
PipedInputStream();
PipedOutputStream out =
new
PipedOutputStream();
in.connect(out);
Read r =
new
Read(in);
Write w =
new
Write(out);
new
Thread(r).start();
new
Thread(w).start();
}
}
|
3,RandomAccessFile------------直接父类为Object
----随机访问文件,自身具备读写的方法
----通过skipBytes(int x),seek(int x)来达到随机访问
该类不是算是IO体系中子类。
而是直接继承自Object。
但是它是IO包中成员。因为它具备读和写功能。
内部封装了一个数组,而且通过指针对数组的元素进行操作。
可以通过getFilePointer获取指针位置, 同时可以通过seek改变指针的位置。
完成读写的原理:------------就是内部封装了字节输入流和输出流。
RandomAccessFile(File file, String mode) ;
RandomAccessFile(String name, String mode)
通过构造函数可以看出,该类只能操作文件。
而且操作文件还有模式:只读r,,读写rw等。
模式----- r>>>不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。
模式-----rw>>>操作的文件不存在,会自动创建。如果存则不会覆盖。
写代码:
1
2
3
4
5
6
|
RandomAccessFile raf =
new
RandomAccessFile(
"ran.txt"
,
"rw"
);
//读写模式
raf.write(
"李四"
.getBytes());
raf.writeInt(
97
);
//write只写数的低8位,当大于255时,数据丢失
raf.write(
"王五"
.getBytes());
raf.writeInt(
99
);
raf.close();
|
1
2
3
4
5
6
7
8
9
|
public
static
void
writeFile_2()
throws
IOException
{
RandomAccessFile raf =
new
RandomAccessFile(
"ran.txt"
,
"rw"
);
raf.seek(
8
*
0
);
//能够实现分段写入
raf.write(
"周期"
.getBytes());
raf.writeInt(
103
);
raf.close();
}
|
注:它可以实现数据的分段写入,可以用不同线程同时写入不同段!他们直接不会互相冲突--------下载软件的原理
第一个线程0----10
第二个线程11----23
第三个线程24-----45........
其他流多线程就会造成数据虽然写入,但是不是连续的,解码出现错误
读代码:
skipBytes(int x);//只能往前跳
seek(int x);调整对象中指针
seek(8*倍数);//数据分段,因此希望数据有规律
//遇到假如name太长的情况就统一让nam按16个字节存!!年龄按4个字节seek(20*倍数)
1
2
3
4
5
6
7
8
9
10
11
|
RandomAccessFile raf =
new
RandomAccessFile(
"ran.txt"
,
"r"
);
//调整对象中指针。
//raf.seek(8*1);
//跳过指定的字节数
raf.skipBytes(
8
);
byte
[] buf =
new
byte
[
4
];
raf.read(buf);
String name =
new
String(buf);
int
age = raf.readInt();
System.out.println(
"name="
+name);
System.out.println(
"age="
+age);
|
4.DataStream操作基本数据类型
DataInputStream
DataOutputStream
----------------------可以用于操作基本数据类型的数据的流对象。
构造函数-----
DataOutputStream(OutputStream out);
DataInputStream(InputStream in) ;
writeUTF(String str) ;//以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流。
readUTF();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
DataInputStream dis =
new
DataInputStream(
new
FileInputStream(
"data.txt"
));
DataOutputStream dos =
new
DataOutputStream(
new
FileOutputStream(
"data.txt"
));
//平时要想写入带编码的数据
OutputStreamWriter osw =
new
OutputStreamWriter(
new
FileOutputStream(
"gbk.txt"
),
"gbk"
);
osw.write(
"你好"
);
//GBK-----写入后是4个字节的大小
OutputStreamWriter osw =
new
OutputStreamWriter(
new
FileOutputStream(
"gbk.txt"
),
"utf-8"
);
osw.write(
"你好"
);
//"utf--8"---写入后是6个字节的大小
//修改版的UTF-8
DataOutputStream dos =
new
DataOutputStream(
new
FileOutputStream(
"utfdate.txt"
));
dos.writeUTF(
"你好"
);
//utf-8-------修改版写入后是8个字节的大小
DataInputStream dis =
new
DataInputStream(
new
FileInputStream(
"utf.txt"
));
String s = dis.readUTF();
System.out.println(s);
|
注:一个汉字gbk是2个字节,utf-8是3个字节
5.ByteArrayStream
用于操作字节数组的流对象。
ByteArrayInputStream :在构造的时候,需要接收数据源,。而且数据源是一个字节数组。
ByteArrayOutputStream: 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。
这就是数据目的地。
因为这两个流对象都操作的数组,并没有使用系统资源。
所以,不用进行close关闭。
在流操作规律讲解时:
源设备,
键盘 System.in,硬盘 FileStream,内存 ArrayStream。
目的设备:
控制台 System.out,硬盘FileStream,内存 ArrayStream。
1
2
3
4
5
6
7
8
|
//数据源。
ByteArrayInputStream bis =
new
ByteArrayInputStream(
"ABCDEFD"
.getBytes());
//数据目的
ByteArrayOutputStream bos =
new
ByteArrayOutputStream();
int
by =
0
;
while
((by=bis.read())!=-
1
){
bos.write(by);
}
|
方法writeTo(Outputstream out);
1
|
bos.writeTo(
new
FileOutputStream(
"a.txt"
));
|
CharStream类
StringStream类
6字符编码
字符流的出现时为了方便操作字符数据,方便的原因是内部加入了编码表。
OutputStreamReader-----两者构造时可以加入字符集编码表
其实还有2个类可以涉及到编码表,PrintWriter(File file, String csn);//csn字符集,编码-----但是只能打印时
编码表的由来:计算机只识别二进制数据,早期是电信号
为了方便应用计算机,让它可以识别各个国家的文字
就将各个国家的文字用数字来表示,并一一对应,形成一张表-----编码表
1
2
3
4
5
6
7
|
ASCII:美国标准信息交换码,用一个字节的
7
位表示
ISO8859-1
:拉丁码表,欧美码表,用一个字节的
8
位表示
GB2312:中国的中文编码表,
2 个字节
GBK:中国的中文编码表升级,永和了更多的中文文字符号
Unicode:国际标准码,融合了多种文字
所有的文字都用 2 个字节表示,java语言使用的就是Unicode
UTF-8:最多用
3
个字节表示一个字符
|
编码:字符串变成字节数组。
解码:字节数组变成字符串。
String-->byte[]; str.getBytes(charsetName);
byte[] -->String: new String(byte[],charsetName);
第一种情况:
编码是错误的无解
1
2
|
String s =
"你好"
;
byte
[] b1 = s.getBytes(
"ISO8859-1"
);//ISO不识别中文
|
第二种情况:
编码对,解码错!
1
2
3
4
5
6
7
8
9
10
11
|
//编码。
String s =
"你好"
;
byte
[] b1 = s.getBytes(
"GBK"
);
System.out.println(Arrays.toString(b1));
//解码,失败
String s1 =
new
String(b1,
"ISO8859-1"
);
System.out.println(
"s1="
+s1);
//解码错误的情况下,反着编码回去解码
byte
[] b2 = s1.getBytes(
"ISO8859-1"
);
//对s1进行iso8859-1编码。
String s2 =
new
String(b2,
"GBK"
);
System.out.println(
"s2="
+s2);
|
第三种情况:gbk<-->utf-8时,因为2者都识别中文,导致反编码时出错。
1
2
3
4
5
6
7
8
9
10
11
|
//编码。
String s =
"你好"
;
byte
[] b1 = s.getBytes(
"GBK"
);
System.out.println(Arrays.toString(b1));
//解码,失败
String s1 =
new
String(b1,
"utf-8"
);
System.out.println(
"s1="
+s1);
//解码错误的情况下,反着编码回去解码
byte
[] b2 = s1.getBytes(
"utf-8"
);
//对s1进行iso8859-1编码。
String s2 =
new
String(b2,
"GBK"
);
System.out.println(
"s2="
+s2);
|
第四种情况:联通<---->▉▉的情况
1
2
3
4
5
6
7
8
9
10
11
|
String s =
"联通"
;
byte
[] by = s.getBytes(
"gbk"
);
for
(
byte
b : by){
System.out.println(Integer.toBinaryString(b&
255
));
}
//打印结果:
11000100
11100011
10111010
11000011
//utf-8有标志头0,110,10
|
乱码的由来,
1是因为在编码和解码的过程用的是不同的编码表而造成的。
2也有的是在文件数据的输入输出时,造成字节的获取不足。
例如,当我们用以byte[] buf=new byte[5];长度为5的字节数组
,
来作为缓冲存取以段数据“你们好啊”并打印,存取到“好”这个字我们才存到啦半个。
也会出现乱码。
3说个特例,“联通”这个词的,二进制表示时,即符合GBK码表也符合UTF-8码表。所以也会出现乱码。