最后,應該補充說明一下,如果不使用rewrite規則,或者通過表單提交數據,其實并不一定會遇到上述問題,因為這時可以在提交數據時指定希望的編碼。另外,中文文件名確實會帶來問題,應該謹慎使用。
6. 其它
下面描述一些和編碼有關的其他問題。
6.1. SecureCRT
除了瀏覽器和控制臺與編碼有關外,一些客戶端也很有關系。比如在使用SecureCRT連接linux時,應該讓SecureCRT的顯示編碼(不同的session,可以有不同的編碼設置)和linux的編碼環境變量保持一致。否則看到的一些幫助信息,就可能是亂碼。
另外,mysql有自己的編碼設置,也應該保持和SecureCRT的顯示編碼一致。否則通過SecureCRT執行sql語句的時候,可能無法處理中文字符,查詢結果也會出現亂碼。
對于Utf-8文件,很多編輯器(比如記事本)會在文件開頭增加三個不可見的標志字節,如果作為mysql的輸入文件,則必須要去掉這三個字符。(用linux的vi保存可以去掉這三個字符)。一個有趣的現象是,在中文windows下,創建一個新txt文件,用記事本打開,輸入"連通"兩個字,保存,再打開,你會發現兩個字沒了,只留下一個小黑點。
6.2. 過濾器
如果需要統一設置編碼,則通過filter進行設置是個不錯的選擇。在filter class中,可以統一為需要的請求或者回應設置編碼。參加上述setCharacterEncoding()。這個類apache已經給出了可以直接使用的例子SetCharacterEncodingFilter。
6.3. POST和GET
很明顯,以POST提交信息時,URL有更好的可讀性,而且可以方便的使用setCharacterEncoding()來處理字符集問題。但GET方法形成的URL能夠更容易表達網頁的實際內容,也能夠用于收藏。
從統一的角度考慮問題,建議采用GET方法,這要求在程序中獲得參數是進行特殊處理,而無法使用setCharacterEncoding()的便利,如果不考慮rewrite,就不存在IE的UTF-8問題,可以考慮通過設置URIEncoding來方便獲取URL中的參數。
6.4. 簡繁體編碼轉換
GBK同時包含簡體和繁體編碼,也就是說同一個字,由于編碼不同,在GBK編碼下屬于兩個字。有時候,為了正確取得完整的結果,應該將繁體和簡體進行統一。可以考慮將UTF、GBK中的所有繁體字,轉換為相應的簡體字,BIG5編碼的數據,也應該轉化成相應的簡體字。當然,仍舊以UTF編碼存儲。
例如,對于"語言 語言",用UTF表示為"\xE8\xAF\xAD\xE8\xA8\x80 \xE8\xAA\x9E\xE8\xA8\x80",進行簡繁體編碼轉換后應該是兩個相同的 "\xE8\xAF\xAD\xE8\xA8\x80>"。
Eceel東西在線 劉科垠
2006-3-8
轉自:http://china.eceel.com/article/study_for_character_encoding_java.htm
posted @
2006-12-12 11:23 保爾任 閱讀(215) |
評論 (0) |
編輯 收藏
這是一篇程序員寫給程序員的趣味讀物。所謂趣味是指可以比較輕松地了解一些原來不清楚的概念,增進知識,類似于打RPG游戲的升級。整理這篇文章的動機是兩個問題:
問題一:
使用Windows記事本的“另存為”,可以在GBK、Unicode、Unicode big endian和UTF-8這幾種編碼方式間相互轉換。同樣是txt文件,Windows是怎樣識別編碼方式的呢?
我很早前就發現Unicode、Unicode big endian和UTF-8編碼的txt文件的開頭會多出幾個字節,分別是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但這些標記是基于什么標準呢?
問題二:
最近在網上看到一個ConvertUTF.c,實現了UTF-32、UTF-16和UTF-8這三種編碼方式的相互轉換。對于Unicode(UCS2)、GBK、UTF-8這些編碼方式,我原來就了解。但這個程序讓我有些糊涂,想不起來UTF-16和UCS2有什么關系。
查了查相關資料,總算將這些問題弄清楚了,順帶也了解了一些Unicode的細節。寫成一篇文章,送給有過類似疑問的朋友。本文在寫作時盡量做到通俗易懂,但要求讀者知道什么是字節,什么是十六進制。
0、big endian和little endian
big endian和little endian是CPU處理多字節數的不同方式。例如“漢”字的Unicode編碼是6C49。那么寫到文件里時,究竟是將6C寫在前面,還是將49寫在前面?如果將6C寫在前面,就是big endian。還是將49寫在前面,就是little endian。
“endian”這個詞出自《格列佛游記》。小人國的內戰就源于吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾發生過六次叛亂,其中一個皇帝送了命,另一個丟了王位。
我們一般將endian翻譯成“字節序”,將big endian和little endian稱作“大尾”和“小尾”。
1、字符編碼、內碼,順帶介紹漢字編碼
字符必須編碼后才能被計算機處理。計算機使用的缺省編碼方式就是計算機的內碼。早期的計算機使用7位的ASCII編碼,為了處理漢字,程序員設計了用于簡體中文的GB2312和用于繁體中文的big5。
GB2312(1980年)一共收錄了7445個字符,包括6763個漢字和682個其它符號。漢字區的內碼范圍高字節從B0-F7,低字節從A1-FE,占用的碼位是72*94=6768。其中有5個空位是D7FA-D7FE。
GB2312支持的漢字太少。1995年的漢字擴展規范GBK1.0收錄了21886個符號,它分為漢字區和圖形符號區。漢字區包括21003個字符。2000年的GB18030是取代GBK1.0的正式國家標準。該標準收錄了27484個漢字,同時還收錄了藏文、蒙文、維吾爾文等主要的少數民族文字。現在的PC平臺必須支持GB18030,對嵌入式產品暫不作要求。所以手機、MP3一般只支持GB2312。
從ASCII、GB2312、GBK到GB18030,這些編碼方法是向下兼容的,即同一個字符在這些方案中總是有相同的編碼,后面的標準支持更多的字符。在這些編碼中,英文和中文可以統一地處理。區分中文編碼的方法是高字節的最高位不為0。按照程序員的稱呼,GB2312、GBK到GB18030都屬于雙字節字符集 (DBCS)。
有的中文Windows的缺省內碼還是GBK,可以通過GB18030升級包升級到GB18030。不過GB18030相對GBK增加的字符,普通人是很難用到的,通常我們還是用GBK指代中文Windows內碼。
這里還有一些細節:
GB2312的原文還是區位碼,從區位碼到內碼,需要在高字節和低字節上分別加上A0。
在DBCS中,GB內碼的存儲格式始終是big endian,即高位在前。
GB2312的兩個字節的最高位都是1。但符合這個條件的碼位只有128*128=16384個。所以GBK和GB18030的低字節最高位都可能不是1。不過這不影響DBCS字符流的解析:在讀取DBCS字符流時,只要遇到高位為1的字節,就可以將下兩個字節作為一個雙字節編碼,而不用管低字節的高位是什么。
2、Unicode、UCS和UTF
前面提到從ASCII、GB2312、GBK到GB18030的編碼方法是向下兼容的。而Unicode只與ASCII兼容(更準確地說,是與ISO-8859-1兼容),與GB碼不兼容。例如“漢”字的Unicode編碼是6C49,而GB碼是BABA。
Unicode也是一種字符編碼方法,不過它是由國際組織設計,可以容納全世界所有語言文字的編碼方案。Unicode的學名是"Universal Multiple-Octet Coded Character Set",簡稱為UCS。UCS可以看作是"Unicode Character Set"的縮寫。
根據維基百科全書(http://zh.wikipedia.org/wiki/)的記載:歷史上存在兩個試圖獨立設計Unicode的組織,即國際標準化組織(ISO)和一個軟件制造商的協會(unicode.org)。ISO開發了ISO 10646項目,Unicode協會開發了Unicode項目。
在1991年前后,雙方都認識到世界不需要兩個不兼容的字符集。于是它們開始合并雙方的工作成果,并為創立一個單一編碼表而協同工作。從Unicode2.0開始,Unicode項目采用了與ISO 10646-1相同的字庫和字碼。
目前兩個項目仍都存在,并獨立地公布各自的標準。Unicode協會現在的最新版本是2005年的Unicode 4.1.0。ISO的最新標準是10646-3:2003。
UCS規定了怎么用多個字節表示各種文字。怎樣傳輸這些編碼,是由UTF(UCS Transformation Format)規范規定的,常見的UTF規范包括UTF-8、UTF-7、UTF-16。
IETF的RFC2781和RFC3629以RFC的一貫風格,清晰、明快又不失嚴謹地描述了UTF-16和UTF-8的編碼方法。我總是記不得IETF是Internet Engineering Task Force的縮寫。但IETF負責維護的RFC是Internet上一切規范的基礎。
3、UCS-2、UCS-4、BMP
UCS有兩種格式:UCS-2和UCS-4。顧名思義,UCS-2就是用兩個字節編碼,UCS-4就是用4個字節(實際上只用了31位,最高位必須為0)編碼。下面讓我們做一些簡單的數學游戲:
UCS-2有2^16=65536個碼位,UCS-4有2^31=2147483648個碼位。
UCS-4根據最高位為0的最高字節分成2^7=128個group。每個group再根據次高字節分為256個plane。每個plane根據第3個字節分為256行 (rows),每行包含256個cells。當然同一行的cells只是最后一個字節不同,其余都相同。
group 0的plane 0被稱作Basic Multilingual Plane, 即BMP。或者說UCS-4中,高兩個字節為0的碼位被稱作BMP。
將UCS-4的BMP去掉前面的兩個零字節就得到了UCS-2。在UCS-2的兩個字節前加上兩個零字節,就得到了UCS-4的BMP。而目前的UCS-4規范中還沒有任何字符被分配在BMP之外。
4、UTF編碼
UTF-8就是以8位為單元對UCS進行編碼。從UCS-2到UTF-8的編碼方式如下:
UCS-2編碼(16進制) UTF-8 字節流(二進制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如“漢”字的Unicode編碼是6C49。6C49在0800-FFFF之間,所以肯定要用3字節模板了:1110xxxx 10xxxxxx 10xxxxxx。將6C49寫成二進制是:0110 110001 001001, 用這個比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
讀者可以用記事本測試一下我們的編碼是否正確。
UTF-16以16位為單元對UCS進行編碼。對于小于0x10000的UCS碼,UTF-16編碼就等于UCS碼對應的16位無符號整數。對于不小于0x10000的UCS碼,定義了一個算法。不過由于實際使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以認為UTF-16和UCS-2基本相同。但UCS-2只是一個編碼方案,UTF-16卻要用于實際的傳輸,所以就不得不考慮字節序的問題。
5、UTF的字節序和BOM
UTF-8以字節為編碼單元,沒有字節序的問題。UTF-16以兩個字節為編碼單元,在解釋一個UTF-16文本前,首先要弄清楚每個編碼單元的字節序。例如收到一個“奎”的Unicode編碼是594E,“乙”的Unicode編碼是4E59。如果我們收到UTF-16字節流“594E”,那么這是“奎”還是“乙”?
Unicode規范中推薦的標記字節順序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一個有點小聰明的想法:
在UCS編碼中有一個叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的編碼是FEFF。而FFFE在UCS中是不存在的字符,所以不應該出現在實際傳輸中。UCS規范建議我們在傳輸字節流前,先傳輸字符"ZERO WIDTH NO-BREAK SPACE"。
這樣如果接收者收到FEFF,就表明這個字節流是Big-Endian的;如果收到FFFE,就表明這個字節流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被稱作BOM。
UTF-8不需要BOM來表明字節順序,但可以用BOM來表明編碼方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF(讀者可以用我們前面介紹的編碼方法驗證一下)。所以如果接收者收到以EF BB BF開頭的字節流,就知道這是UTF-8編碼了。
Windows就是使用BOM來標記文本文件的編碼方式的。
6、進一步的參考資料
本文主要參考的資料是 "Short overview of ISO-IEC 10646 and Unicode" (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)。
我還找了兩篇看上去不錯的資料,不過因為我開始的疑問都找到了答案,所以就沒有看:
"Understanding Unicode A general introduction to the Unicode Standard" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter04a)
"Character set encoding basics Understanding character set encodings and legacy encodings" (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&item_id=IWS-Chapter03)
我寫過UTF-8、UCS-2、GBK相互轉換的軟件包,包括使用Windows API和不使用Windows API的版本。以后有時間的話,我會整理一下放到我的個人主頁上(http://fmddlmyy.home4u.china.com)。
我是想清楚所有問題后才開始寫這篇文章的,原以為一會兒就能寫好。沒想到考慮措辭和查證細節花費了很長時間,竟然從下午1:30寫到9:00。希望有讀者能從中受益。
posted @
2006-12-12 11:22 保爾任 閱讀(298) |
評論 (0) |
編輯 收藏
鏈接都已失效,可以在網上搜索。
高效解析XML
詳細解析XML與J2EE組合
初學者入門 JAVA 的 XML 編程實例解析
Java中四種XML解析技術之不完全測試
XML認證教程,第 7 部分
詳細解析XML與J2EE組合技術的精髓
Java高手解析XML配置文件的讀取操作
使用dom4j解析xml??? http://www-128.ibm.com/developerworks/cn/xml/x-dom4j.html
初學者入門 JAVA 的 XML 編程實例解析 http://tech.ccidnet.com/art/1077/20050307/219781_1.html
posted @
2006-12-12 11:21 保爾任 閱讀(810) |
評論 (0) |
編輯 收藏
Calendar與Date、long的轉換:
? Calendar ca = Calendar.getInstance();
??Date d = ca.getTime();
??long l = ca.getTimeInMillis();
??ca.setTime(d);
??ca.setTimeInMillis(l);
Date和long間的轉換:
??Date d = new Date();
??long l = d.getTime();
??d.setTime(l);
??d = new Date(l);
————————————————————————————————
? 當前年月實際的總天數:???
? Calendar ? cal ? = ? new ? GregorianCalendar(); ?
? ?
? int ? year_days ? ? = ? cal.getActualMaximum(Calendar.DAY_OF_YEAR ? ); ?
? int ? month_days ? = ? cal.getActualMaximum(Calendar.DAY_OF_MONTH);???
???
? 可能出現的最大天數:???
????
? int ? month_days ? = ? cal.getMaximum(Calendar.DAY_OF_MONTH); ?
? // ? 這種方式不隨當前日期的影響,如果取 ? 2 ? 月份,總是的到 ? 29 ?
? ?
? ?
? 在給 ? Calendar ? 指定月份時要注意:?????
????
? Java ? 中的月份,0 ? - ? 表示1月份, ? ..... ? ? 11 ? - ? 表示12月份,不要搞錯了喲???保險的方式是,使用常量:Calendar.JANUARY ? ...??
————————————————————————————————
學習日期, 日期格式, 日期的解析和日期的計算
Java 語言的 Calendar,GregorianCalendar (日歷),Date(日期), 和DateFormat(日期格式)組成了Java標準的一個基本但是非常重要的部分. 日期是商業邏輯計算一個關鍵的部分. 所有的開發者都應該能夠計算未來的日期, 定制日期的顯示格式, 并將文本數據解析成日期對象。學習日期, 日期格式, 日期的解析和日期的計算。
我們將討論下面的類:
1、 具體類(和抽象類相對)java.util.Date
2、 抽象類java.text.DateFormat 和它的一個具體子類,java.text.SimpleDateFormat
3、 抽象類java.util.Calendar 和它的一個具體子類,java.util.GregorianCalendar
具體類可以被實例化, 但是抽象類卻不能. 你首先必須實現抽象類的一個具體子類.
1.?? java.util.Date及其格式化
Date 類從Java 開發包(JDK) 1.0 就開始進化, 當時它只包含了幾個取得或者設置一個日期數據的各個部分的方法, 比如說月, 日, 和年. 這些方法現在遭到了批評并且已經被轉移到了Calendar類里去了, 我們將在本文中進一步討論它. 這種改進旨在更好的處理日期數據的國際化格式. 就象在JDK 1.1中一樣, Date 類實際上只是一個包裹類, 它包含的是一個長整型數據, 表示的是從GMT(格林尼治標準時間)1970年, 1 月 1日00:00:00這一刻之前或者是之后經歷的毫秒數.
1.1. 創建java.util.Date
Java統計從1970年1月1日起的毫秒的數量表示日期。也就是說,例如,1970年1月2日,是在1月1日后的86,400,000毫秒。同樣的,1969年12 月31日是在1970年1月1日前86,400,000毫秒。Java的Date類使用long類型紀錄這些毫秒值.因為long是有符號整數,所以日期可以在1970年1月1日之前,也可以在這之后。Long類型表示的最大正值和最大負值可以輕松的表示290,000,000年的時間,這適合大多數人的時間要求。
讓我們看一個使用系統的當前日期和時間創建一個日期對象并返回一個長整數的簡單例子. 這個時間通常被稱為Java 虛擬機(JVM)主機環境的系統時間.
import java.util.Date;
public class DateExample1 {
public static void main(String[] args) {
// Get the system date/time
Date date = new Date();
// 打印出具體的年,月,日,小時,分鐘,秒鐘以及時區
System.out.println(date.getTime());
}??
}
在星期六, 2001年9月29日, 下午大約是6:50的樣子, 上面的例子在系統輸出設備上顯示的結果是 1001803809710. 在這個例子中,值得注意的是我們使用了Date 構造函數創建一個日期對象, 這個構造函數沒有接受任何參數. 而這個構造函數在內部使用了 System.currentTimeMillis() 方法來從系統獲取日期.
//1年前日期
? java.util.Date myDate=new java.util.Date();
? long myTime=(myDate.getTime()/1000)-60*60*24*365;
? myDate.setTime(myTime*1000);
? String mDate=formatter.format(myDate);
//明天日期
? myDate=new java.util.Date();
? myTime=(myDate.getTime()/1000)+60*60*24;
? myDate.setTime(myTime*1000);
? mDate=formatter.format(myDate);
//兩個時間之間的天數
? SimpleDateFormat myFormatter = new SimpleDateFormat("yyyy-MM-dd");
? java.util.Date date= myFormatter.parse("2003-05-1");
? java.util.Date mydate= myFormatter.parse("1899-12-30");
? long day=(date.getTime()-mydate.getTime())/(24*60*60*1000);
//加半小時
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
java.util.Date date1 = format.parse("2002-02-28 23:16:00");
long Time=(date1.getTime()/1000)+60*30;
date1.setTime(Time*1000);
String mydate1=formatter.format(date1);
//年月周求日期
SimpleDateFormat formatter2 = new SimpleDateFormat("yyyy-MM F E");
java.util.Date date2= formatter2.parse("2003-05 5 星期五");
SimpleDateFormat formatter3 = new SimpleDateFormat("yyyy-MM-dd");
String mydate2=formatter3.format(date2);
//求是星期幾
mydate= myFormatter.parse("2001-1-1");
SimpleDateFormat formatter4 = new SimpleDateFormat("E");
String mydate3=formatter4.format(mydate);
?
?
1.2. Date格式化
能以一種用戶明白的格式來顯示這個日期呢? 在這里類java.text.SimpleDateFormat 和它的抽象基類 java.text.DateFormat。那么, 現在我們已經知道了如何獲取從1970年1月1日開始經歷的毫秒數了. 我們如何才format 就派得上用場了.
// 我們能不能用下面的代碼構件出 2001/8/8 8:8
? import java.io.*;
? import java.util.*;
? public class WhatIsDate
? {
??? public static void main(String[] args) {
??????? Date date = new Date(2001, 8, 8, 8, 8, 8);
??????? System.out.println(date);
??? }
? }
Java 的編譯器竟然報如下信息 (Sun JDK1.3, Windows 2000 中文下)
注意:
WhatIsDate.java 使用或覆蓋一個不鼓勵使用的API。
注意:
使用-deprecation重新編譯,以得到詳細信息。!
那么 Date 對象究竟是為了滿足哪個需求呢?看來它不是用來實現基于年/月/日小時:分鐘 的時間表述。我們查看 Java 的文檔,我們看到有 getTime() 方法,它返回的竟然是一個 long 值。
文檔進一步又告訴我們這個值代表了當前系統的時間離1970/1/1 0:0 的毫秒差,而且是在 GMT 時區下(也被稱為 EPOC)。如果我們指定的時間是在此之前的,那它將返回一個負數值。
這個發現讓我們對 Date 對象有了一個全新的認識-Date 存放的是與 EPOC 的偏差值。換而言之我們也可通過 long 類型來表示時間?對了,這個猜想是得到了 Java 的支持:
? // 第二種獲得當前時間的方法
? long dateInMilliSeconds = System.currentTimeMillis();
? // 這時候打印出的只是一串數字而已
? System.out.println(dateInMilliSeconds);
對程序執行效率敏感的程序員可以發現這個方法只是生成一個 Java 的原始類型 (primitive type) long, 不需要實例化一個對象。因此如果我們對時間的處理只是在內部進行時,可以用 long 來代替 Date 對象。
最典型的應用就是在一段代碼開始和結束時,分別獲得系統當前的時間,然后計算出代碼執行所需的時間(微秒級)。
? long start = System.currentTimeMillis();
? // 代碼段
? System.out.println("需要 "+(System.currentTimeMillis()-start)+" 微秒");
那么當我們要把這個 long 值已更為友好的表現形式顯示處理的時候,我們可以用它來構造 Date 對象:
Date date = new Date(dateInMilliSeconds);
System.out.println(date);
我們看到了在 Java 中對時間最為基本的表示,有通過對EPOC 的偏差值進行處理。Date 對象是對它的一個對象的封裝。我們同時也看到了,在現時世界中我們對時間的描述通常是通過"某年某月某日某時某分"來定義的。Date 的顯示(實際上是 toString() 方法)描述了這些信息,但 Java 并不建議我們用這種方式直接來構件 Date 對象。因此我們需要找出哪個對象可以實現這個需求。這就是我們下面就要講述的 Calendar 對象的功能。
在我們進一步研究 Calendar 之前,請記住 Date 只是一個對 long 值(基于 GMT 時區)的對象封裝。它所表現出來的年/月/日小時:分鐘時區的時間表述,只是它的 toString() 方法所提供的。千萬不要為這個假象所迷惑。
假如我們希望定制日期數據的格式, 比方星期六-9月-29日-2001年. 下面的例子展示了如何完成這個工作:
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateExample2 {
public static void main(String[] args) {
SimpleDateFormat bartDateFormat = new SimpleDateFormat("EEEE-MMMM-dd-yyyy"); Date date = new Date();
System.out.println(bartDateFormat.format(date));
}
}
只要通過向SimpleDateFormat 的構造函數傳遞格式字符串"EEE-MMMM-dd-yyyy", 我們就能夠指明自己想要的格式. 你應該可以看見, 格式字符串中的ASCII 字符告訴格式化函數下面顯示日期數據的哪一個部分. EEEE是星期, MMMM是月, dd是日, yyyy是年. 字符的個數決定了日期是如何格式化的.傳遞"EE-MM-dd-yy"會顯示 Sat-09-29-01. 請察看Sun 公司的Web 站點獲取日期格式化選項的完整的指示.
1.3. 文本數據解析成日期對象
假設我們有一個文本字符串包含了一個格式化了的日期對象, 而我們希望解析這個字符串并從文本日期數據創建一個日期對象. 我們將再次以格式化字符串"MM-dd-yyyy" 調用 SimpleDateFormat類, 但是這一次, 我們使用格式化解析而不是生成一個文本日期數據. 我們的例子, 顯示在下面, 將解析文本字符串 "9-29-2001"并創建一個值為001736000000 的日期對象.
通過parse()方法,DateFormat能夠以一個字符串創立一個Date對象。這個方法能拋出ParseException異常,所以你必須使用適當的異常處理技術。
例子程序:
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateExample3 {
public static void main(String[] args) {
// Create a date formatter that can parse dates of
// the form MM-dd-yyyy.
SimpleDateFormat bartDateFormat = new SimpleDateFormat("MM-dd-yyyy");
// Create a string containing a text date to be parsed.
String dateStringToParse = "9-29-2001";
try {
// Parse the text version of the date.
// We have to perform the parse method in a
// try-catch construct in case dateStringToParse
// does not contain a date in the format we are expecting.
Date date = bartDateFormat.parse(dateStringToParse);
// Now send the parsed date as a long value
// to the system output.
System.out.println(date.getTime());
}catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
}
1.4. 使用標準的日期格式化過程
既然我們已經可以生成和解析定制的日期格式了, 讓我們來看一看如何使用內建的格式化過程. 方法 DateFormat.getDateTimeInstance() 讓我們得以用幾種不同的方法獲得標準的日期格式化過程. 在下面的例子中, 我們獲取了四個內建的日期格式化過程. 它們包括一個短的, 中等的, 長的, 和完整的日期格式.
import java.text.DateFormat;
import java.util.Date;
public class DateExample4 {
public static void main(String[] args) {
Date date = new Date();
DateFormat shortDateFormat = DateFormat.getDateTimeInstance(
DateFormat.SHORT, DateFormat.SHORT);
DateFormat mediumDateFormat = DateFormat.getDateTimeInstance(
DateFormat.MEDIUM, DateFormat.MEDIUM);
DateFormat longDateFormat = DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.LONG);
DateFormat fullDateFormat = DateFormat.getDateTimeInstance(
DateFormat.FULL, DateFormat.FULL);
System.out.println(shortDateFormat.format(date)); System.out.println(mediumDateFormat.format(date)); System.out.println(longDateFormat.format(date)); System.out.println(fullDateFormat.format(date));
}
}
注意我們在對 getDateTimeInstance的每次調用中都傳遞了兩個值. 第一個參數是日期風格, 而第二個參數是時間風格. 它們都是基本數據類型int(整型). 考慮到可讀性, 我們使用了DateFormat 類提供的常量: SHORT, MEDIUM, LONG, 和 FULL. 要知道獲取時間和日期格式化過程的更多的方法和選項, 請看Sun 公司Web 站點上的解釋.
運行我們的例子程序的時候, 它將向標準輸出設備輸出下面的內容:
9/29/01 8:44 PM
Sep 29, 2001 8:44:45 PM
September 29, 2001 8:44:45 PM EDT
Saturday, September 29, 2001 8:44:45 PM EDT
2.?? Calendar 日歷類
首先請記住 Calendar 只是一個抽象類, 也就是說你無法直接獲得它的一個實例,換而言之你可以提供一個自己開發的 Calendar 對象。
那究竟什么是一個 Calendar 呢?中文的翻譯就是日歷,那我們立刻可以想到我們生活中有陽(公)歷、陰(農)歷之分。它們的區別在哪呢?
比如有:
月份的定義 - 陽`(公)歷 一年12 個月,每個月的天數各不同;陰(農)歷,每個月固定28天,每周的第一天 - 陽(公)歷星期日是第一天;陰(農)歷,星期一是第一天
實際上,在歷史上有著許多種紀元的方法。它們的差異實在太大了,比如說一個人的生日是"八月八日" 那么一種可能是陽(公)歷的八月八日,但也可以是陰 (農)歷的日期。所以為了計時的統一,必需指定一個日歷的選擇。那現在最為普及和通用的日歷就是 "Gregorian Calendar"。也就是我們在講述年份時常用 "公元幾幾年"。Calendar 抽象類定義了足夠的方法,讓我們能夠表述日歷的規則。Java 本身提供了對 "Gregorian Calendar" 規則的實現。我們從 Calendar.getInstance() 中所獲得的實例就是一個 "GreogrianCalendar" 對象(與您通過 new GregorianCalendar() 獲得的結果一致)。
下面的代碼可以證明這一點:
? import java.io.*;
? import java.util.*;
? public class WhatIsCalendar
? {
??? public static void main(String[] args) {
??????? Calendar calendar = Calendar.getInstance();
??????? if (calendar instanceof GregorianCalendar)
????????? System.out.println("It is an instance of GregorianCalendar");
??? }
? }
?
Calendar 在 Java 中是一個抽象類(Abstract Class),GregorianCalendar 是它的一個具體實現。
Calendar 與 Date 的轉換非常簡單:
? Calendar calendar = Calendar.getInstance();
? // 從一個 Calendar 對象中獲取 Date 對象
? Date date = calendar.getTime();
? // 將 Date 對象反應到一個 Calendar 對象中,
? // Calendar/GregorianCalendar 沒有構造函數可以接受 Date 對象
? // 所以我們必需先獲得一個實例,然后設置 Date 對象
? calendar.setTime(date);
?
?
Calendar 對象在使用時,有一些值得注意的事項:
1. Calendar 的 set() 方法
set(int field, int value) - 是用來設置"年/月/日/小時/分鐘/秒/微秒"等值
field 的定義在 Calendar 中
set (int year, int month, int day, int hour, int minute, int second) 但沒有set (int year, int month, int day, int hour, int minute, int second, int millisecond) 前面 set(int,int,int,int,int,int) 方法不會自動將 MilliSecond 清為 0。
另外,月份的起始值為0而不是1,所以要設置八月時,我們用7而不是8。
calendar.set(Calendar.MONTH, 7);
我們通常需要在程序邏輯中set(Calendar.MILLISECOND, 0),否則可能會出現下面的情況:
//ObjectOutputStream和ObjectOutputStream是對象存儲的類
//Calendar.MILLISECOND如不設為0則會是個與當前系統時間有關的數
? import java.io.*;
? import java.util.*;
? public class WhatIsCalendarWrite
? {
??? public static void main(String[] args) throws Exception{
??????? ObjectOutputStream out =
????????? new ObjectOutputStream(
??????????? new FileOutputStream("calendar.out"));
??????? Calendar cal1 = Calendar.getInstance();
??????? cal1.set(2000, 7, 1, 0, 0, 0);
??????? out.writeObject(cal1);
??????? Calendar cal2 = Calendar.getInstance();
??????? cal2.set(2000, 7, 1, 0, 0, 0);
??????? cal2.set(Calendar.MILLISECOND, 0);
??????? out.writeObject(cal2);
??????? out.close();
??? }
? }
我們將 Calendar 保存到文件中
? import java.io.*;
? import java.util.*;
? public class WhatIsCalendarRead
? {
??? public static void main(String[] args) throws Exception{
??????? ObjectInputStream in =
????????? new ObjectInputStream(
??????????? new FileInputStream("calendar.out"));
??????? Calendar cal2 = (Calendar)in.readObject();
??????? Calendar cal1 = Calendar.getInstance();
??????? cal1.set(2000, 7, 1, 0, 0, 0);
??????? if (cal1.equals(cal2))
????????? System.out.println("Equals");
??????? else
????????? System.out.println("NotEqual");
??????? System.out.println("Old calendar "+cal2.getTime().getTime());
??????? System.out.println("New calendar "+cal1.getTime().getTime());
??????? cal1.set(Calendar.MILLISECOND, 0);
??????? cal2 = (Calendar)in.readObject();
??????? if (cal1.equals(cal2))
????????? System.out.println("Equals");
??????? else
????????? System.out.println("NotEqual");
??????? System.out.println("Processed Old calendar "+cal2.getTime().getTime());
??????? System.out.println("Processed New calendar "+cal1.getTime().getTime());
??? }
? }
然后再另外一個程序中取回來(模擬對數據庫的存儲),但是執行的結果是:
NotEqual
Old calendar 965113200422 <------------ 最后三位的MilliSecond與當前時間有關
New calendar 965113200059 <-----------/
Equals
Processed Old calendar 965113200000
Processed New calendar 965113200000
?
另外我們要注意的一點是,Calendar 為了性能原因對 set() 方法采取延緩計算的方法。在 JavaDoc 中有下面的例子來說明這個問題:
Calendar cal1 = Calendar.getInstance();
? cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
? cal1.set(Calendar.MONTH, Calendar.SEPTEMBER); //應該是 2000-9-31,也就是 2000-10-1
? cal1.set(Calendar.DAY_OF_MONTH, 30); //如果 Calendar 轉化到 2000-10-1,那么現在的結果就該是 2000-10-30
? System.out.println(cal1.getTime()); //輸出的是2000-9-30,說明 Calendar 不是馬上就刷新其內部的記錄
在 Calendar 的方法中,get() 和 add() 會讓 Calendar 立刻刷新。Set() 的這個特性會給我們的開發帶來一些意想不到的結果。我們后面會看到這個問題。
2. Calendar 對象的容錯性,Lenient 設置
我們知道特定的月份有不同的日期,當一個用戶給出錯誤的日期時,Calendar 如何處理的呢?
? import java.io.*;
? import java.util.*;
? public class WhatIsCalendar
? {
??? public static void main(String[] args) throws Exception{
??????? Calendar cal1 = Calendar.getInstance();
??????? cal1.set(2000, 1, 32, 0, 0, 0);
??????? System.out.println(cal1.getTime());
??????? cal1.setLenient(false);
??????? cal1.set(2000, 1, 32, 0, 0, 0);
??????? System.out.println(cal1.getTime());
??? }
? }
它的執行結果是:
? Tue Feb 01 00:00:00 PST 2000
? Exception in thread "main" java.lang.IllegalArgumentException
??? at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:1368)
??? at java.util.Calendar.updateTime(Calendar.java:1508)
??? at java.util.Calendar.getTimeInMillis(Calendar.java:890)
??? at java.util.Calendar.getTime(Calendar.java:871)
??? at WhatIsCalendar.main(WhatIsCalendar.java:12)
當我們設置該 Calendar 為 Lenient false 時,它會依據特定的月份檢查出錯誤的賦值。
3. 不穩定的 Calendar
我們知道 Calendar 是可以被 serialize 的,但是我們要注意下面的問題
? import java.io.*;
? import java.util.*;
? public class UnstableCalendar implements Serializable
? {
??? public static void main(String[] args) throws Exception{
??????? Calendar cal1 = Calendar.getInstance();
??????? cal1.set(2000, 7, 1, 0, 0 , 0);
??????? cal1.set(Calendar.MILLISECOND, 0);
??????? ObjectOutputStream out =
????????? new ObjectOutputStream(
????????? new FileOutputStream("newCalendar.out"));
??????? out.writeObject(cal1);
??????? out.close();
??????? ObjectInputStream in =
????????? new ObjectInputStream(
????????? new FileInputStream("newCalendar.out"));
??????? Calendar cal2 = (Calendar)in.readObject();
??????? cal2.set(Calendar.MILLISECOND, 0);
??????? System.out.println(cal2.getTime());
??? }
? }
?
運行的結果竟然是: Thu Jan 01 00:00:00 PST 1970
它被復原到 EPOC 的起始點,我們稱該 Calendar 是處于不穩定狀態。這個問題的根本原因是 Java 在 serialize GregorianCalendar 時沒有保存所有的信息,所以當它被恢復到內存中,又缺少足夠的信息時,Calendar 會被恢復到 EPOCH 的起始值。Calendar 對象由兩部分構成:字段和相對于 EPOC 的微秒時間差。字段信息是由微秒時間差計算出的,而 set() 方法不會強制 Calendar 重新計算字段。這樣字段值就不對了。
下面的代碼可以解決這個問題:
? import java.io.*;
? import java.util.*;
? public class StableCalendar implements Serializable
? {
??? public static void main(String[] args) throws Exception{
??????? Calendar cal1 = Calendar.getInstance();
??????? cal1.set(2000, 7, 1, 0, 0 , 0);
??????? cal1.set(Calendar.MILLISECOND, 0);
??????? ObjectOutputStream out =
????????? new ObjectOutputStream(
????????? new FileOutputStream("newCalendar.out"));
??????? out.writeObject(cal1);
??????? out.close();
??????? ObjectInputStream in =
????????? new ObjectInputStream(
????????? new FileInputStream("newCalendar.out"));
??????? Calendar cal2 = (Calendar)in.readObject();
??????? cal2.get(Calendar.MILLISECOND); //先調用 get(),強制 Calendar 刷新
??????? cal2.set(Calendar.MILLISECOND, 0); //再設值
??????? System.out.println(cal2.getTime());
??? }
? }
運行的結果是: Tue Aug 01 00:00:00 PDT 2000,這個問題主要會影響到在 EJB 編程中,參數對象中包含 Calendar 時。經過 Serialize/Deserialize 后,直接操作 Calendar 會產生不穩定的情況。
4. add() 與 roll() 的區別
add() 的功能非常強大,add 可以對 Calendar 的字段進行計算。如果需要減去值,那么使用負數值就可以了,如 add(field, -value)。
add() 有兩條規則:
當被修改的字段超出它可以的范圍時,那么比它大的字段會自動修正。如:
Calendar cal1 = Calendar.getInstance();
cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
cal1.add(Calendar.MONTH, 1); //2000-9-31 => 2000-10-1,對嗎?System.out.println(cal1.getTime()); //結果是 2000-9-30
另一個規則是,如果比它小的字段是不可變的(由 Calendar 的實現類決定),那么該小字段會修正到變化最小的值。
以上面的例子,9-31 就會變成 9-30,因為變化最小。
Roll() 的規則只有一條:當被修改的字段超出它可以的范圍時,那么比它大的字段不會被修正。如:
Calendar cal1 = Calendar.getInstance();
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日
cal1.roll(Calendar.WEEK_OF_MONTH, -1); //1999-6-1, 周二
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日
cal1.add(Calendar.WEEK_OF_MONTH, -1); //1999-5-30, 周日
WEEK_OF_MONTH 比 MONTH 字段小,所以 roll 不能修正 MONTH 字段。
我們現在已經能夠格式化并創建一個日期對象了, 但是我們如何才能設置和獲取日期數據的特定部分呢, 比如說小時, 日, 或者分鐘? 我們又如何在日期的這些部分加上或者減去值呢? 答案是使用Calendar 類. 就如我們前面提到的那樣, Calendar 類中的方法替代了Date 類中被人唾罵的方法.
假設你想要設置, 獲取, 和操縱一個日期對象的各個部分, 比方一個月的一天或者是一個星期的一天. 為了演示這個過程, 我們將使用具體的子類 java.util.GregorianCalendar. 考慮下面的例子, 它計算得到下面的第十個星期五是13號.
import java.util.GregorianCalendar;
import java.util.Date;
import java.text.DateFormat;
public class DateExample5 {
public static void main(String[] args) {
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.FULL);
// Create our Gregorian Calendar.
GregorianCalendar cal = new GregorianCalendar();
// Set the date and time of our calendar
// to the system&s date and time
cal.setTime(new Date());
System.out.println("System Date: " + dateFormat.format(cal.getTime())); // Set the day of week to FRIDAY
cal.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.FRIDAY); System.out.println("After Setting Day of Week to Friday: " + dateFormat.format(cal.getTime()));
int friday13Counter = 0;
while (friday13Counter <= 10) {
// Go to the next Friday by adding 7 days. cal.add(GregorianCalendar.DAY_OF_MONTH, 7);
// If the day of month is 13 we have
// another Friday the 13th.
if (cal.get(GregorianCalendar.DAY_OF_MONTH) == 13) {
friday13Counter++; System.out.println(dateFormat.format(cal.getTime()));
}
}
}
}
在這個例子中我們作了有趣的函數調用:
cal.set(GregorianCalendar.DAY_OF_WEEK, GregorianCalendar.FRIDAY);
和:cal.add(GregorianCalendar.DAY_OF_MONTH, 7);
set 方法能夠讓我們通過簡單的設置星期中的哪一天這個域來將我們的時間調整為星期五. 注意到這里我們使用了常量 DAY_OF_WEEK 和 FRIDAY 來增強代碼的可讀性. add 方法讓我們能夠在日期上加上數值. 潤年的所有復雜的計算都由這個方法自動處理.
我們這個例子的輸出結果是:
System Date: Saturday, September 29, 2001
當我們將它設置成星期五以后就成了: Friday, September 28, 2001
Friday, September 13, 2002
Friday, December 13, 2002
Friday, June 13, 2003
Friday, February 13, 2004
Friday, August 13, 2004
Friday, May 13, 2005
Friday, January 13, 2006
Friday, October 13, 2006
Friday, April 13, 2007
Friday, July 13, 2007
Friday, June 13, 2008
Calendar類的基礎即有變量域的觀念。每個類元素都是域,并且這些域在Calendar類中表現為靜態變量。這些變量域,可以通過get/set類方法來獲得或者設置域值。
// 獲得默認的Calendar實例,給它設置時間
Calendarcal = Calendar.getInstance();
intyear = cal.get(Calendar.YEAR);
cal.set(Calendar.MONTH,Calendar.NOVEMBER);
Calendar類的add和roll方法提供在日期之間轉換的能力。每個方法都由一個參數變量和一個參數值來修改,通過這個可為正數或負數的參數值來修改它。僅僅不同的是,add方法可以向高階的變量域溢出。例如,如果從九月三號向后倒退三天,將得到:
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE,-3);
// 值為: 星期六八月 31 23:43:19 EDT 2002
然而使用roll方法向后回滾三天得出:
Calendar cal = Calendar.getInstance();
cal.roll(Calendar.DATE,-3);
// 值為: 星期一九月 30 23:43:47 EDT 2002
這就是為什么通常主要使用add方法的原因。
還有一個隱藏在最通用的Calendar的子類中的功能性方法--isLeapYear(判斷是否為閏年)方法。
Calendar cal = Calendar.getInstance();
booleanleapYear = ( (GregorianCalendar)cal ).isLeapYear(2002);
// 這個值是false
盡管它是一個實例方法,isLeapYear方法的行為表現像靜態方法,需要提供年份的參數傳值給日歷。
其實求幾天幾月幾年前/后的方法,應該用Calendar類比較好的(比Date)。
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.MONTH,1);
cal.add(Calendar.YEAR,2000);
date = cal.getTime();
通過接管日期修改的功能,java.util.Calendar類看上去更像是Data類的復雜版本。但是它還提供額外的功能,更不用說它的國際化支持,使得它值得擁有學習的難度曲線。
3.???? 使用GregorianCalendar類
創建一個代表任意日期的一個途徑使用GregorianCalendar類的構造函數,它包含在java.util包中:
GregorianCalendar(int year, int month, int date)
注意月份的表示,一月是0,二月是1,以此類推,是12月是11。因為大多數人習慣于使用單詞而不是使用數字來表示月份,這樣程序也許更易讀,父類 Calendar使用常量來表示月份:JANUARY, FEBRUARY,等等。所以,創建Wilbur 和 Orville制造第一架動力飛機的日期(December 17, 1903),你可以使用:
GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);
出于清楚的考慮,你應該使用前面的形式。但是,你也應該學習怎樣閱讀下面的短格式。下面的例子同樣表示December 17,1903(記住,在短格式中,11表示December)
??? GregorianCalendar firstFlight = new GregorianCalendar(1903, 11, 17);?? 在上一節中,你學習了轉換Date對象到字符串。這里,你可以做同樣的事情;但是首先,你需要將GregorianCalendar對象轉換到Date。要做到這一點,你可以使用getTime()方法,從它得父類 Calendar繼承而來。GetTime()方法返回GregorianCalendar相應的Date對象。你能夠創建 GregorianCalendar對象,轉換到Date對象,得到和輸出相應的字符串這樣一個過程。下面是例子:
import java.util.*;
import java.text.*;
public class Flight {
? public static void main(String[] args) {
GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);??
Date d = firstFlight.getTime();
DateFormat df = DateFormat.getDateInstance();
String s = df.format(d);
System.out.println("First flight was " + s);
}
有時候創建一個代表當前時刻的GregorianCalendar類的實例是很有用的。你可以簡單的使用沒有參數的GregorianCalendar構造函數,象這樣:
GregorianCalendar thisday = new GregorianCalendar();
一個輸出今天日期的例子程序,使用GregorianCalendar對象:
import java.util.*;
import java.text.*;
class Today {
? public static void main(String[] args) {
GregorianCalendar thisday = new GregorianCalendar();
Date d = thisday.getTime();
DateFormat df = DateFormat.getDateInstance();
String s = df.format(d);
??? System.out.println("Today is " + s);
? }
}
注意到,Date()構造函數和GregorianCalendar()構造函數很類似:都創建一個對象,條件簡單,代表今天。
GregorianCalendar 類提供處理日期的方法。一個有用的方法是add().使用add()方法,你能夠增加象年,月數,天數到日期對象中。要使用add()方法,你必須提供要增加的字段,要增加的數量。一些有用的字段是DATE, MONTH, YEAR, 和 WEEK_OF_YEAR。下面的程序使用add()方法計算未來80天的一個日期。在Jules的<環球80天>是一個重要的數字,使用這個程序可以計算Phileas Fogg從出發的那一天1872 年10月2日后80天的日期:
import java.util.*;
import java.text.*;
public class World {
? public static void main(String[] args) {
GregorianCalendar worldTour = new GregorianCalendar(1872, Calendar.OCTOBER, 2);
??? worldTour.add(GregorianCalendar.DATE, 80);
Date d = worldTour.getTime();
DateFormat df = DateFormat.getDateInstance();
String s = df.format(d);
System.out.println("80 day trip will end " + s);
? }
}
add ()一個重要的副作用是它改變了原來的日期。有時候,擁有原始日期和修改后的日期很重要。不幸的是,你不能簡單的創建一個 GregorianCalendar對象,設置它和原來的相等(equal)。原因是兩個變量指向同一個Date()對象地址。如果Date對象改變,兩個變量就指向改變后的日期對象。代替這種做法,應該創建一個新對象。下面的程序示范了這種做法:import java.util.*;
import java.text.*;
public class ThreeDates {
? public static void main(String[] args) {
GregorianCalendar gc1 = new GregorianCalendar(2000, Calendar.JANUARY, 1);
GregorianCalendar gc2 = gc1;
GregorianCalendar gc3 = new GregorianCalendar(2000, Calendar.JANUARY, 1);
??? //Three dates all equal to January 1, 2000
gc1.add(Calendar.YEAR, 1);
//gc1 and gc2 are changed????
DateFormat df = DateFormat.getDateInstance();
Date d1 = gc1.getTime();
Date d2 = gc2.getTime();
Date d3 = gc3.getTime();
String s1 = df.format(d1);
String s2 = df.format(d2);
String s3 = df.format(d3);
System.out.println("gc1 is " + s1);
System.out.println("gc2 is " + s2);
System.out.println("gc3 is " + s3);
? }
}
????? 程序運行后,gc1和gc2被變成2001年(因為兩個對象指向同一個Date,而Date已經被改變了)。對象gc3指向一個單獨的Date,它沒有被改變。
package com.minght.sys.util;
/**
* <p>Title: 開源,開放</p>
* <p>Description: opeansource</p>
* <p>Copyright: Copyright (c) 2004</p>
* <p>Company: ?海棠</p>
* @author HaiTang Ming
* @version 1.0
*/
import java.util.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.text.*;
public class timeUtil {
/**
? * 將Date類型日期轉化成String類型"任意"格式
? * java.sql.Date,java.sql.Timestamp類型是java.util.Date類型的子類
? * @param date Date
? * @param format String
? *?????????? "2003-01-01"格式
? *?????????? "yyyy年M月d日"
? *?????????? "yyyy-MM-dd HH:mm:ss"格式
? * @return String
? */
public static String dateToString(java.util.Date date,String format) {
??? if (date==null || format==null) {
????? return null;
??? }
??? SimpleDateFormat sdf = new SimpleDateFormat(format);
??? String str = sdf.format(date);
??? return str;
}
/**
? * 將String類型日期轉化成java.utl.Date類型"2003-01-01"格式
? * @param str String 要格式化的字符串
? * @param format String
? * @return Date
? */
public static java.util.Date stringToUtilDate(String str,String format) {
??? if (str==null||format==null) {
????? return null;
??? }
??? SimpleDateFormat sdf = new SimpleDateFormat(format);
??? java.util.Date date = null;
??? try
??? {
????? date = sdf.parse(str);
??? }
??? catch(Exception e)
??? {
??? }
??? return date;
}
/**
? * 將String類型日期轉化成java.sql.Date類型"2003-01-01"格式
? * @param str String
? * @param format String
? * @return Date
? */
public static java.sql.Date stringToSqlDate(String str,String format) {
??? if (str==null||format==null) {
????? return null;
??? }
??? SimpleDateFormat sdf = new SimpleDateFormat(format);
??? java.util.Date date = null;
??? try
??? {
????? date = sdf.parse(str);
??? }
??? catch(Exception e)
??? {
????? return null;
??? }
??? return new java.sql.Date(date.getTime());
}
/**
? * 將String類型日期轉化成java.sql.Date類型"2003-01-01"格式
? * @param str String
? * @param format String
? * @return Timestamp
? */
public static java.sql.Timestamp stringToTimestamp(String str,String format) {
??? if (str==null||format==null) {
????? return null;
??? }
??? SimpleDateFormat sdf = new SimpleDateFormat(format);
??? java.util.Date date = null;
??? try
??? {
????? date = sdf.parse(str);
??? }
??? catch(Exception e)
??? {
????? return null;
??? }
??? return new java.sql.Timestamp(date.getTime());
}
/**
? * 將java.util.Date日期轉化成java.sql.Date類型
? * @param Date
? * @return 格式化后的java.sql.Date
? */
public static java.sql.Date toSqlDate(Date date) {
??? if (date==null) {
????? return null;
??? }
? return new java.sql.Date(date.getTime());
}
/**
? * 將字符串轉化為時間格式 string to string
? * @param str String
? * @param format String
? * @return String
? */
public static String toDateString(String str,String oldformat,String newformat){
??? return dateToString(stringToUtilDate(str,oldformat),newformat);
}
/**
? * 將日歷轉化為日期
? * @param calendar Calendar
? * @return Date
? */
public static java.util.Date converToDate(java.util.Calendar calendar){
? return Calendar.getInstance().getTime();
}
/**
? * 將日期轉化為日歷
? * @param date Date
? * @return Calendar
? */
public static java.util.Calendar converToCalendar(java.util.Date date){
? Calendar calendar = Calendar.getInstance();
? calendar.setTime(date);
? return calendar;
}
/**
? * 求得從某天開始,過了幾年幾月幾日幾時幾分幾秒后,日期是多少
? * 幾年幾月幾日幾時幾分幾秒可以為負數
? * @param date Date
? * @param year int
? * @param month int
? * @param day int
? * @param hour int
? * @param min int
? * @param sec int
? * @return Date
? */
public static java.util.Date modifyDate(java.util.Date date,int year ,int month,int day,int hour,int min,int sec){
? Calendar cal = Calendar.getInstance();
? cal.setTime(date);
? cal.add(Calendar.YEAR,year);
? cal.add(Calendar.MONTH,month);
? cal.add(Calendar.DATE,day);
? cal.add(Calendar.HOUR,hour);
? cal.add(Calendar.MINUTE,min);
? cal.add(Calendar.SECOND,sec);
? return cal.getTime();
}
/**
? * 取得當前日期時間
? * 1:year
? * 2:month
? * 3:day
? */
public static int getCurTime(int i) {
? if (i == 1) {
??? return java.util.Calendar.getInstance().get(Calendar.YEAR);
? }
? else if (i == 2) {
??? return java.util.Calendar.getInstance().get(Calendar.MONTH) + 1;
? }
? else if (i == 3) {
??? return java.util.Calendar.getInstance().get(Calendar.DATE);
? }
? return 0;
}
public static void main(String[] args){
? System.out.println(dateToString(modifyDate(Calendar.getInstance().getTime(),-1,-1,-1,-1,-1,-1),"yyyy-MM-dd HH:mm:ss"));
}
}
加一:為了保證跨年的周屬于同一周,java API規定跨年的周都是新的一年的第一周,例如:
?public static void main(String[] args){
??Calendar c = Calendar.getInstance();
??c.set(2005, 11, 31);
??System.out.println(DateUtil.formatDate(c.getTime()));//2006.12.31
??System.out.println(c.get(Calendar.WEEK_OF_MONTH));//06年12月的最后一周
??System.out.println(c.get(Calendar.WEEK_OF_YEAR));//07年的第一周
?}
posted @
2006-12-12 11:20 保爾任 閱讀(496) |
評論 (0) |
編輯 收藏
第一篇、http://www.blueidea.com/bbs/newsdetail.asp?id=996916(里面有很多例子)
第二篇、徹底明白Java的IO系統(文摘)---JAVA之精髓IO流
一. Input和Output
1. stream代表的是任何有能力產出數據的數據源,或是任何有能力接收數據的接收源。在Java的IO中,所有的stream(包括Input和Out stream)都包括兩種類型:
1.1 以字節為導向的stream
以字節為導向的stream,表示以字節為單位從stream中讀取或往stream中寫入信息。以字節為導向的stream包括下面幾種類型:
1) input stream:
1) ByteArrayInputStream:把內存中的一個緩沖區作為InputStream使用
2) StringBufferInputStream:把一個String對象作為InputStream
3) FileInputStream:把一個文件作為InputStream,實現對文件的讀取操作
4) PipedInputStream:實現了pipe的概念,主要在線程中使用
5) SequenceInputStream:把多個InputStream合并為一個InputStream
2) Out stream
1) ByteArrayOutputStream:把信息存入內存中的一個緩沖區中
2) FileOutputStream:把信息存入文件中
3) PipedOutputStream:實現了pipe的概念,主要在線程中使用
4) SequenceOutputStream:把多個OutStream合并為一個OutStream
1.2 以Unicode字符為導向的stream
以Unicode字符為導向的stream,表示以Unicode字符為單位從stream中讀取或往stream中寫入信息。以Unicode字符為導向的stream包括下面幾種類型:
1) Input Stream
1) CharArrayReader:與ByteArrayInputStream對應
2) StringReader:與StringBufferInputStream對應
3) FileReader:與FileInputStream對應
4) PipedReader:與PipedInputStream對應
2) Out Stream
1) CharArrayWriter:與ByteArrayOutputStream對應
2) StringWriter:無與之對應的以字節為導向的stream
3) FileWriter:與FileOutputStream對應
4) PipedWriter:與PipedOutputStream對應
以字符為導向的stream基本上對有與之相對應的以字節為導向的stream。兩個對應類實現的功能相同,字是在操作時的導向不同。如CharArrayReader:和ByteArrayInputStream的作用都是把內存中的一個緩沖區作為InputStream使用,所不同的是前者每次從內存中讀取一個字節的信息,而后者每次從內存中讀取一個字符。
1.3 兩種不現導向的stream之間的轉換
InputStreamReader和OutputStreamReader:把一個以字節為導向的stream轉換成一個以字符為導向的stream。
2. stream添加屬性
2.1 “為stream添加屬性”的作用
運用上面介紹的Java中操作IO的API,我們就可完成我們想完成的任何操作了。但通過FilterInputStream和FilterOutStream的子類,我們可以為stream添加屬性。下面以一個例子來說明這種功能的作用。
如果我們要往一個文件中寫入數據,我們可以這樣操作:
FileOutStream fs = new FileOutStream(“test.txt”);
然后就可以通過產生的fs對象調用write()函數來往test.txt文件中寫入數據了。但是,如果我們想實現“先把要寫入文件的數據先緩存到內存中,再把緩存中的數據寫入文件中”的功能時,上面的API就沒有一個能滿足我們的需求了。但是通過FilterInputStream和FilterOutStream的子類,為FileOutStream添加我們所需要的功能。
2.2 FilterInputStream的各種類型
2.2.1 用于封裝以字節為導向的InputStream
1) DataInputStream:從stream中讀取基本類型(int、char等)數據。
2) BufferedInputStream:使用緩沖區
3) LineNumberInputStream:會記錄input stream內的行數,然后可以調用getLineNumber()和setLineNumber(int)
4) PushbackInputStream:很少用到,一般用于編譯器開發
2.2.2 用于封裝以字符為導向的InputStream
1) 沒有與DataInputStream對應的類。除非在要使用readLine()時改用BufferedReader,否則使用DataInputStream
2) BufferedReader:與BufferedInputStream對應
3) LineNumberReader:與LineNumberInputStream對應
4) PushBackReader:與PushbackInputStream對應
2.3 FilterOutStream的各種類型
2.2.3 用于封裝以字節為導向的OutputStream
1) DataIOutStream:往stream中輸出基本類型(int、char等)數據。
2) BufferedOutStream:使用緩沖區
3) PrintStream:產生格式化輸出
2.2.4 用于封裝以字符為導向的OutputStream
1) BufferedWrite:與對應
2) PrintWrite:與對應
3. RandomAccessFile
1) 可通過RandomAccessFile對象完成對文件的讀寫操作
2) 在產生一個對象時,可指明要打開的文件的性質:r,只讀;w,只寫;rw可讀寫
3) 可以直接跳到文件中指定的位置
4. I/O應用的一個例子
import java.io.*;
public class TestIO{
public static void main(String[] args)
throws IOException{
//1.以行為單位從一個文件讀取數據
BufferedReader in =
new BufferedReader(
new FileReader("F:\\nepalon\\TestIO.java"));
String s, s2 = new String();
while((s = in.readLine()) != null)
s2 += s + "\n";
in.close();
//1b. 接收鍵盤的輸入
BufferedReader stdin =
new BufferedReader(
new InputStreamReader(System.in));
System.out.println("Enter a line:");
System.out.println(stdin.readLine());
//2. 從一個String對象中讀取數據
StringReader in2 = new StringReader(s2);
int c;
while((c = in2.read()) != -1)
System.out.println((char)c);
in2.close();
//3. 從內存取出格式化輸入
try{
DataInputStream in3 =
new DataInputStream(
new ByteArrayInputStream(s2.getBytes()));
while(true)
System.out.println((char)in3.readByte());
}
catch(EOFException e){
System.out.println("End of stream");
}
//4. 輸出到文件
try{
BufferedReader in4 =
new BufferedReader(
new StringReader(s2));
PrintWriter out1 =
new PrintWriter(
new BufferedWriter(
new FileWriter("F:\\nepalon\\ TestIO.out")));
int lineCount = 1;
while((s = in4.readLine()) != null)
out1.println(lineCount++ + ":" + s);
out1.close();
in4.close();
}
catch(EOFException ex){
System.out.println("End of stream");
}
//5. 數據的存儲和恢復
try{
DataOutputStream out2 =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("F:\\nepalon\\ Data.txt")));
out2.writeDouble(3.1415926);
out2.writeChars("\nThas was pi:writeChars\n");
out2.writeBytes("Thas was pi:writeByte\n");
out2.close();
DataInputStream in5 =
new DataInputStream(
new BufferedInputStream(
new FileInputStream("F:\\nepalon\\ Data.txt")));
BufferedReader in5br =
new BufferedReader(
new InputStreamReader(in5));
System.out.println(in5.readDouble());
System.out.println(in5br.readLine());
System.out.println(in5br.readLine());
}
catch(EOFException e){
System.out.println("End of stream");
}
//6. 通過RandomAccessFile操作文件
RandomAccessFile rf =
new RandomAccessFile("F:\\nepalon\\ rtest.dat", "rw");
for(int i=0; i<10; i++)
rf.writeDouble(i*1.414);
rf.close();
rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "r");
for(int i=0; i<10; i++)
System.out.println("Value " + i + ":" + rf.readDouble());
rf.close();
rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "rw");
rf.seek(5*8);
rf.writeDouble(47.0001);
rf.close();
rf = new RandomAccessFile("F:\\nepalon\\ rtest.dat", "r");
for(int i=0; i<10; i++)
System.out.println("Value " + i + ":" + rf.readDouble());
rf.close();
}
}
關于代碼的解釋(以區為單位):
1區中,當讀取文件時,先把文件內容讀到緩存中,當調用in.readLine()時,再從緩存中以字符的方式讀取數據(以下簡稱“緩存字節讀取方式”)。
1b區中,由于想以緩存字節讀取方式從標準IO(鍵盤)中讀取數據,所以要先把標準IO(System.in)轉換成字符導向的stream,再進行BufferedReader封裝。
2區中,要以字符的形式從一個String對象中讀取數據,所以要產生一個StringReader類型的stream。
4區中,對String對象s2讀取數據時,先把對象中的數據存入緩存中,再從緩沖中進行讀取;對TestIO.out文件進行操作時,先把格式化后的信息輸出到緩存中,再把緩存中的信息輸出到文件中。
5區中,對Data.txt文件進行輸出時,是先把基本類型的數據輸出屋緩存中,再把緩存中的數據輸出到文件中;對文件進行讀取操作時,先把文件中的數據讀取到緩存中,再從緩存中以基本類型的形式進行讀取。注意in5.readDouble()這一行。因為寫入第一個writeDouble(),所以為了正確顯示。也要以基本類型的形式進行讀取。
6區是通過RandomAccessFile類對文件進行操作。
第三篇、花1K內存實現高效I/O的RandomAccessFile類(http://www-128.ibm.com/developerworks/cn/java/l-javaio/index.html),解決RandomAccessFile類效率低下的問題,特別是“與JDK1.4新類MappedByteBuffer+RandomAccessFile的對比”部分講了怎樣用jdk自己的功能實現。
posted @
2006-12-12 11:19 保爾任 閱讀(242) |
評論 (0) |
編輯 收藏
初始化(initialization)其實包含兩部分:
1.類的初始化(initialization class & interface)
2.對象的創建(creation of new class instances)。
因為類的初始化其實是類加載(loading of classes)的最后一步,所以很多書中把它歸結為“對象的創建”的第一步。其實只是看問題的角度不同而已。為了更清楚的理解,這里還是分開來。
順序:
應為類的加載肯定是第一步的,所以類的初始化在前。大體的初始化順序是:
類初始化 -> 子類構造函數 -> 父類構造函數 -> 實例化成員變量 -> 繼續執行子類構造函數的語句
下面結合例子,具體解釋一下。
1。類的初始化(Initialization classes and interfaces),其實很簡單,具體來說有:
(a)初始化類(initialization of class),是指初始化static field 和執行static初始化塊。
例如:
class Super {
??????? static String s = “initialization static field”; //初始化static field,其中“= “initialization static field” ”又叫做static field initializer
??????? // static初始化塊,又叫做static initializer,或 static initialization block
??????? static {
??????? System.out.println(“This is static initializer”);
}
}
btw,有些書上提到static initializer 和 static field initializer 的概念,與之對應的還有 instance initializer 和 instance variable initializer。例子中的注釋已經解釋了其含義。
(b)初始化接口(initialization of interface),是指初始化定義在該interface中的field。
*注意*
--initialization classes 時,該class的superclass 將首先被初始化,但其實現的interface則不會被初始化。
--initialization classes 時,該class的superclass,以及superlcass的superclass 會首先被遞歸地初始化,從java.lang.Object一直到該class為止。但initialiazation interface的時候,卻不需如此,只會初始化該interface本身。
--對于由引用類變量(class field)所引發的初始化,只會初始化真正定義該field的class。
--如果一個static field是編譯時常量(compile-time constant)(即定義為static final field),則對它的引用不會引起定義它的類的初始化。
為了幫助理解最后兩點,請試試看下面的例子:
public class Initialization {
???????
??????? public static void main(String[] args) {
???????????????
??????????????? System.out.println(Sub.x); // Won't cause initialization of Sub, because x is declared by Super, not Sub.
???????????????????????????????????????? // 不會引起Sub類的初始化,因為x是定義在Super類中的
??????????????? System.out.println("-------------------------");
??????????????? System.out.println(Sub.y); // Won't cause initialization of Sub, because y is constant.
???????????????????????????????????????? // 不會引起Sub類的初始化,因為y是常量
??????????????? System.out.println("-------------------------");
??????????????? System.out.println(Sub.z = 2004); // Will cause initialization of Sub class
??// 將會引起Sub的初始化
??}
}
class Super{
??????? static int x = 2006;
}
class Sub extends Super {
???????
??????? static final int y = 2005;
???????
static int z;
???????
static {
??????????????? System.out.println("Initialization Sub");
??????? }
}
2。對象的創建(creation of new class instances),稍微有點煩瑣,具體的步驟如下
(a) 所有的成員變量—包括該類,及它的父類中的成員變量--被分配內存空間,并賦予默認值。(Btw,這里是第一次初始化成員變量)
(b) 為所調用的構造函數初始化其參數變量。(如果有參數)
(c) 如果在構造函數中用this 調用了同類中的其他構造函數,則按照步驟(b)~(f)去處理被調用到的構造函數。
(d) 如果在構造函數中用super調用了其父類的構造函數,則按照步驟(b)~(f)去處理被調用到的父類構造函數。
(e) 按照書寫順序,執行instance initializer 和 instance variable initializer來初始化成員變量。(Btw,這里是第二次初始化成員變量)
(f) 按照書寫順序,執行constructor的其余部分。
*注意*
成員變量其實都被初始化2次,第一次是賦予默認值,第二次才是你想要設定的值。
最后看一個例子:
public class InitializationOrder {
??????? public static void main(String[] args) {
??????????????? Subclass sb = new Subclass();
??????? }
}
class Super{
???????
??????? static {
??????????????? System.out.println(1);
??????? }
???????
??????? Super(int i){
??????????????? System.out.println(i);
??????? }
}
class Subclass extends Super implements Interface{
???????
??????? static {
??????????????? System.out.println(2);
??????? }???????
???????
??????? Super su = new Super(4);
???????
??????? Subclass() {
??????????????? super(3);
??????????????? new Super(5);
??????? }
}
interface Interface{
??????? static Super su = new Super(0);
}
稍微解釋一下:
首先,Java虛擬機要執行InitializationOrder類中的static 方法main(),這引起了類的初始化。開始初始化InitializationOrder類。具體的步驟略去不說。
接著,InitializationOrder類初始化完畢后,開始執行main()方法。語句Subclass sb = new Subclass()將創建一個Subclass對象。加載類Subclass后對其進行類初始化,但因為Subclass有一個父類Super,所以先初始化Super類,初始化塊static {System.out.println(1);}被執行,打印輸出1;
第三,Super初始化完畢后,開始初始化Subclass類。static {System.out.println(2);}被執行,打印輸出2;
第四,至此,類的加載工作全部完成。開始進入創建Subclass的對象過程。先為Subclass類和其父類Super類分配內存空間,這時Super su 被附值為null;
第五,執行構造函數Subclass()時,super(3)被執行。如前面(d)所說,Super類的構造函數Super(int i){….}被調用,并按照步驟(b)~(f)來處理。因此,遞歸調用Super類的父類Object類的構造函數,并按照步驟(b)~(f)來初始化Object類,不過沒有任何輸入結果。最后打印輸出3;
第六,如前面(e)所說,初始化成員變量su,其結果是打印輸出4;
第七,如前面(f)所說,執行new Super(5),并打印輸出5;
最后,Subclass雖然實現了接口Interface,但是初始化它的時候并不會引起接口的初始化,所以接口Interface中的static Super su = new Super(0)自始至終都沒有被執行到。
max做的小改動:
public class Test {
??? public static void main(String[] args) {
??????????? Subclass sb = new Subclass();
??? }
}
class SS{
?public SS(int i){
??System.out.println(i);
?}
}
class Super{
???
??? static {
??????????? System.out.println(1);
??? }
??? SS ss = new SS(100);
???
??? Super(int i){
??????????? System.out.println(i);
??? }
}
class Subclass extends Super implements Interface{
???
??? static {
??????????? System.out.println(2);
??? }???????
???
??? Super su = new Super(4);
???
??? Subclass() {
??????????? super(3);
??????????? new Super(5);
??? }
}
interface Interface{
??? static Super su = new Super(0);
}
--------------------
結果為:
1
2
100
3
100
4
100
5
posted @
2006-12-12 11:13 保爾任 閱讀(361) |
評論 (0) |
編輯 收藏