过滤流类型
过滤流就是在节点流的基础上附加功能
过滤流
FilterInputStream/FilterOutputStream和FilterReader/FilterWriter
public class FilterInputStream extends InputStream { //典型的装饰模式
protected volatile InputStream in; //被装饰目标
protected FilterInputStream(InputStream in) { //通过构造器组装被装饰对象
this.in = in;
}
public int read() throws IOException {//调用Filter中的read方法时实际操作是由被装饰对象实现
的
return in.read();
}
}
所谓的过滤流实际上就是类似上面的加密处理,在输入之后(后置处理,被装饰对象先执行)或者输出之前(前置处理,先处理然后被装饰对象执行)进行一下额外的处理,最终实际操作是调用被装饰对象的方法完成工作,依靠这种装饰模式实现在节点流的基础上附加额外功能.当然也允许多个过滤流嵌套从而达到功能累加的目的
FilterInputStream实际上就是一个装饰抽象角色
自定义流实现循环13加密:
读取数据不变:FileReader—BufferedReader
写出数据自定义过滤流SecurityWriter(FilterWriter)
public class SecurityWriter extends FilterWriter {
protected SecurityWriter(Writer out) {
super(out);
}
public void write(int c) throws IOException {
if (c >= 'a' && c <= 'z') {
c = (c - 'a' + 13) % 26 + 'a';
} else if (c >= 'A' && c <= 'Z') {
c = (c - 'A' + 13) % 26 + 'A';
}
super.write(c);
}
}
public class SecurityReader extends FilterReader {
protected SecurityReader(Reader in) {
super(in);
}
public int read() throws IOException {
int c = super.read();
if (c >= 'a' && c <= 'z') {
c = (c - 'a' + 13) % 26 + 'a';
} else if (c >= 'A' && c <= 'Z') {
c = (c - 'A' + 13) % 26 + 'A';
}
return c;
}
}
桥接转换流
InputStreamReader和OutputStreamWriter提供了字节流和字符流之间的桥接转换功能,用于与字节数据到字符数据之间的转换,无需编程实现将字节拼接为字符
转换流可以在构造时指定其编码字符集
- InputStreamReader用于将一个InputStream类型的输入流自动转换为Reader字符流
- OutputStreamWriter用于将一个Writer字符输出流转换OutputStream字节输出流
InputStreamReader构造器
- InputStreamReader(InputStream)
- InputStreamReader(InputStream, String)
- InputStreamReader(InputStream, Charset)
- InputStreamReader(InputStream, CharsetDecorder)
Reader r=new InputStreamReader(System.in);
int kk=r.read(); //例如输入的是“中国”,这里实际读取的是"中"
//因为这里读取的是一个字节,所以输入"中国",实际读取的是"中"的一个字节,输出显示为?
kk=System.in.read();
System.out.println("输入的是:"+(char)kk);
InputSteram is=new InputStreamReader(System.in,”iso8859-1”);
Reader r=new InputStreamReader(System.in, "gbk");
int kk=r.read(); //例如输入的是"中国",实际读取的是"中"
System.out.println("输入的是:"+(char)kk);
注意:一般不建议自行设置编码字符集,除非是必须的
缓冲流
缓冲流是套接在响应的节点流之上,对续写的数据提供缓冲的功能,提高读写的效率,同时增加了一些新方法
以介质是硬盘为例,字节流和字符流的弊端:在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。为了解决以上弊端,采用缓存流。
缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。
构造方法
- BufferedReader(Reader)不定义缓存大小,默认8192
- BufferedReader(Reader in, int size)size为自定义缓冲区的大小
- BufferedWriter(Writer)
- BufferedWriter(Writer out, int size)size为自定义缓冲区的大小
- BufferedInputStream(InputStream)
- BufferedInputStream(InputStream in, int size)size为自定义缓冲区的大小
- BufferedOutputStream(OutputStream)
- BufferedOutputStream(OuputStream out, int size)size为自定义缓冲区的大小
缓冲输入流的方法
BuffedReader提供了一个方法readLine():String,但BufferedInputStream中并没有这个
- BufferedReader提供了readLine方法用于读取一行字符串,以\r或\n分割(换行符)
- 如果读取内容为null,则表示读取到了流的末尾
- readLine方法会自动剔除本行内容末尾的换行符
- BufferedWriter提供了newLine方法用于写入一个行分隔符
对于输出的缓冲流,写入的数据会先在内存中缓存,使用flush方法会使内存中的数据立即写出
键盘录入
System.in:InputStream用于指代系统默认的输入设备—键盘
方法read():int 可以实现代码执行到这里则会阻塞等待,只要输入数据为止
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
System.out.println("输入数据");
String temp="";
while((temp=br.readLine()).trim().length()>0){
if("quit".equals(temp)) break;
System.out.println(temp);
}
br.close();
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("只有缓冲区满才自动进行输出显示");
bw.flush(); //刷新缓冲区,否则看不到输出内容
System.in.read();
bw.close(); //关闭输出时会首先自动进行刷新缓冲区
强调:使用缓存流并没有添加什么额外方法,只是它能够在执行过程中自动引入缓存,从而提高执行效率
过滤流使用必须有对应的节点流,因为过滤流是装饰节点流,不是有具体的操作目标
new BufferedReader(new FileReader(…))或new BufferedWriter(new FileWriter())实际上使用的还是Reader/Writer那些方法,这里从编码的角度上说,没有任何区别,但是从执行性能上说,比FileReader/Writer效率高,可以减少磁盘的读写次数
执行close方法会自动关闭被装饰的对象,所以不需要再关闭FileReader和FileWriter
执行flush会自动刷新数据到节点流上,但是并没有执行关闭流。针对输出流关闭时会自动先flush缓存再执行关闭
数据流
DataInputStram和DataOutputStream分别继承自InputStream和OuputStream,属于过滤流,需要分别套接在InputStream和OutputStream类型的节点流上
- 只有字节流,没有对应的字符流
DataInputStream和DataOutputStream提供了可以存取与机器无关的Java原始类型数据的方法
DataInputSteram和DataOutputStream构造方法为
DataInputStream(InputStream)
DataOutputStream(OutputStream)
读取、写出一个double数据到文件中
//使用数据流就可以直接操作简单类型数据
double dd=123.4567;
FileOutputStream fos=new FileOutputStream("d:\\a.data");
fos.write((dd+"").getBytes());
fos.close();
//如果不使用数据流,则需要额外编码进行数据类型转换
FileInputStream fis=new FileInputStream("d:/a.data");
byte[] buffer=new byte[8192];
int len=fis.read(buffer);
fis.close();
String str=new String(buffer,0,len);
double dd=Double.parseDouble(str);
System.out.println(dd);
加入需要写一个double,然后一个String,然后再一个int
需要将输入内容转换为String,并且为了区分数据需要引入特殊符号,例如@@,输入数据为123.456@@shi ya zhou@@12。从功能角度上说没问题,但是编码太复杂了,所以引入Data类型的输入输出流
//这里不使用OutputStream定义变量的原因是:需要使用DataOutputStream中定义的特殊方法,而不是父类中
定义的通用方法
DataOutputStream dos=new DataOutputStream(new FileOutputStream("d:\\a.data"));
dos.writeDouble(123.456);
dos.writeChars("赵天爱小猴!");
dos.writeInt(12);
dos.close();
由于读取出现问题,针对中间的String数据引入一个额外的数据,其中存储String的char个数写出数据
double salary=123.456;
String ss="赵天爱小猴,小猿爱小主,小胡招小天";
int age=12;
DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(new
FileOutputStream("d:\\aa.data")));
dos.writeDouble(salary);
dos.writeInt(ss.length());
dos.writeChars(ss);
dos.writeInt(age);
dos.close();
读取数据
double salary=0;
String str="";
int age=0;
//读取数据的前提是必须知道数据的结构
DataInputStream dis=new DataInputStream(new BufferedInputStream(new
FileInputStream("d:\\aa.data")));
salary=dis.readDouble();
StringBuilder sb=new StringBuilder();
int len=dis.readInt();
for(int i=0;i<len;i++) sb.append(dis.readChar());
str=sb.toString();
age=dis.readInt();
System.out.println(salary+"---"+str+"---"+age);
注意:读取数据判断文件结束EOFException,这里没有-1
在具体应用中建议针对字串使用readUTF和writeUTF
DataOutputStream dos=new DataOutputStream(new FileOutputStream("data2.txt"));
dos.writeDouble(1234.56);
String str="猴子愛小終,小終愛信心";
dos.writeUTF(str);
dos.writeInt(99);
dos.close();
DataInputStream dis = new DataInputStream(new FileInputStream("data2.txt"));
double d1 = dis.readDouble();
String ss=dis.readUTF();
int kk = dis.readInt();
System.out.println(d1 + "\t" + ss + "\t" + kk);
dis.close();
打印流
- PrintStream和PrintWriter都属于输出流,分别针对字节和字符
- PrintWriter和PrintStream都提供了重载的print和println方法用于输出多种类型数据
- print(Object):void
输出引用类型,实际上是调用对象的toString方法转换为String进行输出
public void println(Object x) {
String s = String.valueOf(x); //调用String类中的静态方法将object类型的数据转换为字符串
synchronized (this) {
print(s);
newLine(); //print('\n')
}
}
//String中的valueOf方法的定义
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString(); //如果输出对象非空,则调用对象的toString方法
}
println表示输出后自动换行
PrintWriter和PrintStream的输出操作不会抛出异常,用户通过检测错误状态获取错误信息
PrintWriter和PrintStream有自动的flush功能 textOut.flushBuffer();
PrintWriter(Writer)
PrintWriter(Writer out, boolean autoFlush)自动刷新----println
PrintWriter(OutputStream out) //参数是一个字节流,但是不需要通过桥接处理
PrintWriter(OutputStream out, boolean autoFlush)
PrintStream(OutputStream)
PrintStream(OutputStream out, boolean autoFlush)
对象流
使用DataInputStream或者DataOutputStream可以读写对象数据,但是操作比较繁琐
//从文件中按照id值查找对应的对象
int id=dis.readInt(); //用户id--用户标识
int len=dis.readInt(); //用户名称的字符数
StringBuilder username=new StringBuilder(); //用户名称
for(int i=0;i<len;i++) //一次读取一个字符,然后拼接成完整的字符串
username.append(dis.readChar());
len=dis.readInt();
StringBuilder password=new StringBuilder(); //用户口令
for(int i=0;i<len;i++)
password.append(dis.readChar());
double balance=dis.readDouble(); //用户余额
if(dis==id){
res=new Account(); //Account是一个自定义类型,用于封装账户信息
res.setUsername(username.toString());
res.setPassword(password.toString());
res.setBalance(balance);
break;
}
SUN提供了ObjectInputStream/ObjectOutputStream可以直接将Object写入或读出
这里实际上还有针对8种简单类型及其包装类的操作方法,以及针对String类型的操作方法
readObject():Object
writeObject(Object):void
// 简单写法,应该使用try/finally结构或者使用try/resource的写法
Date now = new Date();
ObjectOutputStream oos = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream("data3.txt")));oos.writeObject(now);oos.close();
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream("data3.txt")));
Object obj=ois.readObject();if(obj!=null&&obj instanceof Date)
{
Date dd = (Date) obj;
System.out.println(dd);
}ois.close();
读写一个对象的前提是这个类型的对象是可以被序列化的;
- NotSerializableException
对象序列化【简单来说就是将对象可以直接转换为二进制数据流】/对象的反序列化【可以将二进制数据流转换为对象】,这一般依靠JVM实现,编程中只做声明对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其他程序一旦获取到这种二进制流,都可以将这种二进制流恢复成原来的Java对象
1、如何声明对象所属于的类可以进行序列化和反序列化Serializable/Externalizable接口其中的接口没有任何定义,仅仅只起到了说明的作用,这种接口叫做标志接口或者旗标接口
2、可以通过ObjectInputStream【readObject():Object】和ObjectOutputStream【writeObject(Object):void】提供的方法直接操作对象
3、输出对象
User user = new User();
user.setId(100L);
user.setUsername("zhangsan");
user.setPassword("123456");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users.data"));
oos.writeObject(user);
oos.close();
4、读取对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users.data"));
Object temp = ois.readObject();
if (temp != null && temp instanceof User) {
User user = (User) temp;
System.out.println(user);
}
ois.close();
编码细节
1、需要通过对象流读写的对象必须实现了序列化接口,否则java.io.NotSerializableException
class User implements Serializable
2、Serializable接口是标志接口,没有需要实现的方法,所有的序列化和反序列化操作由VM负责实现。
Externalizable接口定义为public interface Externalizable extends java.io.Serializable,这个接口中包含两个方法需要实现writeExternal自定义实现对象的序列化,readExternal自定义实现对象的反序列化。除非特殊需求一般不使用Externalizable接口,因为没有必要自定义
class User implements Externalizable {
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// 写出对象的操作
System.out.println("现在需要写出对象:" + this);
out.writeLong(this.id);
out.writeUTF(this.username);
out.writeUTF(this.password);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// 读取对象的操作
this.id = in.readLong();
this.username = in.readUTF();
this.password = in.readUTF();
}
public User(){}
3、类型转换问题:
Object temp = ois.readObject();
if (temp != null && temp instanceof User) {
User user = (User) temp;
System.out.println(user);
}
4、private static nal long serialVersionUID =6889840055394511246L
如果不添加序列号,则会有警告信息,但是不是错误信息
一般选择Add generated serial version ID会生成一个在项目中永不重复的的序列版本编号
序列版本号可以不用添加,这个序列版本号是一种序列化和反序列化中快速识别类型的简单方法,比不加序列号的识别效率高。引入的功能是如果版本号不对应,不会进行类型识别,而是直接报异常InvalidClassException
5、一般针对敏感数据不应该进行序列化操作,针对不需要进行序列操作的属性可以添加一个关键字transient,表示该属性不参与序列化和反序列化操作
class User implements Serializable {
private transient String password; //transient用于声明该属性不支持序列化操作
class User implements Serializable {
private String username;
private transient String password;
private Role role;//因为Role没有实现序列化接口,所以写出user对象时会有报错NotSerializableException。处理报错的方法有:1、可以给Role类定义添加序列接口。2、在role属性上添加transient表示这个属性不序列化处理
6、读文件的判断:读取文件时可以通过EOFException异常来判断文件读取结束
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users.data"));) {
while (true) {
try {
Object temp = ois.readObject();
if (temp != null && temp instanceof User) {
User user = (User) temp;
System.out.println(user);
}
} catch (EOFException ex) { // 这个异常是用于判断文件结尾,所以不需要进行处理
break;
}
}
}
已经向文件中写入数据后,继续追加存储,则读取数据会出现StreamCorruptedException
ObjectOutputStream oos=new ObjectOutputStream(new BueredOutputStream(new
FileOutputStream(“users.data”,true)));
package car;
public abstract class Che {
// 向文件中追加新数据 true
// 首先读取数据,然后再统一写入
Object[] arr = new Object[100];
int counter = 0;
File ff = new File("user.data");if(ff.exists())
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.data"));
while (true) {
try {
Object temp = ois.readObject();
arr[counter++] = temp;
} catch (EOFException e) {
break;
}
}
ois.close();
}
// 追加数据
User user = new User();user.setId(299L);user.setUsername("name299");user.setPassword("pwd299");arr[counter++]=user;
// 然后统一写出到文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.data"));for(
int i = 0;i<arr.length;i++)
{
Object temp = arr[i];
if (temp != null)
oos.writeObject(temp);
}oos.close();
}
问题:如果某个类的属性类型不是基本类型或者String类型,且没有实现可序列化接口,则该类型属性类是不可序列化
针对于对象中的InputStream/OutputStream之类的资源类型的属性,不仅不能进行序列化操作,而且在序列化之前应该释放资源,在反序列化后应该重新创建资源链接。Externalizable
package car;
public abstract class Che {
class User implements Externalizable {
private Long id; // 要求自增长
private String username;
private String password;
private InputStream is; // 输入流不能被序列
@Override
public void writeExternal(ObjectOutput out) throws IOException { //writeObject方法时调
用
is.close(); //释放资源
out.writeLong(this.id);
out.writeUTF(this.username);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// readObject方法时执行
this.id = in.readLong();
this.username = in.readUTF();
is = new FileInputStream("ddd.txt");// 重新获取资源
}
}
}