一、 java Cloneable 详解 (clone,克隆)
上面是GOF设计模式中对原型模式的图形结构描述,原型模式通过克隆使我们可以得到一个对象的复制版本.其好处就是让我们在需要一个与现有对象类似的实例时,不用一一进行每个成员的赋值,而是直接通过现有的对象复制.并且复制出来的对象是互相独立的.
如上图,当对象进行了这样的赋值以后,两个变量指向了同一个对象实例,也就是说它们都引用是相同的,在这种情况下,其中一个对象的改变都会影响另一个对象的变化.两个对象互不独立.
如果想得到互相独立的两个对象就要使用Clone方法,如上图,经Clone后,任何一个对象的改变都不会影响另一个对象。Object1上Object2是两个不同的引用.
如果一个欲实现 Clone 方法的对象其成员中还包含其它对象 ,那么那些对象同样要实现Clone方法(也就是说,同样要实现 Cloneable 接口) ,而且在上一层的对象中要调用这些 Clone 方法.以此类推,直到最后一个对象中没有对象值为止(深拷贝) .
在JAVA中,要实现 Clone方法就要实现 Cloneable 接口,但是 Clone 方法不是这个接口的方法,它只是一个标识接口 。Clone 方法是从 Object 对象继承下来的protected方法,在实现它的时候要声明为public 。在使用Clone的时候还要记得进行类型转换,因为Clone 方法返回的是一个Object 。
下面是一小段简单的代码,是对对象克隆的实验:
package js;
/**
* ImplClone
* @author jiashuai
*
*/
public class ImplClone implements Cloneable {
private String name = "";
private Birth birth;
/** Creates a new instance of ImplClone */
public ImplClone() {
this.setName("bill");
this.birth = new Birth();
}
public Object clone() {
ImplClone cloned = new ImplClone();
try {
cloned = (ImplClone) super.clone();
cloned.birth = (Birth) this.birth.clone();
} catch (CloneNotSupportedException ex) {
ex.printStackTrace();
}
//cloned.birth = (Birth) birth.clone();
return cloned;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Birth getBirth() {
return birth;
}
public void setBirth(Birth birth) {
this.birth = birth;
}
}
/**
* Birth
* @author jiashuai
*
*/
class Birth implements Cloneable {
private String year;
private String month;
private String day;
public Birth(String year, String month, String day) {
this.year = year;
this.month = month;
this.day = day;
}
public Birth() {
}
public String getYear() {
return year;
}
public void setYear(String year) {
this.year = year;
}
public String getMonth() {
return month;
}
public void setMonth(String month) {
this.month = month;
}
public String getDay() {
return day;
}
public void setDay(String day) {
this.day = day;
}
public Object clone() {
Birth cloned = new Birth();
try {
cloned = (Birth) super.clone();
} catch (CloneNotSupportedException ex) {
ex.printStackTrace();
}
return cloned;
}
}
package js;
/**
* CloneMain
* @author jiashuai
*
*/
public class CloneMain {
/** Creates a new instance of CloneMain */
public CloneMain() {
}
public static void main(String[] args){
ImplClone cloneObj=new ImplClone();
cloneObj.setName("Bill");
cloneObj.setBirth(new Birth("2004","03","01"));
System.out.println(cloneObj.getBirth().getYear()+" :1");
//ImplClone cloned=new ImplClone();
//cloned=cloneObj;
ImplClone cloned=(ImplClone)cloneObj.clone();
cloned.getBirth().setYear("1990"); //改变克隆对象的数据
System.out.println(cloneObj.getBirth().getYear()+" :2"); //输出原对象
System.out.println(cloned.getBirth().getYear()+" :3"); //输出改变数据后的克隆对象
}
}
二、 用字节(byte) 截取字符串的问题
( 用字节(byte) 截取字符串的问题 )
编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”。
import java.io.UnsupportedEncodingException;
public class TestSubString {
/**
* 变成 byte[] 截取,然后在转换成一个新的 String 如果旧的 String 包含 新的 String
* 说明没有截取到半个汉字,如果不包含说明截取到半个汉字了,
* 那么就少截取一个字符来防止半个字符存在
* @param str
* @param sub
* @return
* @throws UnsupportedEncodingException
*/
public String test(String str,int sub) throws UnsupportedEncodingException{
String retVal = "";
if (str == null || str.equals("") || sub == 0) {
return retVal;
}
byte[] b = str.getBytes("GBK");
if (b.length < sub) {
return str;
}
byte[] temp = new byte[sub];
for (int i = 0; i < sub; i++) {
temp[i] = b[i];
}
String tempStr = new String(temp);
if (str.contains(tempStr)) {
return tempStr;
}
temp = new byte[sub-1];
for (int i = 0; i < sub-1; i++) {
temp[i] = b[i];
}
retVal = new String(temp);
return retVal;
}
/**
* 打印字符串在指定编码下的字节数和编码名称到控制台
*
* @param s
* 字符串
* @param encodingName
* 编码格式
*/
public void printByteLength(String s, String encodingName) {
System.out.print("字节数:");
try {
System.out.print(s.getBytes(encodingName).length);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println(";编码:" + encodingName);
}
public static void main(String[] args) throws UnsupportedEncodingException {
TestSubString tss = new TestSubString();
String en = "A";
String ch = "人";
// 计算一个英文字母在各种编码下的字节数
System.out.println("英文字母:" + en);
tss.printByteLength(en, "GB2312");
tss.printByteLength(en, "GBK");
tss.printByteLength(en, "GB18030");
tss.printByteLength(en, "ISO-8859-1");
tss.printByteLength(en, "UTF-8");
tss.printByteLength(en, "UTF-16");
tss.printByteLength(en, "UTF-16BE");
tss.printByteLength(en, "UTF-16LE");
System.out.println();
// 计算一个中文汉字在各种编码下的字节数
System.out.println("中文汉字:" + ch);
tss.printByteLength(ch, "GB2312");
tss.printByteLength(ch, "GBK");
tss.printByteLength(ch, "GB18030");
tss.printByteLength(ch, "ISO-8859-1");
tss.printByteLength(ch, "UTF-8");
tss.printByteLength(ch, "UTF-16");
tss.printByteLength(ch, "UTF-16BE");
tss.printByteLength(ch, "UTF-16LE");
//-------------------------------------------------------------------------------
System.out.println();
String t1 = "我ABC汉DEF";
System.out.println(tss.test(t1, 4));
System.out.println(tss.test(t1, 6));
}
}
运行结果:
英文字母:A
字节数:1;编码:GB2312
字节数:1;编码:GBK
字节数:1;编码:GB18030
字节数:1;编码:ISO-8859-1
字节数:1;编码:UTF-8
字节数:4;编码:UTF-16
字节数:2;编码:UTF-16BE
字节数:2;编码:UTF-16LE
中文汉字:人
字节数:2;编码:GB2312
字节数:2;编码:GBK
字节数:2;编码:GB18030
字节数:1;编码:ISO-8859-1
字节数:3;编码:UTF-8
字节数:4;编码:UTF-16
字节数:2;编码:UTF-16BE
字节数:2;编码:UTF-16LE
我AB
我ABC
--------------------------------
可知,GB2312、GBK、GB18030 三种编码格式都符合题目要求
注意 .java 文件也要是相同编码,否则控制台显示乱码
三、 Java URL 工具类(解决 url 和 java 之间的乱码问题)
Java URL 工具类
package ssh.util;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
/**
* URL工具
* @author gary
*
*/
public class URLUtil {
/**
* 对url进行编码
*/
public static String encodeURL(String url) {
try {
return URLEncoder.encode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
/**
* 对url进行解码
* @param url
* @return
*/
public static String decodeURL(String url){
try {
return URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
/**
* 判断URL地址是否存在
* @param url
* @return
*/
public static boolean isURLExist(String url) {
try {
URL u = new URL(url);
HttpURLConnection urlconn = (HttpURLConnection) u.openConnection();
int state = urlconn.getResponseCode();
if (state == 200) {
return true;
} else {
return false;
}
} catch (Exception e) {
return false;
}
}
/**
* 将请求参数还原为key=value的形式,for struts2
* @param params
* @return
*/
public static String getParamString(Map<?, ?> params) {
StringBuffer queryString = new StringBuffer(256);
Iterator<?> it = params.keySet().iterator();
int count = 0;
while (it.hasNext()) {
String key = (String) it.next();
String[] param = (String[]) params.get(key);
for (int i = 0; i < param.length; i++) {
if (count == 0) {
count++;
} else {
queryString.append("&");
}
queryString.append(key);
queryString.append("=");
try {
queryString.append(URLEncoder.encode((String) param[i], "UTF-8"));
} catch (UnsupportedEncodingException e) {
}
}
}
return queryString.toString();
}
/**
* 获得请求的路径及参数
* @param request
* @return
*/
public static String getRequestURL(HttpServletRequest request) {
StringBuffer originalURL = new StringBuffer(request.getServletPath());
Map<?,?> parameters = request.getParameterMap();
if (parameters != null && parameters.size() > 0) {
originalURL.append("?");
originalURL.append(getParamString(parameters));
}
return originalURL.toString();
}
/**
* 抓取网页内容,自动识别编码
* @param urlString
* @return
*/
public static String url2Str(String urlString) {
try {
StringBuffer html = new StringBuffer();
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
URLConnection c = url.openConnection();
c.connect();
String contentType = c.getContentType();
String characterEncoding = null;
int index = contentType.indexOf("charset=");
if(index == -1){
characterEncoding = "UTF-8";
}else{
characterEncoding = contentType.substring(index + 8, contentType.length());
}
InputStreamReader isr = new InputStreamReader(conn.getInputStream(), characterEncoding);
BufferedReader br = new BufferedReader(isr);
String temp;
while ((temp = br.readLine()) != null) {
html.append(temp).append("\n");
}
br.close();
isr.close();
return html.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
String content = URLUtil.url2Str("http://www.baidu.com");;
System.out.println(content);
}
}
JS 中 编码解码
encodeURI(URI) decodeURI(encodedURI);
四、 Java HTML 工具类
Java HTML 工具类
package ssh.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* HTML工具
* @author gary
*
*/
public class HTMLUtil {
//>
public static final String GT = ">";
//<
public static final String LT = "<";
//"
public static final String QUOT = """;
//&
public static final String AMP = "&";
//空格
public static final String SPACE = " ";
//©
public static final String COPYRIGHT = "©";
//®
public static final String REG = "®";
//™
public static final String TM = "™";
//¥
public static final String RMB = "¥";
/**
* 删除script标签
* @param str
* @return
*/
public static String delScriptTag(String str){
String regEx_script = "<script[^>]*?>[\\s\\S]*?<\\/script>";
Pattern p_script = Pattern.compile(regEx_script,Pattern.CASE_INSENSITIVE);
Matcher m_script = p_script.matcher(str);
str = m_script.replaceAll("");
return str.trim();
}
/**
* 删除style标签
* @param str
* @return
*/
public static String delStyleTag(String str){
String regEx_style="<style[^>]*?>[\\s\\S]*?<\\/style>";
Pattern p_style = Pattern.compile(regEx_style,Pattern.CASE_INSENSITIVE);
Matcher m_style = p_style.matcher(str);
str = m_style.replaceAll("");
return str;
}
/**
* 删除HTML标签
* @param str
* @return
*/
public static String delHTMLTag(String str){
String regEx_html = "<[^>]+>";
Pattern p_html = Pattern.compile(regEx_html,Pattern.CASE_INSENSITIVE);
Matcher m_html = p_html.matcher(str);
str = m_html.replaceAll("");
return str;
}
/**
* 删除所有标签
* @param str
* @return
*/
public static String delAllTag(String str){
//删script
str = delScriptTag(str);
//删style
str = delStyleTag(str);
//删HTML
str = delHTMLTag(str);
return str;
}
/**
* 清除标签,恢复HTML转义字符
* @param str
* @return
*/
public static String clean(String str){
str = delAllTag(str);
str = str.replaceAll(SPACE, " ");
str = str.replaceAll(GT, ">");
str = str.replaceAll(LT, "<");
str = str.replaceAll(QUOT, "\"");
str = str.replaceAll(AMP, "&");
str = str.replaceAll(COPYRIGHT, "©");
str = str.replaceAll(REG,"®");
str = str.replaceAll(TM,"™");
str = str.replaceAll(RMB,"¥");
return str;
}
/**
* 过滤指定标签
* @param str
* @param tag
* 指定标签
* @return String
*/
public static String fiterHtmlTag(String str, String tag) {
String regxp = "<\\s*" + tag + "\\s+([^>]*)\\s*>";
Pattern pattern = Pattern.compile(regxp);
Matcher matcher = pattern.matcher(str);
StringBuffer sb = new StringBuffer();
boolean result1 = matcher.find();
while (result1) {
matcher.appendReplacement(sb, "");
result1 = matcher.find();
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 替换指定的标签
* @param str
* @param beforeTag
* 要替换的标签
* @param tagAttrib
* 要替换的标签属性值
* @param startTag
* 新标签开始标记
* @param endTag
* 新标签结束标记
* @return String
* example: 替换img标签的src属性值为[img]属性值[/img]
*/
public static String replaceHtmlTag(String str, String beforeTag,
String tagAttrib, String startTag, String endTag) {
String regxpForTag = "<\\s*" + beforeTag + "\\s+([^>]*)\\s*>";
String regxpForTagAttrib = tagAttrib + "=\"([^\"]+)\"";
Pattern patternForTag = Pattern.compile(regxpForTag);
Pattern patternForAttrib = Pattern.compile(regxpForTagAttrib);
Matcher matcherForTag = patternForTag.matcher(str);
StringBuffer sb = new StringBuffer();
boolean result = matcherForTag.find();
while (result) {
StringBuffer sbreplace = new StringBuffer();
Matcher matcherForAttrib = patternForAttrib.matcher(matcherForTag
.group(1));
if (matcherForAttrib.find()) {
matcherForAttrib.appendReplacement(sbreplace, startTag
+ matcherForAttrib.group(1) + endTag);
}
matcherForTag.appendReplacement(sb, sbreplace.toString());
result = matcherForTag.find();
}
matcherForTag.appendTail(sb);
return sb.toString();
}
public static void main(String[] args) {
System.out.println(clean(URLUtil.url2Str("http://www.baidu.com")));
}
}
五、 用 Java 判断一个URL是否有效
针对一些 URL 地址进行检测是否可用,使用 java.net 下的类来实现,主要用到了 URL 和 HttpURLConnection 二个类 , URL 是统一资源标识符的引用,一个 URL 实例代表着一个 url 的引用,开始使用了 URL 中的的 openStream() 方法,这样使用倒是可以,但是速度慢,代码如下:
try {
url = new URL("http://127.0.0.1/sj/user/getUser");
in = url.openStream();
} catch (Exception e1) {
System.out.println("连接打不开!");
url = null;
}
下面判断 url 是不是 null 就可以了,速度慢
最后使用了HttpURLConnection 中的 getResponseCode() ;方法,HttpURLConnection : 通常一个HttpURLConnection 的实例可以生成一个请求,它有个方法 getResponseCode() ;可以得到请求的响应状态,该方法返回一个 int 分别是 200 and 404 如无法从响应中识别任何代码则返回 -1 ,代码如下:
import java.net.HttpURLConnection;
import java.net.URL;
public class URLAvailability {
private static URL url;
private static HttpURLConnection con;
private static int state = -1;
/**
* 功能:检测当前URL是否可连接或是否有效, 描述:最多连接网络 5 次, 如果 5 次都不成功,视为该地址不可用
*
* @param urlStr 指定URL网络地址
* @return URL
*/
public synchronized URL isConnect(String urlStr) {
int counts = 0;
if (urlStr == null || urlStr.length() <= 0) {
return null;
}
while (counts < 5) {
try {
url = new URL(urlStr);
con = (HttpURLConnection) url.openConnection();
state = con.getResponseCode();
System.out.println(counts + "= " + state);
if (state == 200) {
System.out.println("URL可用!");
}
break;
} catch (Exception ex) {
counts++;
System.out.println("URL不可用,连接第 " + counts + " 次");
urlStr = null;
continue;
}
}
return url;
}
}
六、 Java 对象序列化
java对象序列化
所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。这个过程也可以通过网络实现,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新"装配"。像RMI、Socket、JMS、EJB它们中的一种,彼此为什么能够传递Java对象,当然都是对象序列化机制的功劳。
java对象序列化机制一般来讲有两种用途:
1. Java的JavaBeans: Bean的状态信息通常是在设计时配置的,Bean的状态信息必须被存起来,以便当程序运行时能恢复这些状态信息,这需要将对象的状态保存到文件中,而后能够通过读入对象状态来重新构造对象,恢复程序状态。
2. RMI允许象在本机上一样操作远程机器上的对象;或使用套接字在网络上传送对象的程序来说,这些都是需要实现serializaiton机制的。
我们通过让类实现java.io.Serializable 接口可以将类序列化。这个接口是一个制造者(marker)接口。也就是说,对于要实现它的类来说,该接口不需要实现任何方法。它主要用来通知Java虚拟机(JVM),需要将一个对象序列化。
对于这个,有几点我们需要明确:
(1). 并非所有类都可以序列化,在cmd下,我们输入serialver java.net.Socket,可以得到socket是否可序列化的信息,实际上socket是不可序列化的。
(2). java有很多基础类已经实现了serializable接口,比如string,vector等。但是比如hashtable就没有实现serializable接口。
将对象读出或者写入流的主要类有两个: ObjectOutputStream与ObjectInputStream 。ObjectOutputStream 提供用来将对象写入输出流的writeObject方法, ObjectInputStream提供从输入流中读出对象的readObject方法。使用这些方法的对象必须已经被序列化的。也就是说,必须已经实现 Serializable接口。如果你想writeobject一个hashtable对象,那么,会得到一个异常。
序列化的过程就是对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常简单、强大,在RMI、Socket、JMS、EJB都有应用。对象序列化问题在网络编程中并不是最激动人心的课题,但却相当重要,具有许多实用意义。
1. 对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
2. java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
java序列化比较简单,通常不需要编写保存和恢复对象状态的定制代码。实现java.io.Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。这里要注意:不是每个类都可序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。
序列化机制:
序列化分为两大部分:序列化 和反序列化 。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。下面我们分两大部分来阐述:
处理对象流:
(序列化过程和反序列化过程)
java.io包有两个序列化对象的类。ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。
我们先了解ObjectOutputStream类吧。ObjectOutputStream类扩展DataOutput接口。
writeObject() 方法是最重要的方法,用于对象序列化。如果对象包含其他对象的引用,则writeObject()方法递归序列化这些对象。每个 ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。(这点很重要)由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
下面,让我们从例子中来了解ObjectOutputStream这个类吧。
// 序列化 today's date 到一个文件中.
FileOutputStream f = new FileOutputStream ("tmp" );
ObjectOutputStream s = new ObjectOutputStream (f);
s.writeObject("Today" );
s.writeObject(new Date ());
s.flush();
现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。 ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。 readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出 ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
例子如下:
//从文件中反序列化 string 对象和 date 对象
FileInputStream in = new FileInputStream ("tmp" );
ObjectInputStream s = new ObjectInputStream (in);
String today = (String )s.readObject();
Date date = (Date )s.readObject();
定制序列化过程:
序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为serializable,但仍可手工控制声明为static或transient的数据成员。
例子:一个非常简单的序列化类。
public class simpleSerializableClass implements Serializable {
String sToday="Today:" ;
transient Date dtToday=new Date ();
}
序列化时,类的所有数据成员应可序列化除了声明为transient 或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与 readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
关于如何使用定制序列化的部分代码如下:
//重写writeObject()方法以便处理transient的成员。
public void writeObject(ObjectOutputStream outputStream) throws IOException {
outputStream.defaultWriteObject();//使定制的writeObject()方法可以
利用自动序列化中内置的逻辑。
outputStream.writeObject(oSocket.getInetAddress());
outputStream.writeInt(oSocket.getPort());
}
//重写readObject()方法以便接收transient的成员。
private void readObject(ObjectInputStream inputStream) throws IOException ,ClassNotFoundException {
inputStream.defaultReadObject();//defaultReadObject()补充自动序列化
InetAddress oAddress=(InetAddress )inputStream.readObject();
int iPort =inputStream.readInt();
oSocket = new Socket (oAddress,iPort);
iID=getID();
dtToday =new Date ();
}
完全定制序列化过程:
如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流.类实现 Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现 Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。