發(fā)現(xiàn)這個社區(qū)不錯,所以也湊個熱鬧。
第一篇日志,一定要動手寫才有誠意。
這兩天要給剛做的外網(wǎng)系統(tǒng)登錄頁面加驗(yàn)證碼,以前沒做過。上網(wǎng)搜了一下,資料很多。<?xml version="1.0" encoding="gb2312"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans default-autowire="byName"> <bean id="captchaServlet" class="com.jawava.XXXX.XXX.TopImageCaptchaServlet" /> <bean id="captchaService" class="com.octo.captcha.service.multitype.GenericManageableCaptchaService"> <description>驗(yàn)證碼服務(wù)</description> <constructor-arg index="0"><ref bean="imageEngine"/></constructor-arg> <constructor-arg index="1"><value>300</value></constructor-arg><!--超時時間 秒--> <constructor-arg index="2"><value>20000</value></constructor-arg><!--最大并發(fā)數(shù)--> <constructor-arg index="3"><value>20000</value></constructor-arg>& lt;!--第四個參數(shù)官網(wǎng)示例上沒有給出,會報錯,后來看了API才知道少了個參數(shù)--> </bean> <bean id="imageEngine" class="com.octo.captcha.engine.GenericCaptchaEngine"> <description>圖片引擎</description> <constructor-arg index="0"> <list> <ref bean="CaptchaFactory"/> </list> </constructor-arg> </bean> <bean id="CaptchaFactory" class="com.octo.captcha.image.gimpy.GimpyFactory" > <description>驗(yàn)證碼工廠</description> <constructor-arg><ref bean="wordgen"/></constructor-arg> <constructor-arg><ref bean="wordtoimage"/></constructor-arg> </bean> <bean id="wordgen" class= "com.octo.captcha.component.word.wordgenerator.RandomWordGenerator" > <description>文字產(chǎn)生器,提供了好幾種實(shí)現(xiàn),經(jīng)過比較選用了這種</description> <constructor-arg index="0"><value>0123456789</value></constructor-arg> </bean> <bean id="wordtoimage" class="com.octo.captcha.component.image.wordtoimage.ComposedWordToImage" > <description>圖片生成器</description> <constructor-arg index="0"><ref bean="fontGenRandom"/></constructor-arg> <constructor-arg index="1"><ref bean="backGenUni"/></constructor-arg> <constructor-arg index="2"><ref bean="simpleWhitePaster"/></constructor-arg> </bean> <bean id="fontGenRandom" class="com.octo.captcha.component.image.fontgenerator.RandomFontGenerator" > <description>文字轉(zhuǎn)換圖片</description> <constructor-arg index="0"><value>20</value></constructor-arg><!--字體最小尺寸--> <constructor-arg index="1"><value>20</value></constructor-arg><!--字體最大尺寸--> </bean> <bean id="backGenUni" class="com.octo.captcha.component.image.backgroundgenerator.GradientBackgroundGenerator" > <constructor-arg index="0"><value>62</value></constructor-arg><!--背景圖片寬度--> <constructor-arg index="1"><value>22</value></constructor-arg><!--背景圖片高度--> <constructor-arg type="java.awt.Color" index="2"> <ref bean="colorGrey"/> </constructor-arg> <constructor-arg type="java.awt.Color" index="3"> <ref bean="colorGreen"/> </constructor-arg> </bean> <bean id="simpleWhitePaster" class="com.octo.captcha.component.image.textpaster.SimpleTextPaster" > <constructor-arg type="java.lang.Integer" index="0"> <value>4</value><!--字符最少個數(shù)--> </constructor-arg> <constructor-arg type="java.lang.Integer" index="1"> <value>4</value><!--字符最多個數(shù)--> </constructor-arg> <constructor-arg type="java.awt.Color" index="2"> <ref bean="colorFont"/> </constructor-arg> </bean> <bean id="colorGrey" class="java.awt.Color" > <constructor-arg index="0"><value>200</value></constructor-arg> <constructor-arg index="1"><value>255</value></constructor-arg> <constructor-arg index="2"><value>200</value></constructor-arg> </bean> <bean id="colorGreen" class="java.awt.Color" > <constructor-arg index="0"><value>110</value></constructor-arg> <constructor-arg index="1"><value>120</value></constructor-arg> <constructor-arg index="2"><value>200</value></constructor-arg> </bean> <bean id="colorFont" class="java.awt.Color" > <constructor-arg index="0"><value>60</value></constructor-arg> <constructor-arg index="1"><value>60</value></constructor-arg> <constructor-arg index="2"><value>60</value></constructor-arg> </bean> </beans> |
@Override public void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException, RuntimeException { byte[] captchaChallengeAsJpeg = null; //輸出jpg的字節(jié)流 ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream(); try { // get the session id that will identify the generated captcha. //the same id must be used to validate the response, the session id is a good candidate! String captchaId = httpServletRequest.getSession().getId(); // call the ImageCaptchaService getChallenge method BufferedImage challenge = (BufferedImage) captchaService.getChallengeForID(captchaId, httpServletRequest.getLocale()); // a jpeg encoder JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream); jpegEncoder.encode(challenge); } catch (IllegalArgumentException e) { httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND); return; } catch (CaptchaServiceException e) { httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } captchaChallengeAsJpeg = jpegOutputStream.toByteArray(); // flush it in the response httpServletResponse.setHeader("Cache-Control", "no-store"); httpServletResponse.setHeader("Pragma", "no-cache"); httpServletResponse.setDateHeader("Expires", 0); httpServletResponse.setContentType("image/jpeg"); ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream(); responseOutputStream.write(captchaChallengeAsJpeg); responseOutputStream.flush(); responseOutputStream.close(); } |
<servlet> <servlet-name>CaptchaProxy</servlet-name> <servlet-class>com.jawava.XXXXX.XXX.TopHttpServletProxy</servlet-class> <init-param> <param-name>targetServlet</param-name> <param-value>captchaServlet</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>CaptchaProxy</servlet-name> <url-pattern>/topJcaptcha</url-pattern> </servlet-mapping> |
Boolean isResponseCorrect =Boolean.FALSE; //需要sessionId 來驗(yàn)證校驗(yàn)碼 String sessionId = request.getSession().getId(); //首先校驗(yàn)驗(yàn)證碼 try { isResponseCorrect = captchaService.validateResponseForID(sessionId, captcha_input); if(!isResponseCorrect) { throw new RuntimeException("輸入的驗(yàn)證碼有誤,請重新輸入"); } } catch (CaptchaServiceException e) { //should not happen, may be thrown if the id is not valid throw new RuntimeException("校驗(yàn)驗(yàn)證碼時出現(xiàn)不明錯誤",e); } |
Vector 還是ArrayList――哪一個更好,為什么?
要回答這個問題不能一概而論,有時候使用Vector比較好;有時是ArrayList,有時候這兩個都不是
最好的選擇。你別指望能夠獲得一個簡單肯定答案,因?yàn)檫@要看你用它們干什么。下面有4個要考慮
的因素:
l API
l 同步處理
l 數(shù)據(jù)增長性
l 使用模式
下面針對這4個方面進(jìn)行一一探討
API
在由Ken Arnold等編著的《Java Programming Language》(Addison-Wesley, June 2000)一書中有這
樣的描述,Vector類似于ArrayList.。所有從API的角度來看這兩個類非常相[b]似。但他們之間也還
是有一些主要的區(qū)別的。
同步性
Vector是同步的。這個類中的一些方法保證了Vector中的對象是線程安全的。而ArrayList則是異步
的,因此ArrayList中的對象并不是線程安全的。因?yàn)橥降囊髸绊憟?zhí)行的效率,所以如果你不
需要線程安全的集合那么使用ArrayList是一個很好的選擇,這樣可以避免由于同步帶來的不必要的
性能開銷。
數(shù)據(jù)增長
從內(nèi)部實(shí)現(xiàn)機(jī)制來講ArrayList和Vector都是使用數(shù)組(Array)來控制集合中的對象。當(dāng)你向這兩種類
型中增加元素的時候,如果元素的數(shù)目超出了內(nèi)部數(shù)組目前的長度它們都需要擴(kuò)展內(nèi)部數(shù)組的長度,
Vector缺省情況下自動增長原來一倍的數(shù)組長度,ArrayList是原來的50%,所以最后你獲得的這個集
合所占的空間總是比你實(shí)際需要的要大。所以如果你要在集合中保存大量的數(shù)據(jù)那么使用Vector有一
些優(yōu)勢,因?yàn)槟憧梢酝ㄟ^設(shè)置集合的初始化大小來避免不必要的資源開銷。
使用模式
在ArrayList和Vector中,從一個指定的位置(通過索引)查找數(shù)據(jù)或是在集合的末尾增加、移除一
個元素所花費(fèi)的時間是一樣的,這個時間我們用O(1)表示。但是,如果在集合的其他位置增加或移除
元素那么花費(fèi)的時間會呈線形增長:O(n-i),其中n代表集合中元素的個數(shù),i代表元素增加或移除元
素的索引位置。為什么會這樣呢?以為在進(jìn)行上述操作的時候集合中第i和第i個元素之后的所有元素
都要執(zhí)行位移的操作。這一切意味著什么呢?
這意味著,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或
ArrayList都可以。如果是其他操作,你最好選擇其他的集合操作類。比如,LinkList集合類在增加
或移除集合中任何位置的元素所花費(fèi)的時間都是一樣的—O(1),但它在索引一個元素的使用缺比較慢
-O(i),其中i是索引的位置.使用ArrayList也很容易,因?yàn)槟憧梢院唵蔚氖褂盟饕齺泶鎰?chuàng)建
iterator對象的操作。LinkList也會為每個插入的元素創(chuàng)建對象,所有你要明白它也會帶來額外的開
銷。
最后,在《Practical Java》一書中Peter Haggar建議使用一個簡單的數(shù)組(Array)來代替Vector
或ArrayList。尤其是對于執(zhí)行效率要求高的程序更應(yīng)如此。因?yàn)槭褂脭?shù)組(Array)避免了同步、額外
的方法調(diào)用和不必要的重新分配空間的操作。
摘要: JAVA相關(guān)基礎(chǔ)知識1、面向?qū)ο蟮奶卣饔心男┓矫?1.抽象:抽象就是忽略一個主題中與當(dāng)前目標(biāo)無關(guān)的那些方面,以便更充分地注意與當(dāng)前目標(biāo)有關(guān)的方面。抽象并不打算了解全部問題,而只是選擇其中的一部分,暫時不用部分細(xì)節(jié)。抽象包括兩個方面,一是過程抽象,二是數(shù)據(jù)抽象。2.繼承:繼承是一種聯(lián)結(jié)類的層次模型,并且允許和鼓勵類的重用,它提供了一種明確表述共性的方法。對象的一個新類可以從現(xiàn)有的類中派生,這個過程稱... 閱讀全文2.繼承Thread類實(shí)現(xiàn)多線程
繼承Thread類的方法盡管被我列為一種多線程實(shí)現(xiàn)方式,但Thread本質(zhì)上也是實(shí)現(xiàn)了Runnable接口的一個實(shí)例,它代表一個線程的實(shí)例,并且,啟動線程的唯一方法就是通過Thread類的start()實(shí)例方法。start()方法是一個native方法,它將啟動一個新線程,并執(zhí)行run()方法。這種方式實(shí)現(xiàn)多線程很簡單,通過自己的類直接extend Thread,并復(fù)寫run()方法,就可以啟動新線程并執(zhí)行自己定義的run()方法。例如:
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
在合適的地方啟動線程如下:
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
3.實(shí)現(xiàn)Runnable接口方式實(shí)現(xiàn)多線程
如果自己的類已經(jīng)extends另一個類,就無法直接extends Thread,此時,必須實(shí)現(xiàn)一個Runnable接口,如下:
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
為了啟動MyThread,需要首先實(shí)例化一個Thread,并傳入自己的MyThread實(shí)例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
事實(shí)上,當(dāng)傳入一個Runnable target參數(shù)給Thread后,Thread的run()方法就會調(diào)用target.run(),參考JDK源代碼:
public void run() {
if (target != null) {
target.run();
}
}
4.使用ExecutorService、Callable、Future實(shí)現(xiàn)有返回結(jié)果的多線程
ExecutorService、Callable、Future這個對象實(shí)際上都是屬于Executor框架中的功能類。想要詳細(xì)了解Executor框架的可以訪問http://www.javaeye.com/topic/366591 ,這里面對該框架做了很詳細(xì)的解釋。返回結(jié)果的線程是在JDK1.5中引入的新特征,確實(shí)很實(shí)用,有了這種特征我就不需要再為了得到返回值而大費(fèi)周折了,而且即便實(shí)現(xiàn)了也可能漏洞百出。
可返回值的任務(wù)必須實(shí)現(xiàn)Callable接口,類似的,無返回值的任務(wù)必須Runnable接口。執(zhí)行Callable任務(wù)后,可以獲取一個Future的對象,在該對象上調(diào)用get就可以獲取到Callable任務(wù)返回的Object了,再結(jié)合線程池接口ExecutorService就可以實(shí)現(xiàn)傳說中有返回結(jié)果的多線程了。下面提供了一個完整的有返回結(jié)果的多線程測試?yán)樱贘DK1.5下驗(yàn)證過沒問題可以直接使用。代碼如下:
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
/**
* Java線程:有返回值的線程
*
* @author wb_qiuquan.ying
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序開始運(yùn)行----");
Date date1 = new Date();
int taskSize = 5;
// 創(chuàng)建一個線程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 創(chuàng)建多個有返回值的任務(wù)
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 執(zhí)行任務(wù)并獲取Future對象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 關(guān)閉線程池
pool.shutdown();
// 獲取所有并發(fā)任務(wù)的運(yùn)行結(jié)果
for (Future f : list) {
// 從Future對象上獲取任務(wù)的返回值,并輸出到控制臺
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("----程序結(jié)束運(yùn)行----,程序運(yùn)行時間【"
+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable<Object> {
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任務(wù)啟動");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任務(wù)終止");
return taskNum + "任務(wù)返回運(yùn)行結(jié)果,當(dāng)前任務(wù)時間【" + time + "毫秒】";
}
}
代碼說明:
上述代碼中Executors類,提供了一系列工廠方法用于創(chuàng)先線程池,返回的線程池都實(shí)現(xiàn)了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
創(chuàng)建固定數(shù)目線程的線程池。
public static ExecutorService newCachedThreadPool()
創(chuàng)建一個可緩存的線程池,調(diào)用execute 將重用以前構(gòu)造的線程(如果線程可用)。如果現(xiàn)有線程沒有可用的,則創(chuàng)建一個新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。
public static ExecutorService newSingleThreadExecutor()
創(chuàng)建一個單線程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
創(chuàng)建一個支持定時及周期性的任務(wù)執(zhí)行的線程池,多數(shù)情況下可用來替代Timer類。
ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor后臺線程池還沒有完成Callable的計算,這調(diào)用返回Future對象的get()方法,會阻塞直到計算完成。
String和StringBuffer的區(qū)別,網(wǎng)上資料可以說是數(shù)不勝數(shù),但是看到這篇文章,感覺里面做的小例子很有代表性,所以轉(zhuǎn)一下,并自己做了一點(diǎn)總結(jié)。
在java中有3個類來負(fù)責(zé)字符的操作。
1.Character 是進(jìn)行單個字符操作的,
2.String 對一串字符進(jìn)行操作。不可變類。
3.StringBuffer 也是對一串字符進(jìn)行操作,但是可變類。
String:
是對象不是原始類型.
為不可變對象,一旦被創(chuàng)建,就不能修改它的值.
對于已經(jīng)存在的String對象的修改都是重新創(chuàng)建一個新的對象,然后把新的值保存進(jìn)去.
String 是final類,即不能被繼承.
StringBuffer:
是一個可變對象,當(dāng)對他進(jìn)行修改的時候不會像String那樣重新建立對象
它只能通過構(gòu)造函數(shù)來建立,
StringBuffer sb = new StringBuffer();
note:不能通過付值符號對他進(jìn)行付值.
sb = "welcome to here!";//error
對象被建立以后,在內(nèi)存中就會分配內(nèi)存空間,并初始保存一個null.向StringBuffer
中付值的時候可以通過它的append方法.
sb.append("hello");
字符串連接操作中StringBuffer的效率要比String高:
String str = new String("welcome to ");
str += "here";
的處理步驟實(shí)際上是通過建立一個StringBuffer,讓侯調(diào)用append(),最后
再將StringBuffer toSting();
這樣的話String的連接操作就比StringBuffer多出了一些附加操作,當(dāng)然效率上要打折扣.
并且由于String 對象是不可變對象,每次操作Sting 都會重新建立新的對象來保存新的值.
這樣原來的對象就沒用了,就要被垃圾回收.這也是要影響性能的.
看看以下代碼:
將26個英文字母重復(fù)加了5000次,
可惜我的計算機(jī)不是超級計算機(jī),得到的結(jié)果每次不一定一樣一般為 46687左右。
也就是46秒。
我們再看看以下代碼
得到的結(jié)果為 16 有時還是 0
所以結(jié)論很明顯,StringBuffer 的速度幾乎是String 上萬倍。當(dāng)然這個數(shù)據(jù)不是很準(zhǔn)確。因?yàn)檠h(huán)的次數(shù)在100000次的時候,差異更大。不信你試試。
根據(jù)上面所說:
str += "here";
的處理步驟實(shí)際上是通過建立一個StringBuffer,讓侯調(diào)用append(),最后
再將StringBuffer toSting();
所以str += "here";可以等同于
StringBuffer sb = new StringBuffer(str);
sb.append("here");
str = sb.toString();
所以上面直接利用"+"來連接String的代碼可以基本等同于以下代碼
平均執(zhí)行時間為46922左右,也就是46秒。
總結(jié): 如果在程序中需要對字符串進(jìn)行頻繁的修改連接操作的話.使用StringBuffer性能會更高
===========================================================================================
為什么會出現(xiàn)那么多比較String和StringBuffer的文章?
原因在于當(dāng)改變字符串內(nèi)容時,采用StringBuffer能獲得更好的性能。既然是為了獲得更好的性能,那么采用StringBuffer能夠獲得最好的性能嗎?
答案是NO!
為什么?
如果你讀過《Think in Java》,而且對里面描述HashTable和HashMap區(qū)別的那部分章節(jié)比較熟悉的話,你一定也明白了原因所在。對,就是支持線程同步保證線程安全而導(dǎo)致性能下降的問題。HashTable是線程安全的,很多方法都是synchronized方法,而HashMap不是線程安全的,但其在單線程程序中的性能比HashTable要高。StringBuffer和StringBuilder類的區(qū)別也在于此,新引入的StringBuilder類不是線程安全的,但其在單線程中的性能比StringBuffer高。如果你對此不太相信,可以試試下面的例子:
package com.hct.test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author: chengtai.he
* @created:2009-12-9 上午09:59:57
*/
public class StringBuilderTester {
private static final String base = " base string. ";
private static final int count = 2000000;
public static void stringTest() {
long begin, end;
begin = System.currentTimeMillis();
String test = new String(base);
for (int i = 0; i < count/100; i++) {
test = test + " add ";
}
end = System.currentTimeMillis();
System.out.println((end - begin)
+ " millis has elapsed when used String. ");
}
public static void stringBufferTest() {
long begin, end;
begin = System.currentTimeMillis();
StringBuffer test = new StringBuffer(base);
for (int i = 0; i < count; i++) {
test = test.append(" add ");
}
end = System.currentTimeMillis();
System.out.println((end - begin)
+ " millis has elapsed when used StringBuffer. ");
}
public static void stringBuilderTest() {
long begin, end;
begin = System.currentTimeMillis();
StringBuilder test = new StringBuilder(base);
for (int i = 0; i < count; i++) {
test = test.append(" add ");
}
end = System.currentTimeMillis();
System.out.println((end - begin)
+ " millis has elapsed when used StringBuilder. ");
}
public static String appendItemsToStringBuiler(List list) {
StringBuilder b = new StringBuilder();
for (Iterator i = list.iterator(); i.hasNext();) {
b.append(i.next()).append(" ");
}
return b.toString();
}
public static void addToStringBuilder() {
List list = new ArrayList();
list.add(" I ");
list.add(" play ");
list.add(" Bourgeois ");
list.add(" guitars ");
list.add(" and ");
list.add(" Huber ");
list.add(" banjos ");
System.out.println(StringBuilderTester.appendItemsToStirngBuffer(list));
}
public static String appendItemsToStirngBuffer(List list) {
StringBuffer b = new StringBuffer();
for (Iterator i = list.iterator(); i.hasNext();) {
b.append(i.next()).append(" ");
}
return b.toString();
}
public static void addToStringBuffer() {
List list = new ArrayList();
list.add(" I ");
list.add(" play ");
list.add(" Bourgeois ");
list.add(" guitars ");
list.add(" and ");
list.add(" Huber ");
list.add(" banjos ");
System.out.println(StringBuilderTester.appendItemsToStirngBuffer(list));
}
public static void main(String[] args) {
stringTest();
stringBufferTest();
stringBuilderTest();
addToStringBuffer();
addToStringBuilder();
}
}
上面的程序結(jié)果如下:
5266 millis has elapsed when used String.
375 millis has elapsed when used StringBuffer.
281 millis has elapsed when used StringBuilder.
I play Bourgeois guitars and Huber banjos
I play Bourgeois guitars and Huber banjos
從上面的結(jié)果來看,這三個類在單線程程序中的性能差別一目了然,采用String對象時,即使運(yùn)行次數(shù)僅是采用其他對象的1/100,其執(zhí)行時間仍然比其他對象高出25倍以上;而采用StringBuffer對象和采用StringBuilder對象的差別也比較明顯,前者是后者的1.5倍左右。由此可見,如果我們的程序是在單線程下運(yùn)行,或者是不必考慮到線程同步問題,我們應(yīng)該優(yōu)先使用StringBuilder類;當(dāng)然,如果要保證線程安全,自然非StringBuffer莫屬了。
除了對多線程的支持不一樣外,這兩個類的使用幾乎沒有任何差別,上面的例子就是個很好的說明。appendItemsToStringBuiler和appendItemsToStirngBuffer兩個方法除了采用的對象分別為StringBuilder和StringBuffer外,其他完全相同,而效果也完全相同。