字符流
• 尽管字节流提供了处理任何类型输入/输出操作的足够的功能,它们不能直接操作Unicode字符。既然Java的一个主要目的是支持“只写一次,到处运行”的哲学,包括直接的字符输入/输出支持是必要的。
字符流层次结构的顶层是Reader 和Writer 抽象类。
• 字符输入/输出类是在java 的1.1版本中新加的。由此,你仍然可以发现遗留下的程序代码在应该使用字符流时却使用了字
节流。当遇到这种代码,最好更新它。
字符流Reader和Writer类
• 由于Java采用16位的Unicode字符,因此需要基于字符的输入/输出操作。从Java1.1版开始,加入了专门处理字符流的抽象类Reader和Writer,前者用于处理输入,后者用于处理输出。这两个类类似于InputStream和OuputStream,也只是提供一些用于字符流的规定,本身不能用来生成对象。
• Reader和Writer类也有较多的子类,与字节流类似,它们用来创建具体的字符流对象进行I/O操作。字符流的读写等方法与字节流的相应方法都很类似,但读写对象使用的是字符。
• Reader中包含一套字符输入流需要的方法,可以完成最基本的从输入流读入数据的功能。当Java程序需要外设的数据时,可根据数据的不同形式,创建一个适当的Reader子类类型的对象来完成与该外设的连接,然后再调用执行这个流类对象的特定输入方法,如read(),来实现对相应外设的输入操作。
• Writer中包含一套字符输出流需要的方法,可以完成最基本的输出数据到输出流的功能。当Java程序需要将数据输出到外设时,可根据数据的不同形式,也要创建一个适当的Writer子类类型的对象来完成与该外设的连接,然后再调用执行这个流类对象的特定输出方法,如write(),来实现对相应外设的输出操作。
• Reader是定义Java的流式字符输入模式的抽象类。该类的所有方法在出错情况下都将引发IOException 异常
• Writer 是定义流式字符输出的抽象类。所有该类的方法都返回一个void 值并在出错条件下引发IOException 异常
• Java程序语言使用Unicode来表示字符串和字符,Unicode使用两个字节来表示一个字符,即一个字符占16位
字符流Reader和Writer类的类层次
InputStreamReader和OutputStreamWriter类
• 这是java.io包中用于处理字符流的基本类,用来在字节流和字符流之间搭一座“桥”。这里字节流的编码规范与具体的平台有关,可以在构造流对象时指定规范,也可以使用当前平台的缺省规范。
• InputStreamReader和OutputStreamWriter类的主要构造方法如下
– public InputSteamReader(InputSteam in)
– public InputSteamReader(InputSteam in,String enc)
– public OutputStreamWriter(OutputStream out)
– public OutputStreamWriter(OutputStream out,String enc)
• 其中in和out分别为输入和输出字节流对象,enc为指定的编码规范(若无此参数,表示使用当前平台的缺省规范,可用
getEncoding()方法得到当前字符流所用的编码方式)。
• 读写字符的方法read()、write(),关闭流的方法close()等与Reader和Writer类的同名方法用法都是类似的。
示例程序StreamTest.java
package com.fairy.io2;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class StreamTest {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("file.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos);
BufferedWriter bw = new BufferedWriter(osw);
bw.write("http://www.google.com");
bw.write("\n");
bw.write("http://www.baidu.com");
bw.close();
FileInputStream fis = new FileInputStream("file.txt");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
String str = br.readLine();
while (null != str) {
System.out.println(str);
str = br.readLine();
}
br.close();
}
}
示例程序StreamTest2.java
package com.fairy.io2;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class StreamTest2 {
public static void main(String[] args) throws Exception {
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
String str;
while (null != (str = br.readLine())) {
System.out.println(str);
}
br.close();
}
}
FileReader
• FileReader类创建了一个可以读取文件内容的Reader类。FileReader继承于InputStreamReader。它最常用的构造方法显示如下
– FileReader(String filePath)
– FileReader(File fileObj)
– 每一个都能引发一个FileNotFoundException异常。这里,filePath是一个文件的完整路径,fileObj是描述该文件的File对象
示例程序FileReader1.java
package com.fairy.io2;
import java.io.BufferedReader;
import java.io.FileReader;
public class FileReader1 {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("C:/FileReader1.java");
BufferedReader br = new BufferedReader(fr);
String str;
while (null != (str = br.readLine())) {
System.out.println(str);
}
br.close();
}
}
FileWriter
• FileWriter 创建一个可以写文件的Writer 类。 FileWriter继承于OutputStreamWriter.它最常用的构造方法如下:
– FileWriter(String filePath)
– FileWriter(String filePath, boolean append)
append :如果为 true,则将字节写入文件末尾处,而不是写入文件开始处
– FileWriter(File fileObj)
• 它们可以引发IOException或SecurityException异常。这里,filePath是文件的完全路径,fileObj是描述该文件的File对象。如果append为true,输出是附加到文件尾的。
• FileWriter类的创建不依赖于文件存在与否。在创建文件之前,FileWriter将在创建对象时打开它来作为输出。如果你试图打开一个只读文件,将引发一个IOException异常。
示例程序FileWriter1.java
package com.fairy.io2;
import java.io.FileWriter;
public class FileWriter1 {
public static void main(String[] args) throws Exception {
String str = "Spring Core MVC JDBC";
char[] buffer = new char[str.length()];
str.getChars(0, str.length(), buffer, 0);
FileWriter fw = new FileWriter("file2.txt");
for (char c : buffer) {
fw.write(c);
}
fw.close();
}
}
CharArrayReader
• CharArrayReader 是一个把字符数组作为源的输入流的实现。该类有两个构造方法,每一个都需要一个字符数组提供数据源
– CharArrayReader(char array[ ])
– CharArrayReader(char array[ ], int start, int numChars)
– 这里,array是输入源。第二个构造方法从你的字符数组的子集创建了一个Reader,该子集以start指定的索引开始,长度为numChars
示例程序CharArrayReader1.java
package com.fairy.io2;
import java.io.CharArrayReader;
public class CharArrayReader1 {
public static void main(String[] args) throws Exception {
String tmp = "PythonGroovyGrails";
char[] ch = new char[tmp.length()];
tmp.getChars(0, tmp.length(), ch, 0);
CharArrayReader input = new CharArrayReader(ch);
int i;
while (-1 != (i = input.read())) {
System.out.print((char) i + " ");
}
}
}
CharArrayWriter
• CharArrayWriter 实现了以数组作为目标的输出流。CharArrayWriter 有两个构造方法
– CharArrayWriter( )
– CharArrayWriter(int numChars)
第一种形式,创建了一个默认长度的缓冲区。
第二种形式,缓冲区长度由numChars指定。缓冲区保存在CharArrayWriter的buf 成员中。缓冲区大小在需要的情况下可以自动增长。缓冲区保持的字符数包含在CharArrayWriter的count 成员中。buf 和count 都是受保护的域
(protected)
示例程序CharArrayWriter1.java
package com.fairy.io2;
import java.io.CharArrayWriter;
public class CharArrayWriter1 {
public static void main(String[] args) throws Exception {
String tmp = "PythonGroovyGrails";
char[] ch = new char[tmp.length()];
tmp.getChars(0, tmp.length(), ch, 0);
CharArrayWriter output = new CharArrayWriter();
output.write(ch);
char[] result = output.toCharArray();
for (char c : result) {
System.out.print(c + " ");
}
}
}
BufferedReader
• BufferedReader 通过缓冲输入提高性能。它有两个构造方法
– BufferedReader(Reader inputStream)
– BufferedReader(Reader inputStream, int bufSize)
第一种形式创建一个默认缓冲区长度的缓冲字符流。
第二种形式,缓冲区长度由bufSize传入
• 和字节流的情况相同,缓冲一个输入字符流同样提供支持可用缓冲区中流内反向移动的基础。为支持这点,BufferedReader 实现了mark( )和reset( )方法,并且BufferedReader.markSupported( ) 返回true
BufferedWriter
• BufferedWriter是一个增加了flush( )方法的Writer。flush( )方法可以用来确保数据缓冲区确实被写到实际的输出流。用BufferedWriter 可以通过减小数据被实际的写到输出流的次数而提高程序的性能。
• BufferedWriter有两个构造方法:
– BufferedWriter(Writer outputStream)
– BufferedWriter(Writer outputStream, int bufSize)
第一种形式创建了使用默认大小缓冲区的缓冲流。
第二种形式中,缓冲区大小是由bufSize参数传入的。
字符集的编码
• ASCII(American Standard Code for Information Interchange,美国信息互换标准代码),是基于常用的英 文字符的一套电脑编码系统。我们知道英文中经常使用的 字符、数字符号被计算机处理时都是以二进制码的形式出 现的。这种二进制码的集合就是所谓的ASCII码。每一个 ASCII码与一个8位(bit)二进制数对应。其最高位是0, 相应的十进制数是0-127。如,数字“0”的编码用十进制 数表示就是48。另有128个扩展的ASCII码,最高位都是1, 由一些制表符和其它符号组成。ASCII是现今最通用的单 字节编码系统。
• GB2312:GB2312码是中华人民共和国国家汉字信息交换用 编码,全称《信息交换用汉字编码字符集-基本集》。主 要用于给每一个中文字符指定相应的数字,也就是进行编 码。一个中文字符用两个字节的数字来表示,为了和 ASCII码有所区别,将中文字符每一个字节的最高位置都 用1来表示。
• GBK:为了对更多的字符进行编码,国家又发布了新的编码系统GBK(GBK的K是“扩展”的汉语拼音 第一个字母)。在新的编码系统里,除了完全兼容 GB2312 外,还对繁体中文、一些不常用的汉字和 许多符号进行了编码。
• ISO-8859-1:是西方国家所使用的字符编码集,是一种单字节的字符集,而英文实际上只用了其中数字小于128的分。
• Unicode:这是一种通用的字符集,对所有语言 的文字进行了统一编码,对每一个字符都用2个字 节来表示,对于英文字符采取前面加“0”字节的 策略实现等长兼容。如 “a” 的ASCII码为0x61, UNICODE就为0x00,0x61。(在internet上传 输效率较低)
• UTF-8:Eight-bit UCS Transformation Format,(UCS,Universal Character Set, 通用字符集,UCS 是所有其他字符集标准的一 个超集)。一个7位的ASCII码值,对应的UTF码 是一个字节。如果字符是0x0000,或在0x0080
与0x007f之间,对应的UTF码是两个字节,如果 字符在0x0800与0xffff之间,对应的UTF码是三 个字节(汉字为3个字节)。
示例程序CharSet.java
package com.fairy.io2;
import java.util.Properties;
public class CharSet {
public static void main(String[] args) {
Properties p = System.getProperties();
p.list(System.out);
}
}
示例程序CharSetTest.java
package com.fairy.io3;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import java.util.SortedMap;
public class CharSetTest {
public static void main(String[] args) {
SortedMap<String, Charset> map = Charset.availableCharsets();
Set<String> set = map.keySet();
for (Iterator<String> iter = set.iterator(); iter.hasNext();) {
System.out.println(iter.next());
}
}
}
RandomAccessFile(随机访问文件类)
• RandomAccessFile包装了一个随机访问的文件。它不是派生于InputStream和OutputStream,而是实现定义了基本输入/输出方法的DataInput和DataOutput接口。它支持定位请求——也就是说,可以在文件内部放置文件指针。它有两个构造方法:
• RandomAccessFile(File fileObj, String access) throws FileNotFoundException
• RandomAccessFile(String filename, String access) throws FileNotFoundException
• 第一种形式,fileObj指定了作为File 对象打开的文件的名称。
• 第二种形式,文件名是由filename参数传入的。
• 两种情况下,access 都决定允许访问何种文件类型。如果是“r”,那么文件可读不可写,如果是“rw”,文件以读写模式打开
例如:
new RandomAccessFile(“test.txt", "r");
new RandomAccessFile(“test.txt", "rw");
• RandomAccessFile类同时实现了DataInput和DataOutput接口,提供了对文件随机存取的功能,利用这个类可以在
文件的任何位置读取或写入数据。
• RandomAccessFile类提供了一个文件指针,用来标志要进行读写操作的下一数据的位置。
• 常用方法:
– public long getFilePointer()
– 返回到此文件开头的偏移量(以字节为单位),在该位置发生下一个读取或写入操作
– public void seek(long pos)
– 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。偏移量的设置可能会超出文件末尾。偏移量的设置超出文件末尾不会改变文件的长度。只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度
– public long length()
– 返回此文件的长度
– public int skipBytes(int n)
– 尝试跳过输入的 n 个字节以丢弃跳过的字节
示例程序RandomAccessFile1.java
package com.fairy.io2;
import java.io.RandomAccessFile;
public class RandomAccessFile1 {
public static void main(String[] args) throws Exception {
Person p = new Person(1, "JSP", 1.80);
RandomAccessFile raf = new RandomAccessFile("raf.txt", "rw");
p.write(raf);
Person p2 = new Person();
raf.seek(0); // 让读的位置重回到文件开头
p2.read(raf);
System.out.println(p2.getId() + ", " + p2.getName() + ", "
+ p2.getHeight());
}
}
class Person {
private int id;
private String name;
private double height;
public Person() {
}
public Person(int id, String name, double height) {
this.id = id;
this.name = name;
this.height = height;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public void write(RandomAccessFile raf) throws Exception {
raf.writeInt(this.id);
raf.writeUTF(this.name);
raf.writeDouble(this.height);
}
public void read(RandomAccessFile raf) throws Exception {
this.id = raf.readInt();
this.name = raf.readUTF();
this.height = raf.readDouble();
}
}
序列化
• 将对象转换为字节流保存起来,并在以后还原这个对象,这种机制叫做对象序列化。
• 将一个对象保存到永久存储设备上称为持久化。
• 一个对象要想能够实现序列化,必须实现Serializable接口或Externalizable接口。
• 序列化(serialization)是把一个对象的状态写入一个字节流的过程。当你想要把你的程序状态存到一个固定的存储区域例如文件时,它是很管用的。稍后一点时间,你就可以运用序列化过程存储这些对象
• 假设一个被序列化的对象引用了其他对象,同样,其他对象又引用了更多的对象。这一系列的对象和它们的关系形成了一个顺序图表。在这个对象图表中也有循环引用。也就是说,对象X可以含有一个对象Y的引用,对象Y同样可以包含一个对象X的引用。对象同样可以包含它们自己的引用。对象序列化和反序列化的工具被设计出来并在这一假定条件下运行良好。如果你试图序列化一个对象图表中顶层的对象,所有的其他的引用对象都被循环的定位和序列化。同样,在反序列化过程中,所有的这些对象以及它们的引用都被正确的恢复
• 当一个对象被序列化时,只保存对象的非静态成员变量,不能保存任何的成员方法和静态的成员变量。
• 如果一个对象的成员变量是一个对象,那么这个
对象的数据成员也会被保存。
• 如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化
• Serializable接口
– 只有一个实现Serializable接口的对象可以被序列化工具存储和恢复。Serializable接口没有定义任何成员。它只用来表示一个类可以被序列化。如果一个类可以序列化,它的所有子类都可以序列化。
– 声明成transient的变量不被序列化工具存储。同样,static变量也不被存储。
• Externalizable接口
Java的序列化和反序列化的工具被设计出来,所以很多存储和恢复对象状态的工作自动进行。然而,在某些情况下,程序员必须控制这些过程。例如,在需要使用压缩或加密技术时,Externalizable接口为这些情况而设计。
• Externalizable 接口定义了两个方法:
– void readExternal(ObjectInput inStream) throws IOException, ClassNotFoundException
– void writeExternal(ObjectOutput outStream) throws IOException
– 这些方法中,inStream是对象被读取的字节流,outStream是对象被写入的字节流。
• Externalizable 实例类的惟一特性是可以被写入序列化流中,该类负责保存和恢复实例内容。 若某类要完全控制某一对象及其超类型的流格式和内容,则它要实现 Externalizable 接口的 writeExternal 和 readExternal 方法。这些方法必须显式与超类型进行协调以保存其状态。这些方法将代替自定义的 writeObject 和 readObject 方法实现
• ObjectOutput接口
– ObjectOutput 继承DataOutput接口并且支持对象序列化。特别注意writeObject( )方法,它被称为序列化一个对象。所有这些方法在出错情况下引发IOException 异常
• ObjectOutputStream类
– ObjectOutputStream类继承OutputStream 类和实现ObjectOutput 接口。它负责向流写入对象。该类的构造方法如下:
– ObjectOutputStream(OutputStream outStream) throws IOException
– 参数outStream 是序列化的对象将要写入的输出流
• ObjectInput
– ObjectInput 接口继承DataInput接口。它支持对象序列化。特别注意 readObject( )方法,它叫反序列化对象。所有这些方法在出错情况下引发IOException 异常
• ObjectInputStream
– ObjectInputStream 继承InputStream类并实现ObjectInput 接口。
ObjectInputStream 负责从流中读取对象。
该类的构造方法如下:
– ObjectInputStream(InputStream inStream) throws IOException,StreamCorruptedException
– 参数inStream 是序列化对象将被读取的输入流。
示例程序SerializableTest1.java
package com.fairy.io3;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializableTest1 {
public static void main(String[] args) throws Exception {
Person p1 = new Person("JSP", 20, 1.80);
Person p2 = new Person("Servlet", 30, 1.90);
Person p3 = new Person("JDBC", 40, 2.00);
FileOutputStream fos = new FileOutputStream("person.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(p1);
oos.writeObject(p2);
oos.writeObject(p3);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"person.txt"));
Person p = null;
for (int i = 0; i < 3; i++) {
p = (Person) ois.readObject();
System.out.println(p.getName() + "," + p.getAge() + ","
+ p.getHeight());
}
ois.close();
}
}
class Person implements Serializable {
private String name;
private int age;
private double height;
public Person(String name, int age, double height) {
super();
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
– 反序列化时不会调用对象的任何构造方法,仅仅根据所保存的对象状态信息,在内存中重新构建对象
• 在序列化和反序列化进程中需要特殊处理的 Serializable 类应该实现以下方法:
• private void writeObject(java.io.ObjectOutputStream stream) throws IOException;
• private void readObject(java.io.ObjectInputStream stream) throws IOException,
ClassNotFoundException;
这两个方法不属于任何一个类和任何一个接口,是非常特殊的方法.