对象的序列化(对象要实现Serializable序列化接口,类似实现comparable )
1.操作对象的流:ObjectOutputStream ObjectInputStream
ObjectOutputStream :
将 Java对象的基本数据类型和图形写入 OutputStream。
通过在流中使用文件可以实现对象的持久存储。
构造函数:ObjectOutputStream(OutputStream
out)//初始化时要有目的
方法:
writeObject(Object
obj);Object对象要实现Serializable接口。该接口无任何方法,被称为标记
ObjectInputStream
:将ObjectOutputStream写入的基本数据和对象进行反序列化。
构造函数:
ObjectInputStream(InputStream in)
;指定源
Object】 readObject() ;读取类,返回对象。
注意:
对象是字节码文件。
读写object的对象必须是同一个对象。序列号相等(序列号可以自定义,
如:static final long serialVersionUID =
42L)。
静态是不能被序列化存储的,因为静态是在方法区,序列化只是将对内存的序列化。
transient关键字,可以保证被修饰的变量只在堆中,不被序列化存储。
//写
File fi = new File("2101.txt");
OutputStream os = new FileOutputStream(fi);//对象不是纯文本,是字节码文件
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(new person("lisi",20,"cn"));//是new
person()//假设静态cn被改了,方法区静态变量会变吗 会
//读
File fi = new File("2101.txt");
FileInputStream fis = new FileInputStream(fi);
ObjectInputStream ois = new ObjectInputStream(fis);
person p = (person)ois.readObject();
2.管道流:涉及多线程的IO流对象。(四个流对象都有,inputStream,outputStream,reader,writer)
通常读取流和写入流之间是没有关系的,只有存在中转站的情况下,才存在联系。
如:写入流写到数组中,读取流从数组读取。但是管道流可以实现不需要中转站,
将读取流和写入流的管道接在一起,类似一根管道,一端读,一端写。
但是会出问题:先读呢,还是先写?
我们会用两个现场来操作,通常,数据由某个线程写入
PipedOutputStream 对象,并由其他线程从连接的PipedInputStream读取。
不建议对这两个对象尝试使用单个线程,因为这样可能会造成该线程死锁。因为read方法是堵塞时方法,如果读不到数据,就等待。
与合并流(sequenceInputStream)的区别:合并流只有一个sequenceInputStream,是将多个输入流合并到一起。管道流是将输入流和输出流连接到一起,
多线程的实现:没写数据之前,即使读到也等待,直到读到数据。
构造方法:
PipedOutputStream(PipedInputStream
snk)
创建连接到指定管道输入流的管道输出流。
也可以这样
PipedOutputStream()
创建尚未连接到管道输入流的管道输出流。
用connect(PipedInputStream snk) 连接两个流
将此管道输出流连接到接收者。
ObjectInputStream(InputStream in)
//制定读取的文件
创建从指定 InputStream 读取的
ObjectInputStream。
常用方法:只能读取和写入字节或字节数组。
class writedemo implements Runnable{
private PipedInputStream pis;
writedemo(PipedInputStream
pis){//InputStream不可以放在这,否则this.InputStream=PipedInputStream;
this.pis=pis;
}
public void
run(){
byte[] bys = new byte[1024];
int len = 0;
while ((len=pis.read(bys))!=-1) {
//没有数据,线程等待。pipedoutputStream,写入,若再夺得线程,读取。
Thread.sleep(3000);
String str = new
String(bys,0,len);//获取数组。
System.out.println(str);
}
pis.close();
}
class readdemo implements Runnable{
PipedOutputStream pos;
readdemo(PipedOutputStream pos){
this.pos= pos;
}
public void
run(){
pos.write("管道来2122了".getBytes());
}
}
class day2102{
public static void main(String[] args) throws Exception {
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
pis.connect(pos);
writedemo wd = new writedemo(pis);
readdemo rd = new readdemo(pos);
Thread t1 = new Thread(wd);
Thread t2 = new Thread(rd);
t1.start();
t2.start();
}
}
3.随机访问文件 RandomAccessFile 不是IO子类
功能:此类的实例支持对随机访问文件的读取和写入。可以用seek()方法来访问数据,并进行读写了。
这些数据的大小不必相同;但是其大小和位置必须是可知的
但它是IO包中的成员,因为它具备读写功能。部封装了一个byte数组,而且通过指针对数组的元素进行操作。
可以通过getFilePointer获取真正位置,同时可以通过seek改变指针的位置。
原理:内部封装了字节输入流和字节输出流。
构造函数:
RandomAccessFile(File file, String mode)
创建从中读取和向其中写入(可选)的随机访问文件流,也可以指定文件名称。
局限:通过构造函数可以发现,该类只能操作文件。而且操作文件还有模式Mode:r,只读 rw,读写等
注意:
1.如果模式为只读r,不会创建文件,只会去读取一个已经存在的文件,
如果该文件不存在,则会出现异常
2.如果模式为rw,操作的文件不存咋,会自动创建。如果有则【不会覆盖】。
用处:可以通过多线程实现数据的分段写入。类似迅雷。通过分段规律实现多线程输入。
IO中唯一一个。数据分段,有规律。
常见方法:
int
readInt(),readDouble(),readLong().....
writeInt(int
v),write(long)......
特殊方法:
readLine()
void seek(long pos)
设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
skipBytes(int n) //只能往后走,不能回头
尝试跳过输入的 n
个字节以丢弃跳过的字节。
RandomAccessFile ra1 = new RandomAccessFile("2014.txt","rw");
//ra1.seek(8*2);//跳过8*2个字节
//ra1.write("lisi".getBytes());
//ra1.writeInt(97);//write()存储低八位
//ra1.writeInt(97);//存入4个8位
ra1.skipBytes(8*2);
String s =ra1.readLine();
System.out.println(s);
4.可以操作基本数据类型的流对象:DataOutputStream, DataInputStream 。
数据输出流将基本 Java 数据类型写入输出流中
特点:通常数据类型输出流会按照一定的格式将数据输出,必须按指定好的格式保存才可以用输入流读取进来。
DataOutput 接口、DataInput 接口,这两个接口的操作彼此对应
构造函数:
DataOutputStream(OutputStream out)
writeUTF(String str) //只能以dataInputStream读取。转换流不可以。String :要写入的字符串。
以与机器无关方式使用 UTF-8
修改版编码将一个字符串写入基础输出流。
static 【String】 readUTF()//utf修改版只能由它来读。
DataOutputStream dos = new
DataOutputStream(new FileOutputStream("2104.txt"));
dos.writeBoolean(true);
//
dos.writeBytes("lisi");
//
dos.writeChars("zhansan");
dos.writeInt(20);
dos.close();
DataInputStream(InputStream in)
使用指定的底层 InputStream
创建一个 DataInputStream。
boolean readBoolean()
参见 DataInput 的
readBoolean 方法的常规协定。
byte readByte() char readChar() int readInt() double readDouble()
:分别读取8,16,32,64个字节
DataInputStream dis = new
DataInputStream(new FileInputStream("2104.txt"));
Boolean b = dis.readBoolean();
// byte s
= dis.readByte();
//数据读反了,乱码。
// Byte s1 =
// char b2
= dis.readChar();
int s = dis.readInt();
dis.close();
System.out.println(b+".."+s);
特有编码
DataOutputStream dos = new DataOutputStream(new
FileOutputStream("21041.txt"));
dos.writeUTF("你好");
DataInputStream dis = new DataInputStream(new FileInputStream("21041.txt"));
String str = dis.readUTF();//返回值
// System.out.println(str);
dos.close();
(一) ByteArrayInputStream
操作字节数组:该流没有调用底层资 源,流关闭无效,不产生IOExcetion
字节数组(基本流的数组?)--->字节数组(字节数组流)--->
构造方法:ByteArrayInputStream(byte[] buf)
在构造的时候,需要接受数据源,而且数据源是字节数组。
ByteArrayInputStream:
包含一个内部缓冲区,该缓冲区包含从流中读取的字节
2.ByteArrayOutputStream:该输出流中,数据被写入一个缓冲区数组,该数组随数据的写入自动增加。
构造方法:ByteArrayOutputStream() 创建一个新的 byte 数组输出流。
在构造的时候,不用定义目的,内部数组就是目的。
输出流特有方法:
int size()
返回缓冲区的当前大小。
byte[] toByteArray()
创建一个新分配的 byte 数组。
String toString()
使用平台默认的字符集,通过解码字节将缓冲区内容转换为字符串。
String toString(String charsetName)
使用指定的
charsetName,通过解码字节将缓冲区内容转换为字符串。
void writeTo(OutputStream out):将数组流中的全部内容写到字节输出流中(文件等)
例如:bos.writeTo(new
FileOutputStream("2104.txt"))//此方法涉及异常。
特点:没有调用底层资源(windows?),不用关闭流,也不会产生IOException
内部封装了数组,它是由字节流到字节流,始终都在内存中操作。
作用:将一个文件读到内存中,先用数组存放起来。而且长度是可变的。
源设备:
键盘(System.in),硬盘(FileStream),内存(ArrayStream)
目的设备:
控制台(System.out),硬盘(FileStream),内存(ArrayStream)
用处:将一个文件,读到内存,并放在数组先存取来。而且,数组长度可变, ArrayStream很方便。
对数组元素的操作:只有设置(设置值,改变)和获取。对应于流中就是写和读。
这就是用流读写的思想操作数组,以后遇到数组,可以封装的该流中。
(二)字符数组流和字符串流。CharArrayReader
CharArrayReader,CharArrayWriter 实现一个可用作字符输入流的字符缓冲区
CharArrayReader(char[] buf)
根据指定的 char 数组创建一个
CharArrayReader。
ByteArrayOutputStream()
创建一个新的 byte 数组输出流。
StringReader,
构造方法:StringReader(String s) 创建一个新字符串
reader。
StringWriter()
使用默认初始字符串缓冲区大小创建一个新字符串
writer。
import java.io.*;
class day2105 {
public static void
arraydemo(){
//数据源
byte[] bys = "adfdfdffa".getBytes()
;
ByteArrayInputStream bis = new ByteArrayInputStream(bys);
//数据目的
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//输入流:读取数据,读入流中,已经在流中的缓冲区,输入输出在管道的两头。
int len =0;
//本身具备数组,可以一个一个接受,read()方法一个一个读取,返回的是读取的字符。·
while ((len =bis.read())!=-1){
bos.write(len);//表示将这个字符写入缓冲区中。
}
// bis.close();
不用关闭,不用抛
System.out.println(bos.toString());
System.out.println(bos.size());
}
public static void main(String[]
args){
arraydemo();
}
}
/*必须懂
InputStreamReader,OutputStream转换流中加入了编码
PrintStream,printWriter也有,但是只能用于打印。
ASCII;美国标准信息交换吗 ,用一个字节7位
iso-8859-1:欧洲码表,用一个字节8位
GB2312:中国的中文编码
GBK:中文编码表升级版。2个字节 两个高位是1
Unicode:国际标准码,所有文字用两个字节。java语言使用的就是Unicode
UTF-8:最多用三个字节表示一个字节,一个字节可以表示,两个字节也可以
表示,三个也可以。
字符编码:以转换流为主
*
printStream和PrintWriter也涉及编码表,但是只用于打印。
* "你好"以gbk的编码表的存储。存的是字节。例如:你
查表对应"-45 -56",好 查表"-48 -89".存储的形式-45 -56 -48 -89
* 读取的时候:读的是数字:-45 -56
-48 -89在GBK表中对应的是"你好"。但是如果使用的UTF-8读取时,-45 -56 -48一组,对应的是
* ?,而-48对应的也是?,都是未知字符。
* 编码:变成字节数字 字符串变成字节数组
* 解码: 变成字符串。 字节数组变成字符串
* String-->byte[]
.getBytes()
* byte[]-->String
new String(buf)
/**UTF-8修改版标示头。UTF-8怎么决定使用一个字节,两个字节,三个字节。
* 单个字节:头位:0
* 两个字节表示:第一个字节头位:1 1 0
*
第二个字节头位:1 0
* 第三个字节表示:第一个字节头位:1 1 1 0
*
第二个字节头位:1 1 0
*
第三个字节: 1 0
//联通使用Gbk编码,但是其二进制形式是U8表的表现形式。就去查U8的表。
//[-63, -86, -51, -88]
// 11000001
// 10101010
// 11001101
// 10101000
//看表现形式可以发现,它符合两个字节表示的情况,而在u8中,汉字是以三个字节表示。所以出错。
练习:
* 有五个学生,每个学生有3门功课的成绩,
从键盘输入以上数据(包括姓名,三门成绩)
输入格式:如:zhangsan,50,54,54,计算出总成绩。
并把学生的信息和计算出的总分数按高低顺序存放在
磁盘文件“stud.txt”中。
黑马程序员-常见的一些流和编码表