Java IO 核心操作(五)
Java常见编码简介
在计算机的世界里,任何的文字都是以指定的编码方式存在的,在Java程序的开发中最常见的是ISO8859-1,GBK/GB2312,unicode,UTF编码。
ISO8859-1:属于单字节编码,最多只能表示表示0~255的字符范围,主要在英文上的应用。
GBK/GB2312:中文的国际编码,专门用来表示文字,是双字节编码,如果在此编码中出现中文,则使用ISO8859-1编码,GBK可以表示简体中文和繁体中文,而GB2312只能表示简体的中文,GBK兼容GB2312.
unicode:Java中使用此编码方式,是最标准得的一种编码,使用十六进制表示编码,但此编码不兼容ISO8859-1编码。
UTF:由于unicode不支持ISO8859-1编码,而且容易占用更多的空间 ,而且对于英文字母也需要使用使用两个字节编码,这样使用unicode不便于传输和存储,因此产生了UTF编码。UTF编码兼容了ISO8859-1编码,同时也可以用来表示所有的语言字符,不过UTF编码是不定长编码,每一个字符的长度为1~6个字符不等。一般在中文网页中使用此编码,可以节省空间。
在程序中如果处理不好字符的编码,则就有可能出现乱码问题。如果现在本机的默认编码是GBK,但在程序中使用了ISO8859-1编码则会出现字符的乱码问题,就像两个人交谈,一个人说中文,另外一个人说的是其他语言,如果语言不同,则肯定无法沟通,如图所示:
所以如果要避免产生乱码,则程序的编码与本地的默认编码保持一致即可,而如果要知道本地的默认编码,则可以使用System类完成。
得到本机的编码显示
使用System类可以直接取得与系统有关的信息,所以直接使用此类即可找到系统的默认编码,使用如下方法:
public static Properites getProperty()
使用上述方法得到JVM的默认编码
public class CharSetDemo01{
public static void main(String args[]){
System.out.println("系统默认编码:" +
System.getProperty("file.encoding")) ; // 获取当前系统编码
}
};
程序运行结果:
系统默认编码GBK
可以发现现在操作系统的默认编码是GBK,所以,如果此时使用了ISO8859-1编码,则肯定出现乱码。
乱码产生
下面通过一个范例简介乱码的产生,现在本地的默认编码是GBK,下面通过对文字进行编码转换,如果要实现编码的转换可以使用String类中的getBytes(String charset)方法,此方法可以设置指定的编码,该方法的格式如下:
使用ISO8859-1编码
import java.io.OutputStream ;
import java.io.FileOutputStream ;
import java.io.File ;
public class CharSetDemo02{
public static void main(String args[]) throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 实例化File类
OutputStream out = new FileOutputStream(f) ; // 实例化输出流
byte b[] = "中国,你好!".getBytes("ISO8859-1") ; // 转码操作
out.write(b) ; // 保存
out.close() ; // 关闭
}
};
程序运行结果:
???
可以发现,因为编码不一致,所以在保存时出现了乱码。在Java的开发中,乱码是一个比较常见的问题,乱码的产生就只有一个原因,即输出内容的编码与接收内容的编码不一致。
对象序列化
基本概念与Serializable接口
对象序列化就是把一个对象变为二进制的数据流的一种方式,如图所示,通过对象序列化可以方便地实现对象的传输或存储。
如果一个类想被序列化,则对象的类必须实现java.io.Serializable接口。此接口的定义如下:
public interface Serializable{}
可以发现在接口中没有定义任何的方法,所以此接口是一个标识接口。表示一个具备被序列化的能力。
定义一个可被序列化的类
import java.io.Serializable ;
public class Person implements Serializable{
private String name ; // 声明name属性,但是此属性不被序列化
private int age ; // 声明age属性
public Person(String name,int age){ // 通过构造设置内容
this.name = name ;
this.age = age ;
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
};
以上的Person类已经完成了序列化接口,所以此类的对象可以通过二进制数据流进行传输的。而如果要完成对象的输入或输出,还必须依靠对象输出流(ObjectOutputStream)和对象输入流(ObjectInputStream)。
使用对象输出流输出序列化对象的步骤优势也称为序列化,而使用对象输入流读入对象的过程有时也被称为反序列化,如图所示:
提示
对象序列化和对象反序列化操作时的版本兼容问题
在对象进行序列化或反序列化的操作时,要考虑序列化JDK的版本和反序列化的JDK版本不统一则就有可能造成异常,所以在在序列化操作中引入了一个serialVersionUID的常量,可以通过此常量来验证版本的一致性。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
当实现java.io.Serializable接口的实体类没有显示的定义一个名为SerialVersionUID,类型为long的变量时,Java序列化机制在编译时会自动生成一个此版本的serialVersionUID。当然,如果不希望通过编译来自动生成,也可以直接显示地定义一个名为serialVersionUID,类型为long的变量,只要不修改这个变量值的序列化实体,都可以进行串行化和反串行化。
本程序直接在Person中加入以下的常量即可:
private static final long serialVersionUID=1L;
SerialVersionUID的具体内容由用户指定。
对象输出流ObjectOutputStream
一个对象如果要进行输出,则必须使用ObjectOutputStream类,此类的定义如下:
public class ObjectOutputStream extends OutputStream
implements ObjectOutput,ObjectStreamConstant
ObjectOutputStream类属于OutputStream类的子类,此类的常用方法如表所示:
此类的使用形式与PrintStream非常相似,在实例化时也需要传入一个OutputStream的子类对象,然后根据传入的OutputStream子类的对象不同,输出的位置也不同。
将Person类对象保存在文件中
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
public class SerDemo01{
public static void main(String args[]) throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectOutputStream oos = null ; // 声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 文件输出流
oos = new ObjectOutputStream(out) ;
oos.writeObject(new Person("张三",30)) ; // 保存对象
oos.close() ; // 关闭
}
};
提出问题
到底序列化了哪些内容?
答:只有属性被序列化。因为每个对象都具有相同的方法,但是每个对象的属性不一定相同,也就是说,对象只保存了属性信息,那么在序列化操作时也是那么个道理。
对象输入流ObjectInputStream
使用ObjectInputStream可以直接把序列化好的对象反序列化。ObjectInputStream的定义如下
public class ObjectInputStream extends InputStream implements ObjectInput,ObjectStreamConstants
ObjectInputStream类也是InputStream的子类,与PrintStream类的使用类似。此类型同样需要接收InputStream类的实例才可以实例化。主要操作方法如表所示:
下面使用对象输入流将上面保存在文件中的对象读取出来,此过程也称为反序列化。
从文件中将Person对象反序列化(读取)
import java.io.File ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo02{
public static void main(String args[]) throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj = ois.readObject() ; // 读取对象
ois.close() ; // 关闭
System.out.println(obj) ;
}
};
程序运行结果:
姓名:张三; 年龄: 30
Externalizable接口
被Serializable接口声明的类的对象的内容都将被序列化,如果现在用户希望自己指定序列化的内容,则可以让一个类实现Externalizable接口,此接口定义如下:
public interface Externalizable extends Serializable{
public void writeExternal(ObjectOutput out)throws IOException;
public void readExternal(ObjectInput in)throws IOException,ClassNotFountException;
}
Externalizable接口是Serializable接口的子接口,此接口中定义了两个方法,这两个方法的作用如下:
writeExternal(ObjectOutput out):在此接口中指定要保存的属性信息,对象序列化时调用。
readExternal(ObjectInput in):在此方法中读取被保存的信息,对象反序列化时调用。这两个方法的参数类型是ObjectOutput和ObjectInput,两个接口的定义如下:
ObjectOutput接口定义:
public interface ObjectOutput extends DataOutputObjectInput接口定义:
public interface ObjectInput extends DataInput可以发现两个接口分别继承DataOutput和DataInput,这样在这两个方法中就可以像DataOutputStream和DataInputStream那样直接输出和读取各种类型的数据。
注意:
如果一个类要使用Externalizable实现序列化时,在此类中必须存在一个无参构造方法,因为在反序列化时会默认调用无参构造实例化对象,如果没有此无参构造,则运行时将会出现异常,这一点的实现机制与Serializable接口是不同的。
下面我们就具体操作一下
- 序列化和反序列化Person对象
import java.io.File ;
import java.io.IOException ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo03{
public static void main(String args[]) throws Exception{
//ser() ;
dser() ;
}
public static void ser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectOutputStream oos = null ; // 声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 文件输出流
oos = new ObjectOutputStream(out) ;
oos.writeObject(new Person("张三",30)) ; // 保存对象
oos.close() ; // 关闭
}
public static void dser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj = ois.readObject() ; // 读取对象
ois.close() ; // 关闭
System.out.println(obj) ;
}
};
从以上代码可以发现,使用Externalizable接口实现序列化明显要比使用Serializable接口实现序列化麻烦的多,除此之外,两者的实现还有不同,如表所示:
一般在开发中,Serializable接口比较常用。
transient关键字
Serializable接口实现的操作实际上是将一个对象中的全部属性进行序列化,当然也可以使用Externalizable接口以实现部分属性的序列化,但这样操作比较麻烦。
当使用Serializable接口实现序列化操作时,如果一个对象中的某个属性不希望被序列化,则可以使用transient关键字进行声明,代码如下所示:
Person类中的name属性不希望被序列化
import java.io.Serializable ;
public class Person implements Serializable{
private transient String name ; // 声明name属性,但是此属性不被序列化
private int age ; // 声明age属性
public Person(String name,int age){ // 通过构造设置内容
this.name = name ;
this.age = age ;
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
};
重新保存再读取对象
import java.io.File ;
import java.io.IOException ;
import java.io.FileOutputStream ;
import java.io.OutputStream ;
import java.io.ObjectOutputStream ;
import java.io.FileInputStream ;
import java.io.InputStream ;
import java.io.ObjectInputStream ;
public class SerDemo04{
public static void main(String args[]) throws Exception{
ser() ;
dser() ;
}
public static void ser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectOutputStream oos = null ; // 声明对象输出流
OutputStream out = new FileOutputStream(f) ; // 文件输出流
oos = new ObjectOutputStream(out) ;
oos.writeObject(new Person("张三",30)) ; // 保存对象
oos.close() ; // 关闭
}
public static void dser() throws Exception {
File f = new File("D:" + File.separator + "test.txt") ; // 定义保存路径
ObjectInputStream ois = null ; // 声明对象输入流
InputStream input = new FileInputStream(f) ; // 文件输入流
ois = new ObjectInputStream(input) ; // 实例化对象输入流
Object obj = ois.readObject() ; // 读取对象
ois.close() ; // 关闭
System.out.println(obj) ;
}
};
程序运行结果:
姓名:null 年龄:30
以上程序中的姓名为null,表示内容没有被序列化保存下来。这样在对象反序列化后,输出时姓名就是默认值null。