javaIO

《Thinking in Java》 --IO

说明:

IO包括了Input和Output两个方面,基本上是对应的

Input输入表示读取,也就是建立input流从各种可能的源读取数据。Output输出表示写入,也就是建立output流向外写数据

在JAVA的I/O中,流一般分为两种模式,一种是数据流,一种是装饰流。数据流是实际读取写入的操作流,装饰流是对其功能封装加强的流。

注意字节流是直接写入到文件的,而字符流是写到缓存然后写文件的
因此,字节流不用close()也能看到东西,而字符流必须close()以后才能看到,除非强制用flush()刷新缓存。

 

 

常用的输入输出流(数据源)有两个:

ByteArrayStream用来操作内存,FileStream用来操作文件

① ByteArrayInputStream

这个流内部包含一个Byte[]数组引用,需要在构造时给定这个数组,用来存放读取的字节

1 构造方法:
2 public ByteArrayInputStream(byte[] buf)
3 
4 基本方法:
5 public int read()       读取下一个数据字节,返回一个int
6 public int read(byte[] b,  读取len个数据字节(如果够读的话),先存入内部Byte[]再调用System.arrayCopy来复制到b里面,复制的时候b中的起始位置由off决定
7                 int off,
8                 int len)
9 public int available()

 

② ByteArrayOutputStream

 1 构造方法:
 2 public ByteArrayOutputStream()         缓冲区的容量最初是 32 字节,如有必要可增加其大小。
 3 public ByteArrayOutputStream(int size)     它具有指定大小的缓冲区容量(以字节为单位)。
 4 
 5 主要方法:
 6 public void write(int b)             将指定的字节写入此 byte 数组输出流。
 7 public void write(byte[] b,          将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此 byte 数组输出流。 
 8                   int off,
 9                   int len)
10 public byte[] toByteArray()          创建一个新分配的 byte 数组。其大小是此输出流的当前大小,并且缓冲区的有效内容已复制到该数组中。
11 public int size()               返回缓冲区的当前大小。 

Byte[] toByteAarray() 会把当前这个outputStream转换成一个Byte数组供ByteArrayInputStream使用

比如:

1 ByteArrayOutputStream br = new ByteArrayOutputStream();
2 
3 DataOutputStream ds = new DataOutputStream(br);
4 
5 ds...
6 
7 ByteArrayInputStream in = new ByteArrayInputStream(br.toByteArray());

ByteArrayInputStream是不需要关闭的,也无法关闭

 


③ FileInputStream
用于读取诸如图像数据之类的原始字节流。要读取字符流,请考虑使用 FileReader。
构造方法接受String,File参数来读取一个文件

1 构造方法:
2 public FileInputStream(String name)
3 public FileInputStream(File file)
4 主要方法: 5 int available()           返回的是下一次无阻塞读取的最多可能的数据字节数量 6 int read()              同上 7 int read(Byte[] b,int off, int len) 同上 8 void close()            关闭这个流

 

④ FileOutputStream(基本同上)

 

通常读取流的时候,还需要使用装饰器(包装流),就是filterInputStream类,这个类是个抽象类,它的几个实现类用来控制特定输入流,主要有两个:

① DataInputStream(DataOutputStream)
这是一个重要的类,除了对String的读取之外,这个类应该是读取数据字节的首选
数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。
构造方法需要一个基本输入流,也就是FileInputStream或者ByteArrayInputStream等
主要方法:
封装了全部基本数据类型的读取,包括readBoolean(),readByte(),readInt(),readFloat(),readDouble()等

② BufferedInputStream
使用了读缓冲,不必每次读取都进行写操作,实际使用频率很高
BufferedInputStream 为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。
它自带一个内部Byte[]数组作为缓冲,可以自动变更大小
构造方法同DataInputStream,需要一个基本数据流


因此,通常我们创建一个面向字节的数据流时,都要建立包装对象,其构造方法里用基本数据流,如下:

DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("TestIO.java")));

 

 

----------------------------

 

另外,在java里也提供了兼容Unicode和面向字符IO的流。它们和面向字符基本是对应关系

 

常用的输入流:

 

FileReader 对应FileInputStream,默认的文件读取流 创建需要一个file或者String
StringReader 无对应 创建需要一个String
CharArrayReader 对应ByteArrayInputStream 创建需要一个char[]

 

注意FileReader默认字符集和缓冲区,如果需要自己指定,需要创建InputStreamReader类,FileReader继承这个类,这个类的作用就是从字节到字符的转换桥梁

 

常用的包装流:

 

BufferedReader 对应BufferedInputStream
PrintWriter 对应PrintStream

 

这里提到了PrintStream,这个是属于写入的,一般写入使用:

 

DataOutputStream、BufferedOutputStream以及PrintStream把读取的数据写入到文件

----------------------------------------

注意:无论何时,读取一行字符串使用readLine()都要用BufferedReader,而其他则使用DataInputStream是首选
注意:所有read()方法返回的都是Int,可能需要强制转换成Char等其他类型

几个典型的IO使用方法:

1、读取输入文件(一行一行,使用readLine())

BufferedReader in = new BufferedReader(new FileReader(name));

 

2、读取输入字符串(str是一个字符串,一个字符一个字符,使用read())

1 StringReader in = new StringReader(Str);
2 int c;
3 while((c = in.read()) != null)
4 System.out.println((char)c);

 

3、读取格式化的数据,使用DataInputStream(这里假定是一个字符一个字符读)str.getBytes()是String类的一个方法,把String转换成Byte[]

1 DataInputStream in = new DataInputStream(new ByteArrayInputStream(str.getBytes()));
2 in.readByte();

 

4、打印输出文件(把s写入到file中,这个比较好用)

1 PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
2 String s ;
3 out.println(s);

上述第一步可以简化写成PrintWriter out = new PrintWriter(file);



5、存储和恢复数据,使用DataOutputStream

1 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("Data.txt")));
2 out.writeDouble(3.33);
3 out.writeInt(4);
4 out.writeUTF("hahaha");
5 out.close();

在用DataInputStream对应读取即可,注意必须使用固定格式的字符序列,或者将额外的定位信息保存在文件中,便于读取

 

6、使用DataInputStream一个字节一个字节读数据

1 DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("TestFinal.java")));
2 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("TestFinal_bak2.java")));
3 
4 Byte b;
5 while(in.available() != 0) {
6 out.writeByte((Byte)(in.readByte()));
7 }
8 in.close();
9 out.close();

 

------------------------------------------
关于标准I/O:

Java标准I/O包括System.in,System.out和System.err
其中System.out和System.err已经被封装成了printStream对象
而System.in没有封装,使用的时候必须对其包装

7、关于InputStreamReader的使用:

1 InputStreamReader把一个InputStream转换成一个Reader
2 
3 BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
4 String s;
5 while((s = in.readLine()) != null)
6 System.out.println(s);

 

标准I/O的重定向:

System.setIn(InputStream)
System.setOut(PrintStream)
System.setErr(PrintStream)


使用技巧:
先用InputStream来创建一个流,用setIn指定输入后,在用一个InputStreamReader包装这个流,readLine()读取

 

-------------------------------------------------

Serializable说明:


1. 只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。

2. writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。

3. 对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。其他对象的引用(瞬态和静态字段除外)也会导致写入那些对象。

4. 序列化操作不写出没有实现 java.io.Serializable 接口的任何对象的字段。
不可序列化的 Object 的子类可以是可序列化的。在此情况下,不可序列化的类必须有一个无参数构造方法,以便允许初始化其字段。
在此情况下,子类负责保存和恢复不可序列化的类的状态。经常出现的情况是,该类的字段是可访问的(public、package 或 protected),或者存在可用来恢复状态的 get 和 set 方法。

5. ObjectOutputStream实现了DataOutput接口,因此包含了与DataOuputStream一样的write方法,可以writeInt,writeUTF,writeFloat等等

6. readObject 方法用于从流读取对象。应该使用 Java 的安全强制转换来获取所需的类型。在 Java 中,字符串和数组都是对象,所以在序列化期间将其视为对象。读取时,需要将其强制转换为期望的类型。

7. 可以使用 DataInput 上的适当方法从流读取基本数据类型。 因为它实现了DataInput接口

8. 对其他对象的引用使得根据需要从流中读取这些对象。反序列化时始终分配新对象,这样可以避免现有对象被重写。

9. ObjectOutputStream和ObjectInputStream也是包装流,他们必须调用一个实际的数据流来完成读写(当然是FileOutputSream或者ByteArrayOutputStream等)

10. 序列化对象还原的时候,没有调用任何构造器,包括默认构造器。

------------------------------------------------
Serializable的另外一个方法:

使用Serializable可以序列化一个对象(不调用构造器,不传递static数据),如果某些数据不想传输,使用transient关键字。
如果想更加细粒度控制,使用Externalizable接口,实现wrtieExternal(ObjectOutput out)和readExternal(ObjectInput in)
现在,可以在Serializable对象中定义两个方法,而不使用默认的序列化机制(可以理解为不用Externalizable关键字但是实现其功能):
piravte void writeObject(ObjectOutputStream stream)
piravte void readObject(ObjectInputStream stream)
在调用ObjectOutputStream.writeObject()的时候,会检测该Serializable对象里是否包含了这个私有的writeObject(),如果包含就调用这个;同理于read

另外,在这个private的writeObject()中,还可以继续使用普通的序列化机制,就是调用一个stream.defaultWriteObject()方法
这个defaultWriteObject()方法必须是writeObject()的第一个序列化操作(而不能先writeObject()后再调用它)
而对应的,defaultReadObject()方法必须是readObject()的第一个反序列化操作

 

------------------------------------------------

File的一些基本操作:

1. File中定义的两个常量:

1 public static final String pathSeparator
2 public static final String separator

注意,在windows下;而在linux下是\

2. 常用 方法:

 1 (1)createNewFile() throws IOException : boolean
 2 (2)delete() : boolean
 3 (3)exists() : boolean
 4 (4)isDirectory() : boolean
 5 (5)length() : Long
 6 (6)isFile() : boolean
 7 (7)list() : String[]
 8 (8)listFiles() : File[]
 9 (9)mkdir() : boolean
10 (0) renameTo(File dest) : boolean

 

3. 创建file的时候,写路径时,最好用上File.separator保证其是可移植的程序

4. list和listFile可以接收一个过滤器,用来筛选列出来的文件,这需要一个FilenameFilter对象

FilenameFileter是一个接口,包含一个方法:
public boolean accept(File dir ,String name);
这个name是要匹配的字符串(也就是路径名了)

5. 因此,可以在内部实现一个过滤器类,举例如下:

 1 private class DirFilter implements FilenameFilter {
 2 private Pattern pattern;
 3 DirFilter(String regex) {
 4 pattern = Pattern.compile(regex);
 5 }
 6 
 7 public boolean accept(File dir, String name) {
 8 Matcher m = pattern.matcher(name);
 9 while(m.find()) {
10 System.out.println(m.group());
11 System.out.println(m.group(0));
12 }
13 // System.out.println( name + "    " + m.find() +"    " + m.matches());
14 System.out.println();
15 return m.matches();
16 
17 }
18 
19 
20 }

 

-------------------------------------------------

Extenalizable接口总结:

1. 使用Externalizable接口,要实现两个方法,一个是writeExternal(ObjectOutput out),一个是readExternal(ObjectInput in)
2. 使用Externalizable接口,必须保证被序列化的类有一个默认的public构造方法(使用Serializable不需要,也不会调用构造器)
3. 所有默认构造器中、字段定义时直接赋值的初始化,都可以在恢复时自动恢复(注意,必须是默认构造器,其他有参构造器不会被调用)
4. 没有使用默认定义的字段,必须使用写在writeExternal中(用out.writeObject等方法写),而且在readExternal中读,才能恢复(这里就是控制的地方!);
5. 其他的变量,直接视为0或者null
6. writeExternal和readExternal方法在恢复的时候不用显式调用

 

-------------------------------------------------

transient关键字:

1. transient关键字的作用是,在一个可序列化对象中,如果不想某个变量被传递,可以设置为transient,这样在接收后重新读取,这个变量为空
2. transient关键字只能和Serializable一起使用,不能跟Externalizable一起使用

 

-------------------------------------------------

在序列化中,对于static数据字段的说明:

1.如果基类有一个static字段,子类没有,那么不继承static字段
2.类中的static字段,如果直接赋予了初始值,那么在【其他恢复类】中恢复的时候,恢复出来的是初始值(也就是初始状态),无法恢复当前值(比如可能被修改过)
3.类中的static字段,如果在构造方法里初始化,那么恢复出来的是0
4.要保存static字段的方法是,手动把它写入到序列中去,可以创建如Pupyy类中seriliazeStaticState这样的正反序列函数,恢复的时候只要按顺序调用即可恢复状态值

5.Class对象是可序列化的,而且它保存了static字段,因此可以序列化Class对象来保存static字段

 

------------------------------------------------

关于持久化的另外说明:
1. 如果要保证状态持久化,最好的做法是利用原子性——把所有的状态打包成一个数据结构,一次性写入
2. 使用ByteArrayOutputStream和ByteArrayInputStream可以实现一个可序列化对象的深度复制,如下:
ByteArrayOutputSream br = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(br);
out.writeObject();
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(br.toByteArray()));
in.readObject();

这样恢复出来的对象,不是原来的对象地址,实现了深度复制。

 

posted on 2013-05-12 14:30  melburg  阅读(195)  评论(0)    收藏  举报