?? chapter17.htm
字號:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Thinking in Java | Chinese Version by Trans Bot</title>
<meta name="Microsoft Theme" content="inmotion 111, default"></head>
<body background="../_themes/inmotion/inmtextb.gif" tppabs="http://member.netease.com/%7etransbot/Thinking%20in%20Java/_themes/inmotion/inmtextb.gif" bgcolor="#FFFFCC" text="#000000" link="#800000" vlink="#996633" alink="#FF3399">
<p>第17章 項目<br>
<br>
本章包含了一系列項目,它們都以本書介紹的內(nèi)容為基礎(chǔ),并對早期的章節(jié)進(jìn)行了一定程度的擴(kuò)充。<br>
與以前經(jīng)歷過的項目相比,這兒的大多數(shù)項目都明顯要復(fù)雜得多,它們充分演示了新技術(shù)以及類庫的運用。<br>
<br>
17.1 文字處理<br>
如果您有C或C++的經(jīng)驗,那么最開始可能會對Java控制文本的能力感到懷疑。事實上,我們最害怕的就是速度特別慢,這可能妨礙我們創(chuàng)造能力的發(fā)揮。然而,Java對應(yīng)的工具(特別是String類)具有很強(qiáng)的功能,就象本節(jié)的例子展示的那樣(而且性能也有一定程度的提升)。<br>
正如大家即將看到的那樣,建立這些例子的目的都是為了解決本書編制過程中遇到的一些問題。但是,它們的能力并非僅止于此。通過簡單的改造,即可讓它們在其他場合大顯身手。除此以外,它們還揭示出了本書以前沒有強(qiáng)調(diào)過的一項Java特性。<br>
<br>
17.1.1 提取代碼列表<br>
對于本書每一個完整的代碼列表(不是代碼段),大家無疑會注意到它們都用特殊的注釋記號起始與結(jié)束('//:'和'///:~')。之所以要包括這種標(biāo)志信息,是為了能將代碼從本書自動提取到兼容的源碼文件中。在我的前一本書里,我設(shè)計了一個系統(tǒng),可將測試過的代碼文件自動合并到書中。但對于這本書,我發(fā)現(xiàn)一種更簡便的做法是一旦通過了最初的測試,就把代碼粘貼到書中。而且由于很難第一次就編譯通過,所以我在書的內(nèi)部編輯代碼。但如何提取并測試代碼呢?這個程序就是關(guān)鍵。如果你打算解決一個文字處理的問題,那么它也很有利用價值。該例也演示了String類的許多特性。<br>
我首先將整本書都以ASCII文本格式保存成一個獨立的文件。CodePackager程序有兩種運行模式(在usageString有相應(yīng)的描述):如果使用-p標(biāo)志,程序就會檢查一個包含了ASCII文本(即本書的內(nèi)容)的一個輸入文件。它會遍歷這個文件,按照注釋記號提取出代碼,并用位于第一行的文件名來決定創(chuàng)建文件使用什么名字。除此以外,在需要將文件置入一個特殊目錄的時候,它還會檢查package語句(根據(jù)由package語句指定的路徑選擇)。<br>
但這樣還不夠。程序還要對包(package)名進(jìn)行跟蹤,從而監(jiān)視章內(nèi)發(fā)生的變化。由于每一章使用的所有包都以c02,c03,c04等等起頭,用于標(biāo)記它們所屬的是哪一章(除那些以com起頭的以外,它們在對不同的章進(jìn)行跟蹤的時候會被忽略)——只要每一章的第一個代碼列表包含了一個package,所以CodePackager程序能知道每一章發(fā)生的變化,并將后續(xù)的文件放到新的子目錄里。<br>
每個文件提取出來時,都會置入一個SourceCodeFile對象,隨后再將那個對象置入一個集合(后面還會詳盡講述這個過程)。這些SourceCodeFile對象可以簡單地保存在文件中,那正是本項目的第二個用途。如果直接調(diào)用CodePackager,不添加-p標(biāo)志,它就會將一個“打包”文件作為輸入。那個文件隨后會被提取(釋放)進(jìn)入單獨的文件。所以-p標(biāo)志的意思就是提取出來的文件已被“打包”(packed)進(jìn)入這個單一的文件。<br>
但為什么還要如此麻煩地使用打包文件呢?這是由于不同的計算機(jī)平臺用不同的方式在文件里保存文本信息。其中最大的問題是換行字符的表示方法;當(dāng)然,還有可能存在另一些問題。然而,Java有一種特殊類型的IO數(shù)據(jù)流——DataOutputStream——它可以保證“無論數(shù)據(jù)來自何種機(jī)器,只要使用一個DataInputStream收取這些數(shù)據(jù),就可用本機(jī)正確的格式保存它們”。也就是說,Java負(fù)責(zé)控制與不同平臺有關(guān)的所有細(xì)節(jié),而這正是Java最具魅力的一點。所以-p標(biāo)志能將所有東西都保存到單一的文件里,并采用通用的格式。用戶可從Web下載這個文件以及Java程序,然后對這個文件運行CodePackager,同時不指定-p標(biāo)志,文件便會釋放到系統(tǒng)中正確的場所(亦可指定另一個子目錄;否則就在當(dāng)前目錄創(chuàng)建子目錄)。為確保不會留下與特定平臺有關(guān)的格式,凡是需要描述一個文件或路徑的時候,我們就使用File對象。除此以外,還有一項特別的安全措施:在每個子目錄里都放入一個空文件;那個文件的名字指出在那個子目錄里應(yīng)找到多少個文件。<br>
下面是完整的代碼,后面會對它進(jìn)行詳細(xì)的說明:<br>
<br>
959-968頁程序<br>
<br>
我們注意到package語句已經(jīng)作為注釋標(biāo)志出來了。由于這是本章的第一個程序,所以package語句是必需的,用它告訴CodePackager已改換到另一章。但是把它放入包里卻會成為一個問題。當(dāng)我們創(chuàng)建一個包的時候,需要將結(jié)果程序同一個特定的目錄結(jié)構(gòu)聯(lián)系在一起,這一做法對本書的大多數(shù)例子都是適用的。但在這里,CodePackager程序必須在一個專用的目錄里編譯和運行,所以package語句作為注釋標(biāo)記出去。但對CodePackager來說,它“看起來”依然象一個普通的package語句,因為程序還不是特別復(fù)雜,不能偵查到多行注釋(沒有必要做得這么復(fù)雜,這里只要求方便就行)。<br>
頭兩個類是“支持/工具”類,作用是使程序剩余的部分在編寫時更加連貫,也更便于閱讀。第一個是Pr,它類似ANSI
C的perror庫,兩者都能打印出一條錯誤提示消息(但同時也會退出程序)。第二個類將文件的創(chuàng)建過程封裝在內(nèi),這個過程已在第10章介紹過了;大家已經(jīng)知道,這樣做很快就會變得非常累贅和麻煩。為解決這個問題,第10章提供的方案致力于新類的創(chuàng)建,但這兒的“靜態(tài)”方法已經(jīng)使用過了。在那些方法中,正常的違例會被捕獲,并相應(yīng)地進(jìn)行處理。這些方法使剩余的代碼顯得更加清爽,更易閱讀。<br>
幫助解決問題的第一個類是SourceCodeFile(源碼文件),它代表本書一個源碼文件包含的所有信息(內(nèi)容、文件名以及目錄)。它同時還包含了一系列String常數(shù),分別代表一個文件的開始與結(jié)束;在打包文件內(nèi)使用的一個標(biāo)記;當(dāng)前系統(tǒng)的換行符;文件路徑分隔符(注意要用System.getProperty()偵查本地版本是什么);以及一大段版權(quán)聲明,它是從下面這個Copyright.txt文件里提取出來的:<br>
<br>
969-967頁程序<br>
<br>
從一個打包文件中提取文件時,當(dāng)初所用系統(tǒng)的文件分隔符也會標(biāo)注出來,以便用本地系統(tǒng)適用的符號替換它。<br>
當(dāng)前章的子目錄保存在chapter字段中,它初始化成c02(大家可注意一下第2章的列表正好沒有包含一個打包語句)。只有在當(dāng)前文件里發(fā)現(xiàn)一個package(打包)語句時,chapter字段才會發(fā)生改變。<br>
<br>
1. 構(gòu)建一個打包文件<br>
第一個構(gòu)建器用于從本書的ASCII文本版里提取出一個文件。發(fā)出調(diào)用的代碼(在列表里較深的地方)會讀入并檢查每一行,直到找到與一個列表的開頭相符的為止。在這個時候,它就會新建一個SourceCodeFile對象,將第一行的內(nèi)容(已經(jīng)由調(diào)用代碼讀入了)傳遞給它,同時還要傳遞BufferedReader對象,以便在這個緩沖區(qū)中提取源碼列表剩余的內(nèi)容。<br>
從這時起,大家會發(fā)現(xiàn)String方法被頻繁運用。為提取出文件名,需調(diào)用substring()的過載版本,令其從一個起始偏移開始,一直讀到字串的末尾,從而形成一個“子串”。為算出這個起始索引,先要用length()得出startMarker的總長,再用trim()刪除字串頭尾多余的空格。第一行在文件名后也可能有一些字符;它們是用indexOf()偵測出來的。若沒有發(fā)現(xiàn)找到我們想尋找的字符,就返回-1;若找到那些字符,就返回它們第一次出現(xiàn)的位置。注意這也是indexOf()的一個過載版本,采用一個字串作為參數(shù),而非一個字符。<br>
解析出并保存好文件名后,第一行會被置入字串contents中(該字串用于保存源碼清單的完整正文)。隨后,將剩余的代碼行讀入,并合并進(jìn)入contents字串。當(dāng)然事情并沒有想象的那么簡單,因為特定的情況需加以特別的控制。一種情況是錯誤檢查:若直接遇到一個startMarker(起始標(biāo)記),表明當(dāng)前操作的這個代碼列表沒有設(shè)置一個結(jié)束標(biāo)記。這屬于一個出錯條件,需要退出程序。<br>
另一種特殊情況與package關(guān)鍵字有關(guān)。盡管Java是一種自由形式的語言,但這個程序要求package關(guān)鍵字必須位于行首。若發(fā)現(xiàn)package關(guān)鍵字,就通過檢查位于開頭的空格以及位于末尾的分號,從而提取出包名(注意亦可一次單獨的操作實現(xiàn),方法是使用過載的substring(),令其同時檢查起始和結(jié)束索引位置)。隨后,將包名中的點號替換成特定的文件分隔符——當(dāng)然,這里要假設(shè)文件分隔符僅有一個字符的長度。盡管這個假設(shè)可能對目前的所有系統(tǒng)都是適用的,但一旦遇到問題,一定不要忘了檢查一下這里。<br>
默認(rèn)操作是將每一行都連接到contents里,同時還有換行字符,直到遇到一個endMarker(結(jié)束標(biāo)記)為止。該標(biāo)記指出構(gòu)建器應(yīng)當(dāng)停止了。若在endMarker之前遇到了文件結(jié)尾,就認(rèn)為存在一個錯誤。<br>
<br>
2. 從打包文件中提取<br>
第二個構(gòu)建器用于將源碼文件從打包文件中恢復(fù)(提取)出來。在這兒,作為調(diào)用者的方法不必?fù)?dān)心會跳過一些中間文本。打包文件包含了所有源碼文件,它們相互間緊密地靠在一起。需要傳遞給該構(gòu)建器的僅僅是一個BufferedReader,它代表著“信息源”。構(gòu)建器會從中提取出自己需要的信息。但在每個代碼列表開始的地方還有一些配置信息,它們的身份是用packMarker(打包標(biāo)記)指出的。若packMarker不存在,意味著調(diào)用者試圖用錯誤的方法來使用這個構(gòu)建器。<br>
一旦發(fā)現(xiàn)packMarker,就會將其剝離出來,并提取出目錄名(用一個'#'結(jié)尾)以及文件名(直到行末)。不管在哪種情況下,舊分隔符都會被替換成本地適用的一個分隔符,這是用String
replace()方法實現(xiàn)的。老的分隔符被置于打包文件的開頭,在代碼列表稍靠后的一部分即可看到是如何把它提取出來的。<br>
構(gòu)建器剩下的部分就非常簡單了。它讀入每一行,把它合并到contents里,直到遇見endMarker為止。<br>
<br>
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -