jhengfei
          愛JAVA,愛生活

          原作者:SonyMusic

          讀:rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
          在Java中讀取Excel文件的內容
          在這里,我使用的是一個叫Java Excel API的東西,類似的還有jakarta的POI,不過感覺那個
          太復雜了點兒。而且jxl對中文的支持相當的好,至少我在用的過程中一點問題沒出。

          一、下載地址
          http://sourceforge.net/project/showfiles.php?group_id=79926

          二、特性
          可以讀取Excel 95, 97, 2000文件
          可以讀或寫Excel 97及其以后版本的的公式(不過我發現好像有bug)
          生成Excel 97格式的電子表格
          支持字體、數字和日期格式化
          支持單元格的顏色和陰影
          可以編輯現有的文件

          三、讀文件
          //聲明一下,記得后面要關閉哦。。
          Workbook workbook = null;

          try {
          workbook = Workbook.getWorkbook(new File("d:\\temp\\TestRead.xls"));
          } catch (Exception e) {
          throw new Exception("file to import not found!");
          }

          Sheet sheet = workbook.getSheet(0);
          Cell cell = null;

          int columnCount=3;
          int rowCount=sheet.getRows();
          for (int i = 0; i <rowCount; i++) {
          for (int j = 0; j <columnCount; j++) {
          //注意,這里的兩個參數,第一個是表示列的,第二才表示行
          cell=sheet.getCell(j, i);
          //要根據單元格的類型分別做處理,否則格式化過的內容可能會不正確
          if(cell.getType()==CellType.NUMBER){
          System.out.print(((NumberCell)cell).getValue());
          }
          else if(cell.getType()==CellType.DATE){
          System.out.print(((DateCell)cell).getDate());
          }
          else{
          System.out.print(cell.getContents());
          }

          //System.out.print(cell.getContents());
          System.out.print("\t");
          }
          System.out.print("\n");
          }
          //關閉它,否則會有內存泄露
          workbook.close();

          寫:wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
          在Java中向Excel文件寫入內容


          四、導出數據到Excel文件中
          下面的例子,設置了數字、日期的格式,還有字體,顏色等。

          File tempFile=new File("d:/temp/output.xls");
          WritableWorkbook workbook = Workbook.createWorkbook(tempFile);
          WritableSheet sheet = workbook.createSheet("TestCreateExcel", 0);

          //一些臨時變量,用于寫到excel中
          Label l=null;
          jxl.write.Number n=null;
          jxl.write.DateTime d=null;

          //預定義的一些字體和格式,同一個Excel中最好不要有太多格式
          WritableFont headerFont = new WritableFont(WritableFont.ARIAL, 12, WritableFont.BOLD, false, Underlinestyle.NO_UNDERLINE, jxl.format.Colour.BLUE);
          WritableCellFormat headerFormat = new WritableCellFormat (headerFont);

          WritableFont titleFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false, Underlinestyle.NO_UNDERLINE, jxl.format.Colour.RED);
          WritableCellFormat titleFormat = new WritableCellFormat (titleFont);

          WritableFont detFont = new WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false, Underlinestyle.NO_UNDERLINE, jxl.format.Colour.BLACK);
          WritableCellFormat detFormat = new WritableCellFormat (detFont);

          NumberFormat nf=new NumberFormat("0.00000"); //用于Number的格式
          WritableCellFormat priceFormat = new WritableCellFormat (detFont, nf);

          DateFormat df=new DateFormat("yyyy-MM-dd");//用于日期的
          WritableCellFormat dateFormat = new WritableCellFormat (detFont, df);

          //剩下的事情,就是用上面的內容和格式創建一些單元格,再加到sheet中
          l=new Label(0, 0, "用于測試的Excel文件", headerFormat);
          sheet.addCell(l);

          //add Title
          int column=0;
          l=new Label(column++, 2, "標題", titleFormat);
          sheet.addCell(l);
          l=new Label(column++, 2, "日期", titleFormat);
          sheet.addCell(l);
          l=new Label(column++, 2, "貨幣", titleFormat);
          sheet.addCell(l);
          l=new Label(column++, 2, "價格", titleFormat);
          sheet.addCell(l);

          //add detail
          int i=0;
          column=0;
          l=new Label(column++, i+3, "標題 "+i, detFormat);
          sheet.addCell(l);
          d=new DateTime(column++, i+3, new java.util.Date(), dateFormat);
          sheet.addCell(d);
          l=new Label(column++, i+3, "CNY", detFormat);
          sheet.addCell(l);
          n=new jxl.write.Number(column++, i+3, 5.678, priceFormat);
          sheet.addCell(n);

          i++;
          column=0;
          l=new Label(column++, i+3, "標題 "+i, detFormat);
          sheet.addCell(l);
          d=new DateTime(column++, i+3, new java.util.Date(), dateFormat);
          sheet.addCell(d);
          l=new Label(column++, i+3, "SGD", detFormat);
          sheet.addCell(l);
          n=new jxl.write.Number(column++, i+3, 98832, priceFormat);
          sheet.addCell(n);

          //設置列的寬度
          column=0;
          sheet.setColumnView(column++, 20);
          sheet.setColumnView(column++, 20);
          sheet.setColumnView(column++, 10);
          sheet.setColumnView(column++, 20);

          workbook.write();
          workbook.close();

          posted @ 2006-05-22 15:51 點滴鑄就輝煌 閱讀(1862) | 評論 (0)編輯 收藏
           
          這一陣不知道忙的什么,日志也不記了,一切都似乎處在停滯中.....
          是頹廢還是懶惰呢?
          呼....只有自己清楚.
          振作....
          posted @ 2006-05-19 09:24 點滴鑄就輝煌 閱讀(206) | 評論 (0)編輯 收藏
           

          摘要:

          在這篇文章中,我推薦使用Lucene,它是基于Java的開源搜索引擎,通過提取和索引相關的源碼元素來搜索源代碼。這里,我僅限定搜索Java源代碼。然而,Lucene同樣可以做到對其他編程語言的源代碼的搜索。
          某些網站允許軟件開發社團通過發布開發者指南、白皮書、FAQs【常見問題解答】和源代碼以實現信息的共享。隨著信息量的增長,和幾個開發者貢獻出自己的知識庫,于是網站提供搜索引擎來搜索站點上現有的所有信息。雖然這些搜索引擎對文本文件的搜索可以做的很好,但對開發者搜索源代碼做了比較嚴格的限制。搜索引擎認為源代碼就是純文本文件,因此,在這一點上,與成熟的可以處理大量源文件的工具――grep相比沒有什么不同。

          在這篇文章中,我推薦使用Lucene,它是基于Java的開源搜索引擎,通過提取和索引相關的源碼元素來搜索源代碼。這里,我僅限定搜索Java源代碼。然而,Lucene同樣可以做到對其他編程語言的源代碼的搜索。

          文章給出了在Lucene環境下搜索引擎重點方面的簡短概述。要了解更多細節信息,參考Resources部分。

          版權聲明:任何獲得Matrix授權的網站,轉載時請務必保留以下作者信息和鏈接
          作者:Renuka;Knightchen(作者的blog:http://blog.matrix.org.cn/page/Knightchen)
          原文:http://www.matrix.org.cn/resource/article/44/44362_Lucene+Java.html
          關鍵字:Lucene;Java

          概述
          Lucene是最流行的開源搜索引擎庫之一。它由能文本索引和搜索的核心API組成。Lucene能夠對給出一組文本文件創建索引并且允許你用復雜的查詢來搜索這些索引,例如:+title:Lucene -content:Search、search AND Lucene、+search +code。在進入搜索細節之前,先讓我來介紹一下Lucene的一些功能。

          在Lucene中索引文本

          搜索引擎對所有需要被搜索的數據進行掃描并將其存儲到能有效獲取的一個結構里。這個最有名的結構被稱為倒排索引。例如,現在考慮對一組會議記錄進行索引。首先,每個會議記錄的文件被分為幾個獨立的部分或者域:如標題、作者、email、摘要和內容。其次,每一域的內容被標記化并且提取出關鍵字或者術語。這樣就可以建立如下表所示會議記錄的倒排索引。

          ????????....???????? ????????

          對于域中的每一術語而言,上圖存儲了兩方面的內容:該術語在文件中出現的數量(即頻率【DF】)以及包含該術語的每一文件的ID。對于每個術語保存的其它細節:例如術語在每個文件中出現的次數以及出現的位置也被保存起來。無論如何,對于我們非常重要的一點是要知道:利用Lucene檢索文件意味著將其保存為一種特定格式,該格式允許高效率查詢及獲取。

          分析被索引的文本

          Lucene使用分析器來處理被索引的文本。在將其存入索引之前,分析器用于將文本標記化、摘錄有關的單詞、丟棄共有的單詞、處理派生詞(把派生詞還原到詞根形式,意思是把bowling、bowler和bowls還原為bowl)和完成其它要做的處理。Lucene提供的通用分析器是:
          &#61548;????????SimpleAnalyzer:用字符串標記一組單詞并且轉化為小寫字母。
          &#61548;????????StandardAnalyzer:用字符串標記一組單詞,可識別縮寫詞、email地址、主機名稱等等。并丟棄基于英語的stop words (a, an, the, to)等、處理派生詞。

          檢索(搜索索引)
          索引結構建立后,可以通過指定被搜索的字段和術語構造復雜的查詢來對索引進行檢索。例如,用戶查詢abstract:system AND email:abc@mit.edu得到的結果是所有在摘要中包含system、在email地址中有abc@mit.edu的文件。也就是說,如果在前面倒排索引表的基礎上搜索就返回Doc15。與查詢匹配的文件是按照術語在文件中出現的次數以及包含該術語的文檔的數量進行排列的。Lucene執行一種順序排列機制并且提供了給我們更改它的彈性。

          源代碼搜索引擎

          現在我們知道了關于搜索引擎的基本要點,下面讓我們看一看用于搜索源代碼的搜索引擎應如何實現。下文中展示在搜索Java示例代碼時,開發者主要關注以下Java類:
          繼承一個具體類或實現一個接口。
          調用特定的方法。
          使用特定的Java類。

          綜合使用上述部分的組合可以滿足開發者獲取他們正在尋找相關代碼的需要。因此搜索引擎應該允許開發者對這些方面進行單個或組合查詢。IDEs【集成開發環境】有另一個局限性:大部分可使用的工具僅僅基于上述標準之一來支持搜索源代碼。在搜索中,缺乏組合這些標準進行查詢的靈活性。

          現在我們開始建立一個支持這些要求的源代碼搜索引擎。

          編寫源代碼分析器
          第一步先寫一個分析器,用來提取或去除源代碼元素,確保建立最佳的索引并且僅包含相關方面的代碼。在Java語言中的關鍵字--public,null,for,if等等,在每個.java文件中它們都出現了,這些關鍵字類似于英語中的普通單詞(the,a,an,of)。因而,分析器必須把這些關鍵字從索引中去掉。

          我們通過繼承Lucene的抽象類Analyzer來建立一個Java源代碼分析器。下面列出了JavaSourceCodeAnalyzer類的源代碼,它實現了tokenStream(String,Reader)方法。這個類定義了一組【stop words】,它們能夠在索引過程中,使用Lucene提供的StopFilter類來被去除。tokenStream方法用于檢查被索引的字段。如果該字段是“comment”,首先要利用LowerCaseTokenizer類將輸入項標記化并轉換成小寫字母,然后利用StopFilter類除去英語中的【stop words】(有限的一組英語【stop words】),再利用PorterStemFilter移除通用的語形學以及詞尾后綴。如果被索引的內容不是“comment”,那么分析器就利用LowerCaseTokenizer類將輸入項標記化并轉換成小寫字母,并且利用StopFilter類除去Java關鍵字。

          package com.infosys.lucene.code JavaSourceCodeAnalyzer.;

          import java.io.Reader;
          import java.util.Set;
          import org.apache.lucene.analysis.*;

          public class JavaSourceCodeAnalyzer extends Analyzer {
          ??????private Set javaStopSet;
          ??????private Set englishStopSet;
          ??????private static final String[] JAVA_STOP_WORDS = {
          ???????? "public","private","protected","interface",
          ????????????"abstract","implements","extends","null""new",
          ?? ????????"switch","case", "default" ,"synchronized" ,
          ????????????"do", "if", "else", "break","continue","this",
          ?? ????????"assert" ,"for","instanceof", "transient",
          ????????????"final", "static" ,"void","catch","try",
          ????????????"throws","throw","class", "finally","return",
          ????????????"const" , "native", "super","while", "import",
          ????????????"package" ,"true", "false" };
          ???? private static final String[] ENGLISH_STOP_WORDS ={
          ????????????"a", "an", "and", "are","as","at","be" "but",
          ????????????"by", "for", "if", "in", "into", "is", "it",
          ????????????"no", "not", "of", "on", "or", "s", "such",
          ????????????"that", "the", "their", "then", "there","these",
          ????????????"they", "this", "to", "was", "will", "with" };
          ???? public SourceCodeAnalyzer(){
          ????????????super();
          ????????????javaStopSet = StopFilter.makeStopSet(JAVA_STOP_WORDS);
          ????????????englishStopSet = StopFilter.makeStopSet(ENGLISH_STOP_WORDS);
          ???? }
          ???? public TokenStream tokenStream(String fieldName, Reader reader) {
          ????????????if (fieldName.equals("comment"))
          ???????????????????? return?? new PorterStemFilter(new StopFilter(
          ????????????????????????new LowerCaseTokenizer(reader),englishStopSet));
          ????????????else
          ???????????????????? return?? new StopFilter(
          ?????????????????? new LowerCaseTokenizer(reader),javaStopSet);
          ???? }
          }


          編寫類JavaSourceCodeIndexer
          第二步生成索引。用來建立索引的非常重要的類有IndexWriter、Analyzer、Document和Field。對每一個源代碼文件建立Lucene的一個Document實例。解析源代碼文件并且摘錄出與代碼相關的語法元素,主要包括:導入聲明、類名稱、所繼承的類、實現的接口、實現的方法、方法使用的參數和每個方法的代碼等。然后把這些句法元素添加到Document實例中每個獨立的Field實例中。然后使用存儲索引的IndexWriter實例將Document實例添加到索引中。

          下面列出了JavaSourceCodeIndexer類的源代碼。該類使用了JavaParser類解析Java文件和摘錄語法元素,也可以使用Eclipse3.0 ASTParser。這里就不探究JavaParser類的細節了,因為其它解析器也可以用于提取相關源碼元素。在源代碼文件提取元素的過程中,創建Filed實例并添加到Document實例中。
          import org.apache.lucene.document.*;
          import org.apache.lucene.index.*;
          import com.infosys.lucene.code.JavaParser.*;

          public class JavaSourceCodeIndexer {
          ????private static JavaParser parser = new JavaParser();
          ????????private static final String IMPLEMENTS = "implements";
          ????????private static final String IMPORT = "import";
          ????????...
          ????????public static void main(String[] args) {
          ????????????????File indexDir = new File("C:\\Lucene\\Java");
          ????????????????File dataDir = new File("C:\\JavaSourceCode ");
          ????????????????IndexWriter writer = new IndexWriter(indexDir,
          ????????????????????new JavaSourceCodeAnalyzer(), true);
          ????????????????indexDirectory(writer, dataDir);
          ????????????????writer.close();
          ????????}
          ????????public static void indexDirectory(IndexWriter writer, File dir){
          ????????????File[] files = dir.listFiles();
          ????????????for (int i = 0; i < files.length; i++) {
          ????????????????????File f = files[i];
          ????????????????// Create a Lucene Document
          ????????????????Document doc = new Document();
          ????????????????//??Use JavaParser to parse file
          ????????????????parser.setSource(f);
          ????????????????addImportDeclarations(doc, parser);
          ?????? ???????? ????????addComments(doc, parser);
          ???????? ????????// Extract Class elements Using Parser
          ????????????????JClass cls = parser.getDeclaredClass();
          ????????????????addClass(doc, cls);
          ???????? ????????// Add field to the Lucene Document
          ?????? ????????????????doc.add(Field.UnIndexed(FILENAME, f.getName()));
          ????????????????writer.addDocument(doc);
          ?? ???????? ????????}
          ????????}
          ????????private static void addClass(Document doc, JClass cls) {
          ?? ????????????????//For each class add Class Name field
          ????????????doc.add(Field.Text(CLASS, cls.className));
          ????????????String superCls = cls.superClass;
          ????????????if (superCls != null)
          ?? ????????????????//Add the class it extends as extends field
          ????????doc.add(Field.Text(EXTENDS, superCls));
          ????????????// Add interfaces it implements
          ????????????ArrayList interfaces = cls.interfaces;
          ????????????for (int i = 0; i < interfaces.size(); i++)
          ????????????????doc.add(Field.Text(IMPLEMENTS, (String) interfaces.get(i)));
          ?? ???????? ????????//Add details??on methods declared
          ????????????addMethods(cls, doc);
          ????????????ArrayList innerCls = cls.innerClasses;
          ?? ????????????????for (int i = 0; i < innerCls.size(); i++)
          ????????????????addClass(doc, (JClass) innerCls.get(i));
          ????????}
          ????????private static void addMethods(JClass cls, Document doc) {
          ????????????ArrayList methods = cls.methodDeclarations;
          ????????????for (int i = 0; i < methods.size(); i++) {
          ?????? ????????????????JMethod method = (JMethod) methods.get(i);
          ????????????????// Add method name field
          ????????????????doc.add(Field.Text(METHOD, method.methodName));
          ????????????????// Add return type field
          ????????????????doc.add(Field.Text(RETURN, method.returnType));
          ????????????????ArrayList params = method.parameters;
          ????????????????for (int k = 0; k < params.size(); k++)
          ????????????????// For each method add parameter types
          ????????????????????doc.add(Field.Text(PARAMETER, (String)params.get(k)));
          ????????????????String code = method.codeBlock;
          ????????????????if (code != null)
          ????????????????//add the method code block
          ????????????????????doc.add(Field.UnStored(CODE, code));
          ????????????}
          ????????}
          ????????private static void addImportDeclarations(Document doc, JavaParser parser) {
          ?? ????????????????ArrayList imports = parser.getImportDeclarations();
          ????????????if (imports == null)???? return;
          ????????????for (int i = 0; i < imports.size(); i++)
          ????????????????????//add import declarations as keyword
          ????????????????doc.add(Field.Keyword(IMPORT, (String) imports.get(i)));
          ????????}
          }


          Lucene有四種不同的字段類型:Keyword,UnIndexed,UnStored和Text,用于指定建立最佳索引。
          &#61548;????????Keyword字段是指不需要分析器解析但需要被編入索引并保存到索引中的部分。JavaSourceCodeIndexer類使用該字段來保存導入類的聲明。
          &#61548;????????UnIndexed字段是既不被分析也不被索引,但是要被逐字逐句的將其值保存到索引中。由于我們一般要存儲文件的位置但又很少用文件名作為關鍵字來搜索,所以用該字段來索引Java文件名。
          &#61548;????????UnStored字段和UnIndexed字段相反。該類型的Field要被分析并編入索引,但其值不會被保存到索引中。由于存儲方法的全部源代碼需要大量的空間。所以用UnStored字段來存儲被索引的方法源代碼。可以直接從Java源文件中取出方法的源代碼,這樣作可以控制我們的索引的大小。
          &#61548;????????Text字段在索引過程中是要被分析、索引并保存的。類名是作為Text字段來保存。下表展示了JavaSourceCodeIndexer類使用Field字段的一般情況。



          1.
          ?? 用Lucene建立的索引可以用Luke預覽和修改,Luke是用于理解索引很有用的一個開源工具。圖1中是Luke工具的一張截圖,它顯示了JavaSourceCodeIndexer類建立的索引。

          resized image
          圖1:在Luke中索引截圖

          如圖所見,導入類的聲明沒有標記化或分析就被保存了。類名和方法名被轉換為小寫字母后,才保存的。

          查詢Java源代碼
          建立多字段索引后,可以使用Lucene來查詢這些索引。它提供了這兩個重要類分別是IndexSearcher和QueryParser,用于搜索文件。QueryParser類則用于解析由用戶輸入的查詢表達式,同時IndexSearcher類在文件中搜索滿足查詢條件的結果。下列表格顯示了一些可能發生的查詢及它的含義:


          用戶通過索引不同的語法元素組成有效的查詢條件并搜索代碼。下面列出了用于搜索的示例代碼。
          public class JavaCodeSearch {
          public static void main(String[] args) throws Exception{
          ????File indexDir = new File(args[0]);
          ????String q =??args[1]; //parameter:JGraph code:insert
          ????Directory fsDir = FSDirectory.getDirectory(indexDir,false);
          ????IndexSearcher is = new IndexSearcher(fsDir);

          ????PerFieldAnalyzerWrapper analyzer = new
          ????????PerFieldAnalyzerWrapper( new
          ????????????????JavaSourceCodeAnalyzer());

          ????analyzer.addAnalyzer("import", new KeywordAnalyzer());
          ????Query query = QueryParser.parse(q, "code", analyzer);
          ????long start = System.currentTimeMillis();
          ????Hits hits = is.search(query);
          ????long end = System.currentTimeMillis();
          ????System.err.println("Found " + hits.length() +
          ????????????????" docs in " + (end-start) + " millisec");
          ????for(int i = 0; i < hits.length(); i++){
          ????Document doc = hits.doc(i);
          ????????System.out.println(doc.get("filename")
          ????????????????+ " with a score of " + hits.score(i));
          ????}
          ????is.close();
          }
          }


          IndexSearcher實例用FSDirectory來打開包含索引的目錄。然后使用Analyzer實例分析搜索用的查詢字符串,以確保它與索引(還原詞根,轉換小寫字母,過濾掉,等等)具有同樣的形式。為了避免在查詢時將Field作為一個關鍵字索引,Lucene做了一些限制。Lucene用Analyzer分析在QueryParser實例里傳給它的所有字段。為了解決這個問題,可以用Lucene提供的PerFieldAnalyzerWrapper類為查詢中的每個字段指定必要的分析。因此,查詢字符串import:org.w3c.* AND code:Document將用KeywordAnalyzer來解析字符串org.w3c.*并且用JavaSourceCodeAnalyzer來解析Document。QueryParser實例如果查詢沒有與之相符的字段,就使用默認的字段:code,使用PerFieldAnalyzerWrapper來分析查詢字符串,并返回分析后的Query實例。IndexSearcher實例使用Query實例并返回一個Hits實例,它包含了滿足查詢條件的文件。

          結束語

          這篇文章介紹了Lucene——文本搜索引擎,其可以通過加載分析器及多字段索引來實現源代碼搜索。文章只介紹了代碼搜索引擎的基本功能,同時在源碼檢索中使用愈加完善的分析器可以提高檢索性能并獲得更好的查詢結果。這種搜索引擎可以允許用戶在軟件開發社區搜索和共享源代碼。

          資源
          這篇文章的示例Sample code
          Matrix:http://www.matrix.org.cn
          Onjava:http://www.onjava.com/
          Lucene home page
          "Introduction to Text Indexing with Apache Jakarta Lucene:" 這是篇簡要介紹使用Lucene的文章。
          Lucene in Action: 在使用Lucene方面進行了深入地講解。

          Renuka Sindhgatta 是一位資深的構架師,現在在印度班加羅爾市【 in the Software Engineering and Technology labs of Infosys Technologies Limited 】工作。
          posted @ 2006-04-21 13:53 點滴鑄就輝煌 閱讀(203) | 評論 (0)編輯 收藏
           
          我們都知道log4j是一個優秀的開源日志記錄項目,我們不僅可以對輸出的日志的格式自定義,還可以自己定義日志輸出的目的地,比如:屏幕,文本文件,數據庫,甚至能通過socket輸出。  

            現在讓我們對日志輸出到數據庫來進行配置  

            配置如下:  

           
           #---JDBC ---輸出到數據庫
            # JDBCAppender log4j.properties file
            #log4j.rootCategory=WARN,JDBC
            # APPENDER JDBC
            log4j.appender.JDBC=org.apache.log4j.jdbc.JDBCAppender
            log4j.appender.JDBC.driver=com.mysql.jdbc.Driver
            log4j.appender.JDBC.URL=jdbc:mysql://localhost:3306/test
            log4j.appender.JDBC.user=use
            log4j.appender.JDBC.password=password
            log4j.appender.JDBC.layout=org.apache.log4j.PatternLayout
            log4j.appender.JDBC.sql=INSERT INTO LOGGING (log_date, log_level,
           location, message) VALUES ('%d{ISO8601}', '%-5p', '%C,%L', '%m')
            
            表結構如下:
            
            log_date  varchar2(50)
            log_level varchar2(5)
            location  varchar2(100)
            message  varchar2(1000)
            
            筆者照做,但沒有運行成功,而且此種方法是利用傳統的數據庫連接方法,對于數據庫的
          管理和效率嚴重不足,在現在這個連接池橫行的時代,為什么我們不能給給Log4j配上連接池,
          讓Log4j利用數據連接池的連接和數據庫進行通訊。現查看Log4j的Api,
          log4j建議我們把其提供的JDBCAppender作為基類來使用,然后Override三個父類的方法:
          getConnection(),closeConnection(Connection con)和
          getLogStatement(LoggingEvent event)。
            
            原來如此,那就寫一個子類JDBCPoolAppender來替代這個JDBCAppender
            
            JDBCPoolAppender代碼和其相關代碼如下:
            
            JDBCPoolAppender.java:
            
            package common.log;
            import java.sql.Connection;
            import org.apache.log4j.spi.LoggingEvent;
            import java.sql.SQLException;
            import java.sql.Statement;
            import java.util.Iterator;
            import org.apache.log4j.spi.ErrorCode;
            import org.apache.log4j.PatternLayout;
            import common.sql.MyDB;
            import common.sql.GeneralDb;
            
            public class JDBCPoolAppender extends org.apache.log4j.jdbc.JDBCAppender {
            
            private MyDB mydb = null;
            protected String sqlname=""; //增加一個數據庫jndiName的屬性
            
            protected Connection connection = null;
            protected String sqlStatement = "";
            /**
            * size of LoggingEvent buffer before writting to the database.
            * Default is 1.
            */
            protected int bufferSize = 1;
            
            public JDBCPoolAppender() {
            super();
            }
            
            /**
            * ArrayList holding the buffer of Logging Events.
            */
            public void append(LoggingEvent event) {
            buffer.add(event);
            if (buffer.size() >= bufferSize)
            flushBuffer();
            }
            
            /**
            * By default getLogStatement sends the event to the required Layout object.
            * The layout will format the given pattern into a workable SQL string.
            *
            * Overriding this provides direct access to the LoggingEvent
            * when constructing the logging statement.
            *
            */
            protected String getLogStatement(LoggingEvent event) {
            return getLayout().format(event);
            }
            
            /**
            *
            * Override this to provide an alertnate method of getting
            * connections (such as caching). One method to fix this is to open
            * connections at the start of flushBuffer() and close them at the
            * end. I use a connection pool outside of JDBCAppender which is
            * accessed in an override of this method.
            * */
            protected void execute(String sql) throws SQLException {
            Connection con = null;
            Statement stmt = null;
            try {
            con = getConnection();
            stmt = con.createStatement();
            stmt.executeUpdate(sql);
            } catch (SQLException e) {
            if (stmt != null)
            stmt.close();
            throw e;
            }
            stmt.close();
            closeConnection(con);
            //System.out.println("Execute: " + sql);
            }
            
            /**
            * Override this to return the connection to a pool, or to clean up the
            * resource.
            *
            * The default behavior holds a single connection open until the appender
            * is closed (typically when garbage collected).
            */
            protected void closeConnection(Connection con) {
            mydb=null;
            try {
            if (connection != null && !connection.isClosed())
            connection.close();
            } catch (SQLException e) {
            errorHandler.error("Error closing connection", e,
            ErrorCode.GENERIC_FAILURE);
            }
            
            }
            
            /**
            * Override 此函數來利用連接池返回一個Connetion對象
            *
            */
            protected Connection getConnection() throws SQLException {
            try {
            mydb = GeneralDb.getInstance(sqlname);
            connection = mydb.getConnection();
            } catch (Exception e) {
            errorHandler.error("Error opening connection", e, ErrorCode.GENERIC_FAILURE);
            }
            return connection;
            }
            
            /**
            * Closes the appender, flushing the buffer first then closing the default
            * connection if it is open.
            */
            public void close() {
            flushBuffer();
            
            try {
            if (connection != null && !connection.isClosed())
            connection.close();
            } catch (SQLException e) {
            errorHandler.error("Error closing connection", e,
            ErrorCode.GENERIC_FAILURE);
            }
            this.closed = true;
            }
            
            /**
            * loops through the buffer of LoggingEvents, gets a
            * sql string from getLogStatement() and sends it to execute().
            * Errors are sent to the errorHandler.
            *
            * If a statement fails the LoggingEvent stays in the buffer!
            */
            public void flushBuffer() {
            //Do the actual logging
            removes.ensureCapacity(buffer.size());
            for (Iterator i = buffer.iterator(); i.hasNext(); ) {
            try {
            LoggingEvent logEvent = (LoggingEvent) i.next();
            String sql = getLogStatement(logEvent);
            execute(sql);
            removes.add(logEvent);
            } catch (SQLException e) {
            errorHandler.error("Failed to excute sql", e,
            ErrorCode.FLUSH_FAILURE);
            }
            }
            
            // remove from the buffer any events that were reported
            buffer.removeAll(removes);
            
            // clear the buffer of reported events
            removes.clear();
            }
            
            /** closes the appender before disposal */
            public void finalize() {
            close();
            }
            
            /**
            * JDBCAppender requires a layout.
            * */
            public boolean requiresLayout() {
            return true;
            }
            
            /**
            *
            */
            public void setSql(String s) {
            sqlStatement = s;
            if (getLayout() == null) {
            this.setLayout(new PatternLayout(s));
            } else {
            ((PatternLayout) getLayout()).setConversionPattern(s);
            }
            }
            
            /**
            * Returns pre-formated statement eg: insert into LogTable (msg) values ("%m")
            */
            public String getSql() {
            return sqlStatement;
            }
            
            public void setSqlname(String sqlname){
            sqlname=sqlname;
            }
            
            public String getSqlname(){
            return sqlname;
            }
            
            public void setBufferSize(int newBufferSize) {
            bufferSize = newBufferSize;
            buffer.ensureCapacity(bufferSize);
            removes.ensureCapacity(bufferSize);
            }
            
            public int getBufferSize() {
            return bufferSize;
            }
            }
            
            MyDB.java:
            package common.sql;
            import java.sql.*;
            import com.codestudio.sql.*; //引入開源項目Poolman數據庫連接池的包
            
            public class MyDB {
            public static final String module = MyDB.class.getName();
            private String dbName = "";
            private PoolMan plmn = null;
            
            public MyDB(String dbName) {
            try {
            if (plmn == null) {
            plmn = (PoolMan) Class.forName("com.codestudio.sql.PoolMan").
            newInstance();
            }
            } catch (Exception ec) {
            System.out.println(ec.toString()+module);
            }
            this.dbName = dbName;
            }
            
            private Connection getNewConnection() {
            Connection conn = null;
            try {
            conn = plmn.connect("jdbc:poolman://" + dbName);
            conn.setAutoCommit(true);
            } catch (Exception ec) {
            System.out.println(ec.toString()+"First:Connect sqlsever failed"+module);
            try {
            Thread.sleep(1000);
            conn = plmn.connect("jdbc:poolman://" + dbName);
            conn.setAutoCommit(true);
            } catch (Exception ecs) {
            System.out.println(ecs.toString()+"Again:Connect sqlsever faile"+module);
            }
            }
            return conn;
            }
            
            public Connection getConnection() {
            return getNewConnection();
            }
            }
            GeneralDb.java:
            
            package common.sql;
            
            package common.sql;
            import java.util.*;
            
            public class GeneralDb {
            private static Hashtable dbPool;
            public static MyDB getInstance(String dbname) {
            if (dbPool == null) {
            dbPool = new Hashtable();
            }
            MyDB db = (MyDB) dbPool.get(dbname);
            if (db == null) {
            db = new MyDB(dbname);
            dbPool.put(dbname, db);
            }
            return db;
            }
            }
            
            Log4j數據庫連接池的配置如下:
            log4j.appender.JDBC=common.log.JDBCPoolAppender
            log4j.appender.JDBC.sqlname=log
            log4j.appender.JDBC.layout=org.apache.log4j.PatternLayout
            log4j.appender.JDBC.sql=INSERT INTO LOGGING (log_date, log_level, 
          location, message) VALUES ('%d{ISO8601}', '%-5p', '%C,%L', '%m')
            
            poolman.xml配置如下:
            
            〈?xml version="1.0" encoding="UTF-8"?>
            〈poolman>
            〈management-mode>local〈/management-mode>
            〈datasource>
            〈dbname>log〈/dbname>
            〈jndiName>log〈/jndiName>
            〈driver>com.mysql.jdbc.Driver〈/driver>
            〈url>jdbc:mysql://localhost:3306/test〈/url>
            〈username>use〈/username>
            〈password>password〈/password>
            〈minimumSize>0〈/minimumSize>
            〈maximumSize>10〈/maximumSize>
            〈logFile>logs/mysql.log〈/logFile>
            〈/datasource>  
            〈/poolman>


            

            運行成功!對于JDBCPoolAppender的屬性(比如sqlname屬性)我們可以利用Log4j的反射機制隨便添加,只要在配置文件給其附上值即可應用,而原來的父類里面的一些屬性(username什么的)和其get,set方法由于在連接池中不需要,所以刪除。而在JDBCPoolAppender類中,我也只是將getConnection 方法Override ,在這個方法中我們可以根據需要生成我們的Connection對象,另外兩個方法大家可以根據需求來決定怎樣Override。)
          posted @ 2006-03-31 09:33 點滴鑄就輝煌 閱讀(779) | 評論 (0)編輯 收藏
           
          作者:未知 來源:未知 加入時間:2004-11-1 天新軟件園
          在平時工作中,難免會遇到把 XML 作為數據存儲格式。面對目前種類繁多的解決方案,哪個最適合我們呢?在這篇文章中,我對這四種主流方案做一個不完全評測,僅僅針對遍歷 XML 這塊來測試,因為遍歷 XML 是工作中使用最多的(至少我認為)。

            預 備

            測試環境:

            AMD 毒龍1.4G OC 1.5G、256M DDR333、Windows2000 Server SP4、Sun JDK 1.4.1+Eclipse 2.1+Resin 2.1.8,在 Debug 模式下測試。

            XML 文件格式如下:

          <?xml version="1.0" encoding="GB2312"?>
          <RESULT>
           <VALUE>
            <NO>A1234</NO>
            <ADDR>四川省XX縣XX鎮XX路X段XX號</ADDR>
           </VALUE>
           <VALUE>
            <NO>B1234</NO>
            <ADDR>四川省XX市XX鄉XX村XX組</ADDR>
           </VALUE>
          </RESULT>

            測試方法:

            采用 JSP 端調用Bean(至于為什么采用JSP來調用,請參考:http://blog.csdn.net/rosen/archive/2004/10/15/138324.aspx),讓每一種方案分別解析10K、100K、1000K、10000K的 XML 文件,計算其消耗時間(單位:毫秒)。

            JSP 文件:

          <%@ page contentType="text/html; charset=gb2312" %>
          <%@ page import="com.test.*"%>

          <html>
          <body>
          <%
          String args[]={""};
          MyXMLReader.main(args);
          %>
          </body>
          </html>

            測 試

            首先出場的是 DOM(JAXP Crimson 解析器)

            DOM 是用與平臺和語言無關的方式表示 XML 文檔的官方 W3C 標準。DOM 是以層次結構組織的節點或信息片斷的集合。這個層次結構允許開發人員在樹中尋找特定信息。分析該結構通常需要加載整個文檔和構造層次結構,然后才能做任何工作。由于它是基于信息層次的,因而 DOM 被認為是基于樹或基于對象的。DOM 以及廣義的基于樹的處理具有幾個優點。首先,由于樹在內存中是持久的,因此可以修改它以便應用程序能對數據和結構作出更改。它還可以在任何時候在樹中上下導航,而不是像 SAX 那樣是一次性的處理。DOM 使用起來也要簡單得多。

            另一方面,對于特別大的文檔,解析和加載整個文檔可能很慢且很耗資源,因此使用其他手段來處理這樣的數據會更好。這些基于事件的模型,比如 SAX。

            Bean文件:

          package com.test;

          import java.io.*;
          import java.util.*;
          import org.w3c.dom.*;
          import javax.xml.parsers.*;

          public class MyXMLReader{

           public static void main(String arge[]){
            long lasting =System.currentTimeMillis();
            try{
             File f=new File("data_10k.xml");
             DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
             DocumentBuilder builder=factory.newDocumentBuilder();
             Document doc = builder.parse(f);
             NodeList nl = doc.getElementsByTagName("VALUE");
             for (int i=0;i<nl.getLength();i++){
              System.out.print("車牌號碼:" + doc.getElementsByTagName("NO").item(i).getFirstChild().getNodeValue());
              System.out.println(" 車主地址:" + doc.getElementsByTagName("ADDR").item(i).getFirstChild().getNodeValue());
            }
            }catch(Exception e){
             e.printStackTrace();
            }
            System.out.println("運行時間:"+(System.currentTimeMillis() - lasting)+" 毫秒");
           }
          }

            10k消耗時間:265 203 219 172
            100k消耗時間:9172 9016 8891 9000
            1000k消耗時間:691719 675407 708375 739656
            10000k消耗時間:OutOfMemoryError

            接著是 SAX

            這種處理的優點非常類似于流媒體的優點。分析能夠立即開始,而不是等待所有的數據被處理。而且,由于應用程序只是在讀取數據時檢查數據,因此不需要將數據存儲在內存中。這對于大型文檔來說是個巨大的優點。事實上,應用程序甚至不必解析整個文檔;它可以在某個條件得到滿足時停止解析。一般來說,SAX 還比它的替代者 DOM 快許多。

          選擇 DOM 還是選擇 SAX ?

            對于需要自己編寫代碼來處理 XML 文檔的開發人員來說,??


          ???


          選擇 DOM 還是 SAX 解析模型是一個非常重要的設計決策。

            DOM 采用建立樹形結構的方式訪問 XML 文檔,而 SAX 采用的事件模型。

            DOM 解析器把 XML 文檔轉化為一個包含其內容的樹,并可以對樹進行遍歷。用 DOM 解析模型的優點是編程容易,開發人員只需要調用建樹的指令,然后利用navigation APIs訪問所需的樹節點來完成任務。可以很容易的添加和修改樹中的元素。然而由于使用 DOM 解析器的時候需要處理整個 XML 文檔,所以對性能和內存的要求比較高,尤其是遇到很大的 XML 文件的時候。由于它的遍歷能力,DOM 解析器常用于 XML 文檔需要頻繁的改變的服務中。

            SAX 解析器采用了基于事件的模型,它在解析 XML 文檔的時候可以觸發一系列的事件,當發現給定的tag的時候,它可以激活一個回調方法,告訴該方法制定的標簽已經找到。SAX 對內存的要求通常會比較低,因為它讓開發人員自己來決定所要處理的tag。特別是當開發人員只需要處理文檔中所包含的部分數據時,SAX 這種擴展能力得到了更好的體現。但用 SAX 解析器的時候編碼工作會比較困難,而且很難同時訪問同一個文檔中的多處不同數據。

            Bean文件:



          package com.test;
          import org.xml.sax.*;
          import org.xml.sax.helpers.*;
          import javax.xml.parsers.*;

          public class MyXMLReader extends DefaultHandler {

           java.util.Stack tags = new java.util.Stack();

           public MyXMLReader() {
            super();
           }

           public static void main(String args[]) {
            long lasting = System.currentTimeMillis();
            try {
             SAXParserFactory sf = SAXParserFactory.newInstance();
             SAXParser sp = sf.newSAXParser();
             MyXMLReader reader = new MyXMLReader();
             sp.parse(new InputSource("data_10k.xml"), reader);
            } catch (Exception e) {
             e.printStackTrace();
            }
            System.out.println("運行時間:" + (System.currentTimeMillis() - lasting) + " 毫秒");
           }

           public void characters(char ch[], int start, int length) throws SAXException {
            String tag = (String) tags.peek();
            if (tag.equals("NO")) {
             System.out.print("車牌號碼:" + new String(ch, start, length));
           }
           if (tag.equals("ADDR")) {
            System.out.println(" 地址:" + new String(ch, start, length));
           }
          }

          public void startElement(
           String uri,
           String localName,
           String qName,
           Attributes attrs) {
            tags.push(qName);
           }
          }



            10k消耗時間:110 47 109 78
            100k消耗時間:344 406 375 422
            1000k消耗時間:3234 3281 3688 3312
            10000k消耗時間:32578 34313 31797 31890 30328

            然后是 JDOM http://www.jdom.org/

            JDOM 的目的是成為 Java 特定文檔模型,它簡化與 XML 的交互并且比使用 DOM 實現更快。由于是第一個 Java 特定模型,JDOM 一直得到大力推廣和促進。正在考慮通過“Java 規范請求 JSR-102”將它最終用作“Java 標準擴展”。從 2000 年初就已經開始了 JDOM 開發。

            JDOM 與 DOM 主要有兩方面不同。首先,JDOM 僅使用具體類而不使用接口。這在某些方面簡化了 API,但是也限制了靈活性。第二,API 大量使用了 Collections 類,簡化了那些已經熟悉這些類的 Java 開發者的使用。

            JDOM 文檔聲明其目的是“使用 20%(或更少)的精力解決 80%(或更多)Java/XML 問題”(根據學習曲線假定為 20%)。JDOM 對于大多數 Java/XML 應用程序來說當然是有用的,并且大多數開發者發現 API 比 DOM 容易理解得多。JDOM 還包括對程序行為的相當廣泛檢查以防止用戶做任何在 XML 中無意義的事。然而,它仍需要您充分理解 XML 以便做一些超出基本的工作(或者甚至理解某些情況下的錯誤)。這也許是比學習 DOM 或 JDOM 接口都更有意義的工作。

            JDOM 自身不包含解析器。它通常使用 SAX2 解析器來解析和驗證輸入 XML 文檔(盡管它還可以將以前構造的 DOM 表示作為輸入)。它包含一些轉換器以將 JDOM 表示輸出成 SAX2 事件流、DOM 模型或 XML 文本文檔。JDOM 是在 Apache 許可證變體下發布的開放源碼。

            Bean文件:



          package com.test;

          import java.io.*;
          import java.util.*;
          import org.jdom.*;
          import org.jdom.input.*;

          public class MyXMLReader {

           public static void main(String arge[]) {
            long lasting = System.currentTimeMillis();
            try {
             SAXBuilder builder = new SAXBuilder();
             Document doc = builder.build(new File("data_10k.xml"));
             Element foo = doc.getRootElement();
             List allChildren = foo.getChildren();
             for(int i=0;i<allChildren.size();i++) {
              System.out.print("車牌號碼:" + ((Element)allChildren.get(i)).getChild("NO").getText());
              System.out.println(" 車主地址:" + ((Element)allChildren.get(i)).getChild("ADDR").getText());
             }
            } catch (Exception e) {
             e.printStackTrace();
            }
            System.out.println("運行時間:" + (System.currentTimeMillis() - lasting) + " 毫秒");
           }
          }



            10k消耗時間:125 62 187 94
            100k消耗時間:704 625 640 766
            1000k消耗時間:27984 30750 27859 30656
            10000k消耗時間:OutOfMemoryError

            最后是 DOM4J http://dom4j.sourceforge.net/

            雖然 DOM4J 代表了完全獨立的開發結果,但最初,它是 JDOM 的一種智能分支。它合并了許多超出基本 XML 文檔表示的功能,包括集成的 XPath 支持、XML Schema 支持以及用于大文檔或流化文檔的基于事件的處理。它還提供了構建文檔表示的選項,它通過 DOM4J API 和標準 DOM 接口具有并行訪問功能。從 2000 下半年開始,它就一直處于開發之中。

            為支持所有這些功能,DOM4J 使用接口和抽象基本類方法。DOM4J 大量使用了 API 中的 Collections 類,但是在許多情況下,它還提供一些替代方法以允許更好的性能或更直接的編碼方法。直接好處是,雖然 DOM4J 付出了更復雜的 API 的代價,但是它提供了比 JDOM 大得多的靈活性。

            在添加靈活性、XPath 集成和對大文檔處理的目標時,DOM4J 的目標與 JDOM 是一樣的:針對 Java 開發者的易用性和直觀操作。它還致力于成為比 JDOM 更完整的解決方案,實現在本質上處理所有 Java/XML 問題的目標。在完成該目標時,它比 JDOM 更少強調防止不正確的應用程序行為。

            DOM4J 是一個非常非常優秀的Java XML API,具有性能優異、功能強大和極端易用使用的特點,同時它也是一個開放源代碼的軟件。如今你可以看到越來越多的 Java 軟件都在使用 DOM4J 來讀寫 XML,特別值得一提的是連 Sun 的 JAXM 也在用 DOM4J。

            Bean文件:



          package com.test;

          import java.io.*;
          import java.util.*;
          import org.dom4j.*;
          import org.dom4j.io.*;

          public class MyXMLReader {

           public static void main(String arge[]) {
            long lasting = System.currentTimeMillis();
            try {
             File f = new File("data_10k.xml");
             SAXReader reader = new SAXReader();
             Document doc = reader.read(f);
             Element root = doc.getRootElement();
             Element foo;
             for (Iterator i = root.elementIterator("VALUE"); i.hasNext();) {
              foo = (Element) i.next();
              System.out.print("車牌號碼:" + foo.elementText("NO"));
              System.out.println(" 車主地址:" + foo.elementText("ADDR"));
             }
            } catch (Exception e) {
             e.printStackTrace();
            }
            System.out.println("運行時間:" + (System.currentTimeMillis() - lasting) + " 毫秒");
           }
          }



            10k消耗時間:109 78 109 31
            100k消耗時間:297 359 172 312
            1000k消耗時間:2281 2359 2344 2469
            10000k消耗時間:20938 19922 20031 21078

            JDOM 和 DOM 在性能測試時表現不佳,在測試 10M 文檔時內存溢出。在小文檔情況下還值得考慮使用 DOM 和 JDOM。雖然 JDOM 的開發者已經說明他們期望在正式發行版前專注性能問題,但是從性能觀點來看,它確實沒有值得推薦之處。另外,DOM 仍是一個非常好的選擇。DOM 實現廣泛應用于多種編程語言。它還是許多其它與 XML 相關的標準的基礎,因為它正式獲得 W3C 推薦(與基于非標準的 Java 模型相對),所以在某些類型的項目中可能也需要它(如在 JavaScript 中使用 DOM)。

            SAX表現較好,這要依賴于它特定的解析方式。一個 SAX 檢測即將到來的XML流,但并沒有載入到內存(當然當XML流被讀入時,會有部分文檔暫時隱藏在內存中)。

            無疑,DOM4J是這場測試的獲勝者,目前許多開源項目中大量采用 DOM4J,例如大名鼎鼎的 Hibernate 也用 DOM4J 來讀取 XML 配置文件。如果不考慮可移植性,那就采用DOM4J吧!
          posted @ 2006-03-29 16:13 點滴鑄就輝煌 閱讀(178) | 評論 (0)編輯 收藏
           
          Tidy 是 W3C 用來解析網頁的一個軟件包,可以方便地將 HTML 文檔轉換為符合 XML 標準的文檔,由于 XML 可以方便地使用 XSLT 技術對內容進行抽取,所以使用 Tidy 配合 XSLT 可以方便地將各種網頁的內容抽取出來,保存成我們需要的格式。
          通過 JTidy 可以方便地將標準的 HTML 網頁轉換為 XML 的 DOM 對象,然后,通過 XPaht 和 XSLT 將需要的內容抽取出來。
          ?
          使用 JTidy 抽取網頁內容的代碼如下:
          ?
          package com.tsinghua;
          import java.io.File;
          import java.io.FileInputStream;
          import java.io.FileOutputStream;
          import java.io.IOException;
          import java.io.InputStreamReader;
          import java.io.OutputStreamWriter;
          import java.util.logging.Level;
          import java.util.logging.Logger;
          import javax.xml.parsers.DocumentBuilder;
          import javax.xml.parsers.DocumentBuilderFactory;
          import javax.xml.parsers.ParserConfigurationException;
          import javax.xml.transform.Result;
          import javax.xml.transform.Source;
          import javax.xml.transform.Templates;
          import javax.xml.transform.Transformer;
          import javax.xml.transform.TransformerConfigurationException;
          import javax.xml.transform.TransformerException;
          import javax.xml.transform.TransformerFactory;
          import javax.xml.transform.dom.DOMSource;
          import javax.xml.transform.stream.StreamResult;
          import javax.xml.transform.stream.StreamSource;
          import org.w3c.dom.Document;
          import org.w3c.dom.Element;
          import org.w3c.dom.Node;
          import org.w3c.dom.NodeList;
          import org.w3c.tidy.Configuration;
          import org.w3c.tidy.Tidy;
          import org.xml.sax.SAXException;
          public class HTMLParserByW3CDOM {
          ?private Templates template;
          ?/*
          ? * 解析網頁
          ? * XSLTFileName?用于解析網頁的樣式表文件名
          ? * HTMLFileName 待解析的網頁文件名
          ? * OutputFileName 輸出文件名
          ? */
          ?public void parser(String HTMLFileName, String OutputFileName)
          ?{
          ??if( this.template != null){
          ???Document doc =? this.HTMLToXML( HTMLFileName );?// 解析網頁,返回 W3c Document 文檔對象
          ???Transformer(doc, OutputFileName);????// 使用樣式表轉換 Document 為最終結果
          ??}
          ?}
          ?
          ?/**
          ? * 解析網頁,轉換為 W3C Document 文檔對象
          ? * @param fileName?HTML 網頁的文件名
          ? * @return???utf-8 W3C Document 文檔對象
          ? */
          ?private Document HTMLToXML(String fileName) {
          ??Logger log = Logger.getLogger("HTMLToXML");
          ??Document doc = null;
          ??try{
          ???FileInputStream in = new FileInputStream( fileName );?// 打開文件,轉換為 UTF-8 編碼?
          ???InputStreamReader isr = new InputStreamReader(in, "GB2312");?// 源文件編碼為 gb2312
          ???
          ???File tmpNewFile = File.createTempFile("GB2312",".html");?// 轉換后的文件,設定編碼為 utf-8
          ???FileOutputStream out = new FileOutputStream( tmpNewFile );?// 需要將文件轉換為字符流
          ???OutputStreamWriter osw = new OutputStreamWriter( out , "UTF-8");// 指定目標編碼為 utf-8
          ???osw.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
          ???
          ???char[] buffer = new char[10240];??????// 文件緩沖區
          ???int len = 0;???????????// 使用字符讀取方式,循環讀取源文件內容
          ???while( (len = isr.read(buffer)) !=-1 )?????// 轉換后寫入目標文件中
          ???{
          ????osw.write( buffer, 0, len);
          ???}
          ???osw.close();???????????// 轉換完成
          ???isr.close();
          ???out.close();
          ???in.close();
          ???
          ???if( log.isLoggable( Level.INFO)){
          ????log.info("HTML 文檔轉 UTF-8 編碼完成!");
          ???}
          ???
          ???//??設置 tidy ,準備轉換
          ???Tidy tidy = new Tidy();
          ???tidy.setXmlOut(true);????// 輸出格式 xml
          ???tidy.setDropFontTags(true);???// 刪除字體節點
          ???tidy.setDropEmptyParas(true);??// 刪除空段落
          ???tidy.setFixComments(true);???// 修復注釋
          ???tidy.setFixBackslash(true);???// 修復反斜桿
          ???tidy.setMakeClean(true);???// 刪除混亂的表示
          ???tidy.setQuoteNbsp(false);???// 將空格輸出為 &nbsp;
          ???tidy.setQuoteMarks(false);???// 將雙引號輸出為 &quot;
          ???tidy.setQuoteAmpersand(true);??// 將 &amp; 輸出為 &
          ???tidy.setShowWarnings(false);??// 不顯示警告信息
          ???tidy.setCharEncoding(Configuration.UTF8);?// 文件編碼為 UTF8
          ???
          ???
          ???FileInputStream src = new FileInputStream( tmpNewFile );?//
          ???doc = tidy.parseDOM( src ,null );?// 通過 JTidy 將 HTML 網頁解析為
          ???src.close();???????????// W3C 的 Document 對象
          ???tmpNewFile.delete();?????????// 刪除臨時文件
          ???
          ???NodeList list = doc.getChildNodes();?????// 頁面中 DOCTYPE 中可能問題
          ???for(int i=0; i<list.getLength(); i++)?????// 刪除 DOCTYPE 元素
          ???{
          ????Node node = list.item(i);
          ????if( node.getNodeType() == Node.DOCUMENT_TYPE_NODE)?// 查找類型定義節點
          ????{
          ?????node.getParentNode().removeChild( node );
          ?????if( log.isLoggable( Level.INFO)){
          ??????log.info("已經將文檔定義節點刪除!" );
          ?????}
          ????}
          ???}
          ???
          ???list = doc.getElementsByTagName("script");????// 腳本中的注釋有時有問題
          ???for(int i=0; i<list.getLength(); i++){?????// 清理 script 元素
          ????Element script = (Element) list.item(i);
          ????if( script.getFirstChild() != null){
          ?????if( log.isLoggable( Level.FINEST)){
          ??????log.finest("刪除腳本元素: " + script.getFirstChild().getNodeValue());
          ?????}
          ?????script.removeChild( script.getFirstChild());
          ????}
          ???}
          ???
          ???list = doc.getElementsByTagName("span");????// sina 中 span 元素有時有問題
          ???for(int i=0; i<list.getLength(); i++){?????// 清理 span 元素
          ????Element span = (Element) list.item(i);
          ????span.getParentNode().removeChild( span );
          ????if( log.isLoggable( Level.FINEST)){
          ?????log.finest("刪除 span 元素: " );
          ????}
          ????
          ???}
          ???
          ???list = doc.getElementsByTagName("sohuadcode");???// 清除 sohuadcode 元素
          ???for(int i=0; i<list.getLength(); i++){
          ????Element sohuadcode = (Element) list.item(i);
          ????sohuadcode.getParentNode().removeChild( sohuadcode );
          ???}
          ???
          ???if( log.isLoggable( Level.INFO)){
          ????log.info("HTML 文檔解析 DOM 完成.");
          ???}
          ??}
          ??catch(Exception e)
          ??{
          ???log.severe(e.getMessage());
          ???e.printStackTrace();
          ??}finally
          ??{
          ???
          ??}
          ??return doc;
          ?}
          ?
          ?/**
          ? * 解析轉換的樣式表,保存為模板
          ? * @param xsltFileName??樣式表文件名
          ? * @return?????樣式表模板對象
          ? */
          ?public Templates setXSLT(String xsltFileName)
          ?{
          ??Logger log = Logger.getLogger( "setXSLT" );
          ??File xsltFile = new File( xsltFileName );
          ??StreamSource xsltSource = new StreamSource( xsltFile );??// 使用 JAXP 標準方法建立樣式表的模板對象
          ??TransformerFactory tff = TransformerFactory.newInstance();?// 可以重復利用這個模板
          ??Templates template = null;
          ??try {
          ???template = tff.newTemplates( xsltSource );
          ???if( log.isLoggable( Level.INFO)){
          ????log.info("樣式表文件 " + xsltFileName + " 解析完成");
          ???}
          ??} catch (TransformerConfigurationException e) {
          ???log.severe( e.getMessage() );
          ??}
          ??this.template = template;
          ??return template;
          ?}
          ?
          ?/**
          ? * 使用樣式表轉換文檔對象,得到最終的結果
          ? * @param doc???文檔對象
          ? * @param outFileName?保存轉換結果的文件名
          ? */
          ?private void Transformer(Document doc , String outFileName )
          ?{
          ??Logger log = Logger.getLogger( "Transformer" );
          ??try {
          ???Source source = new DOMSource( doc );
          ???
          ???File outFile = new File( outFileName );
          ???Result result = new StreamResult( outFile );
          ???
          ???Transformer transformer = template.newTransformer();?// 使用保存的樣式表模板對象
          ???transformer.transform(source, result );?????// 生成轉換器,轉換文檔對象
          ???if( log.isLoggable( Level.INFO)){
          ????log.info("轉換完成, 請查看 " + outFileName + " 文件。");
          ???}
          ??} catch (Exception e) {
          ???log.severe( e.getMessage() );
          ??}?
          ?}
          }
          posted @ 2006-03-25 11:16 點滴鑄就輝煌 閱讀(825) | 評論 (1)編輯 收藏
           

          最近要從網頁中提取信息,想先把html轉換成標準的xml格式,然后方便使用dom4j進行后續的分析,試用了不少現成的類庫,JTidy、NekoHTML、HTML Parser、Jericho,最后還是使用了JTidy。

          只是r8 snapshot還只是一個nightly builds,前面的r7版更已經是4年前的事了,這個項目就這樣荒廢了?可能是參與的人太少又或者是覺得已經成熟?

          JTidy提供了一個語法檢查器和標簽補償器,能夠對各種亂七八糟的html進行修復,使之符合xhtml標準。

          r8 snapshot相對于r7改變了一些參數的設置方法,特別是在字符編碼處理上,用setInputEncoding和setOutputEncoding方法來確定輸入和輸出文件的字符編碼,可以使用任何有效的Java編碼名稱,這就比以前的強多了。

          總體上解析的結果還不錯,只是有些地方還需要對生成文件進行手工調整,或者自己再編一段代碼處理一下,已經不是大問題了。

          一些常用的參數設置:

          setAltText(java.lang.String altText)?
          加上默認的alt屬性值
          setBreakBeforeBR(boolean breakBeforeBR)
          在換行<br />之前加一空行
          setCharEncoding(int charencoding)
          已廢棄
          setConfigurationFromFile(java.lang.String filename)
          從文件中讀取配置信息
          setConfigurationFromProps(java.util.Properties props)
          從properties中讀取配置信息
          setErrfile(java.lang.String errfile)
          錯誤輸出文件
          setFixBackslash(boolean fixBackslash)
          URL中用/取代\
          setForceOutput(boolean forceOutput)
          不管生成的xml是否有錯,強制輸出。
          setHideComments(boolean hideComments)?
          結果中不生成注釋
          setInputEncoding(java.lang.String encoding)
          輸入編碼
          setLogicalEmphasis(boolean logicalEmphasis)
          用em替代i,strong替代b
          setMessageListener(TidyMessageListener listener)
          加入一個TidyMessageListener監聽器
          setOnlyErrors(boolean onlyErrors)?
          只輸出錯誤文件
          setOutputEncoding(java.lang.String encoding)?
          輸出編碼
          setPrintBodyOnly(boolean bodyOnly)?
          只輸出body中的部分
          setRepeatedAttributes(int repeatedAttributes)?
          重復屬性的處理
          setSpaces(int spaces)?
          每行前的空格數,就是縮進格式
          setTidyMark(boolean tidyMark)?
          是否生成tidy標記
          setTrimEmptyElements(boolean trimEmpty)?
          不輸出空元素
          setUpperCaseAttrs(boolean upperCaseAttrs)?
          屬性變大寫
          setUpperCaseTags(boolean upperCaseTags)?
          標記變大寫
          setWraplen(int wraplen)?
          多長換行
          setXHTML(boolean xhtml)?
          輸出xhtml
          setXmlOut(boolean xmlOut)
          輸出xml
          setXmlPi(boolean xmlPi)?
          文件頭輸出xml標記
          setXmlSpace(boolean xmlSpace)
          加入xml名字空間屬性

          使用方法極為easy,定義輸入輸出流,用tidy.parse()轉換就行了:

          BufferedInputStream in;

          FileOutputStream out;

          Tidy tidy = new Tidy();

          tidy.setConfigurationFromFile(configFileName);//配置文件,寫入上面的設置參數

          try {

          in = new BufferedInputStream(new FileInputStream(srcFileName));

          out = new FileOutputStream(outFileName);

          tidy.parse(in, out);

          } catch (IOException e) {

          System.out.println(e);

          }

          posted @ 2006-03-25 11:15 點滴鑄就輝煌 閱讀(777) | 評論 (2)編輯 收藏
           

          數據庫設計經驗談(夜來香)

          一個成功的管理系統,是由:[50% 的業務 + 50% 的軟件] 所組成,而 50% 的成功軟件又有 [25% 的數據庫 + 25% 的程序] 所組成,數據庫設計的好壞是一個關鍵。如果把企業的數據比做生命所必需的血液,那么數據庫的設計就是應用中最重要的一部分。有關數據庫設計的材料汗牛充棟,大學學位課程里也有專門的講述。不過,就如我們反復強調的那樣,再好的老師也比不過經驗的教誨。所以我歸納歷年來所走的彎路及體會,并在網上找了些對數據庫設計頗有造詣的專業人士給大家傳授一些設計數據庫的技巧和經驗。精選了其中的 60 個最佳技巧,并把這些技巧編寫成了本文,為了方便索引其內容劃分為 5 個部分:

          第 1 部分 - 設計數據庫之前
          這一部分羅列了 12 個基本技巧,包括命名規范和明確業務需求等。
          第 2 部分 - 設計數據庫表
          總共 24 個指南性技巧,涵蓋表內字段設計以及應該避免的常見問題等。
          第 3 部分 - 選擇鍵
          怎么選擇鍵呢?這里有 10 個技巧專門涉及系統生成的主鍵的正確用法,還有何 時以及如何索引字段以獲得最佳性能等。
          第 4 部分 - 保證數據完整性
          討論如何保持數據庫的清晰和健壯,如何把有害數據降低到最小程度。
          第 5 部分 - 各種小技巧
          不包括在以上 4 個部分中的其他技巧,五花八門,有了它們希望你的數據庫開發工作會更輕松一些。
          第 1 部分 - 設計數據庫之前
          考察現有環境
          在設計一個新數據庫時,你不但應該仔細研究業務需求而且還要考察現有的系統。大多數數據庫項目都不是從頭開始建立的;通常,機構內總會存在用來滿足特定需求的現有系統(可能沒有實現自動計算)。顯然,現有系統并不完美,否則你就不必再建立新系統了。但是對舊系統的研究可以讓你發現一些可能會忽略的細微問題。一般來說,考察現有系統對你絕對有好處。
          定義標準的對象命名規范
          一定要定義數據庫對象的命名規范。對數據庫表來說,從項目一開始就要確定表名是采用復數還是單數形式。此外還要給表的別名定義簡單規則(比方說,如果表名是一個單詞,別名就取單詞的前 4 個字母;如果表名是兩個單詞,就各取兩個單詞的前兩個字母組成 4 個字母長的別名;如果表的名字由 3 個單詞組成,你不妨從頭兩個單詞中各取一個然后從最后一個單詞中再取出兩個字母,結果還是組成 4 字母長的別名,其余依次類推)對工作用表來說,表名可以加上前綴 WORK_ 后面附上采用該表的應用程序的名字。表內的列[字段]要針對鍵采用一整套設計規則。比如,如果鍵是數字類型,你可以用 _N 作為后綴;如果是字符類型則可以采用 _C 后綴。對列[字段]名應該采用標準的前綴和后綴。再如,假如你的表里有好多“money”字段,你不妨給每個列[字段]增加一個 _M 后綴。還有,日期列[字段]最好以 D_ 作為名字打頭。

          檢查表名、報表名和查詢名之間的命名規范。你可能會很快就被這些不同的數據庫要素的名稱搞糊涂了。假如你堅持統一地命名這些數據庫的不同組成部分,至少你應該在這些對象名字的開頭用 Table、Query 或者 Report 等前綴加以區別。

          如果采用了 Microsoft Access,你可以用 qry、rpt、tbl 和 mod 等符號來標識對象(比如 tbl_Employees)。我在和 SQL Server 打交道的時候還用過 tbl 來索引表,但我用 sp_company (現在用 sp_feft_)標識存儲過程,因為在有的時候如果我發現了更好的處理辦法往往會保存好幾個拷貝。我在實現 SQL Server 2000 時用 udf_ (或者類似的標記)標識我編寫的函數。
          工欲善其事, 必先利其器
          采用理想的數據庫設計工具,比如:SyBase 公司的 PowerDesign,她支持 PB、VB、Delphe 等語言,通過 ODBC 可以連接市面上流行的 30 多個數據庫,包括 dBase、FoxPro、VFP、SQL Server 等,今后有機會我將著重介紹 PowerDesign 的使用。
          獲取數據模式資源手冊
          正在尋求示例模式的人可以閱讀《數據模式資源手冊》一書,該書由 Len Silverston、W. H. Inmon 和 Kent Graziano 編寫,是一本值得擁有的最佳數據建模圖書。該書包括的章節涵蓋多種數據領域,比如人員、機構和工作效能等。其他的你還可以參考:[1]薩師煊 王珊著 數據庫系統概論(第二版)高等教育出版社 1991、[2][美] Steven M.Bobrowski 著 Oracle 7 與客戶/服務器計算技術從入門到精通 劉建元等譯 電子工業出版社,1996、[3]周中元 信息系統建模方法(下) 電子與信息化 1999年第3期,1999
          暢想未來,但不可忘了過去的教訓
          我發現詢問用戶如何看待未來需求變化非常有用。這樣做可以達到兩個目的:首先,你可以清楚地了解應用設計在哪個地方應該更具靈活性以及如何避免性能瓶頸;其次,你知道發生事先沒有確定的需求變更時用戶將和你一樣感到吃驚。

          一定要記住過去的經驗教訓!我們開發人員還應該通過分享自己的體會和經驗互相幫助。即使用戶認為他們再也不需要什么支持了,我們也應該對他們進行這方面的教育,我們都曾經面臨過這樣的時刻“當初要是這么做了該多好..”。
          在物理實踐之前進行邏輯設計
          在深入物理設計之前要先進行邏輯設計。隨著大量的 CASE 工具不斷涌現出來,你的設計也可以達到相當高的邏輯水準,你通常可以從整體上更好地了解數據庫設計所需要的方方面面。
          了解你的業務
          在你百分百地確定系統從客戶角度滿足其需求之前不要在你的 ER(實體關系)模式中加入哪怕一個數據表(怎么,你還沒有模式?那請你參看技巧 9)。了解你的企業業務可以在以后的開發階段節約大量的時間。一旦你明確了業務需求,你就可以自己做出許多決策了。

          一旦你認為你已經明確了業務內容,你最好同客戶進行一次系統的交流。采用客戶的術語并且向他們解釋你所想到的和你所聽到的。同時還應該用可能、將會和必須等詞匯表達出系統的關系基數。這樣你就可以讓你的客戶糾正你自己的理解然后做好下一步的 ER 設計。
          創建數據字典和 ER 圖表
          一定要花點時間創建 ER 圖表和數據字典。其中至少應該包含每個字段的數據類型和在每個表內的主外鍵。創建 ER 圖表和數據字典確實有點費時但對其他開發人員要了解整個設計卻是完全必要的。越早創建越能有助于避免今后面臨的可能混亂,從而可以讓任何了解數據庫的人都明確如何從數據庫中獲得數據。

          有一份諸如 ER 圖表等最新文檔其重要性如何強調都不過分,這對表明表之間關系很有用,而數據字典則說明了每個字段的用途以及任何可能存在的別名。對 SQL 表達式的文檔化來說這是完全必要的。
          創建模式
          一張圖表勝過千言萬語:開發人員不僅要閱讀和實現它,而且還要用它來幫助自己和用戶對話。模式有助于提高協作效能,這樣在先期的數據庫設計中幾乎不可能出現大的問題。模式不必弄的很復雜;甚至可以簡單到手寫在一張紙上就可以了。只是要保證其上的邏輯關系今后能產生效益。
          從輸入輸出下手
          在定義數據庫表和字段需求(輸入)時,首先應檢查現有的或者已經設計出的報表、查詢和視圖(輸出)以決定為了支持這些輸出哪些是必要的表和字段。舉個簡單的例子:假如客戶需要一個報表按照郵政編碼排序、分段和求和,你要保證其中包括了單獨的郵政編碼字段而不要把郵政編碼糅進地址字段里。
          報表技巧
          要了解用戶通常是如何報告數據的:批處理還是在線提交報表?時間間隔是每天、每周、每月、每個季度還是每年?如果需要的話還可以考慮創建總結表。系統生成的主鍵在報表中很難管理。用戶在具有系統生成主鍵的表內用副鍵進行檢索往往會返回許多重復數據。這樣的檢索性能比較低而且容易引起混亂。
          理解客戶需求
          看起來這應該是顯而易見的事,但需求就是來自客戶(這里要從內部和外部客戶的角度考慮)。不要依賴用戶寫下來的需求,真正的需求在客戶的腦袋里。你要讓客戶解釋其需求,而且隨著開發的繼續,還要經常詢問客戶保證其需求仍然在開發的目的之中。一個不變的真理是:“只有我看見了我才知道我想要的是什么”必然會導致大量的返工,因為數據庫沒有達到客戶從來沒有寫下來的需求標準。而更糟的是你對他們需求的解釋只屬于你自己,而且可能是完全錯誤的。
          第 2 部分 - 設計表和字段
          檢查各種變化
          我在設計數據庫的時候會考慮到哪些數據字段將來可能會發生變更。比方說,姓氏就是如此(注意是西方人的姓氏,比如女性結婚后從夫姓等)。所以,在建立系統存儲客戶信息時,我傾向于在單獨的一個數據表里存儲姓氏字段,而且還附加起始日和終止日等字段,這樣就可以跟蹤這一數據條目的變化。
          采用有意義的字段名
          有一回我參加開發過一個項目,其中有從其他程序員那里繼承的程序,那個程序員喜歡用屏幕上顯示數據指示用語命名字段,這也不賴,但不幸的是,她還喜歡用一些奇怪的命名法,其命名采用了匈牙利命名和控制序號的組合形式,比如 cbo1、txt2、txt2_b 等等。
          除非你在使用只面向你的縮寫字段名的系統,否則請盡可能地把字段描述的清楚些。當然,也別做過頭了,比如 Customer_Shipping_Address_Street_Line_1,雖然很富有說明性,但沒人愿意鍵入這么長的名字,具體尺度就在你的把握中。
          采用前綴命名
          如果多個表里有好多同一類型的字段(比如 FirstName),你不妨用特定表的前綴(比如 CusLastName)來幫助你標識字段。

          時效性數據應包括“最近更新日期/時間”字段。時間標記對查找數據問題的原因、按日期重新處理/重載數據和清除舊數據特別有用。
          標準化和數據驅動
          數據的標準化不僅方便了自己而且也方便了其他人。比方說,假如你的用戶界面要訪問外部數據源(文件、XML 文檔、其他數據庫等),你不妨把相應的連接和路徑信息存儲在用戶界面支持表里。還有,如果用戶界面執行工作流之類的任務(發送郵件、打印信箋、修改記錄狀態等),那么產生工作流的數據也可以存放在數據庫里。預先安排總需要付出努力,但如果這些過程采用數據驅動而非硬編碼的方式,那么策略變更和維護都會方便得多。事實上,如果過程是數據驅動的,你就可以把相當大的責任推給用戶,由用戶來維護自己的工作流過程。
          標準化不能過頭
          對那些不熟悉標準化一詞(normalization)的人而言,標準化可以保證表內的字段都是最基礎的要素,而這一措施有助于消除數據庫中的數據冗余。標準化有好幾種形式,但 Third Normal Form(3NF)通常被認為在性能、擴展性和數據完整性方面達到了最好平衡。簡單來說,3NF 規定:
          * 表內的每一個值都只能被表達一次。
          * 表內的每一行都應該被唯一的標識(有唯一鍵)。
          * 表內不應該存儲依賴于其他鍵的非鍵信息。
          遵守 3NF 標準的數據庫具有以下特點:有一組表專門存放通過鍵連接起來的關聯數據。比方說,某個存放客戶及其有關定單的 3NF 數據庫就可能有兩個表:Customer 和 Order。Order 表不包含定單關聯客戶的任何信息,但表內會存放一個鍵值,該鍵指向 Customer 表里包含該客戶信息的那一行。
          更高層次的標準化也有,但更標準是否就一定更好呢?答案是不一定。事實上,對某些項目來說,甚至就連 3NF 都可能給數據庫引入太高的復雜性。

          為了效率的緣故,對表不進行標準化有時也是必要的,這樣的例子很多。曾經有個開發餐飲分析軟件的活就是用非標準化表把查詢時間從平均 40 秒降低到了兩秒左右。雖然我不得不這么做,但我絕不把數據表的非標準化當作當然的設計理念。而具體的操作不過是一種派生。所以如果表出了問題重新產生非標準化的表是完全可能的。
          Microsoft Visual FoxPro 報表技巧
          如果你正在使用 Microsoft Visual FoxPro,你可以用對用戶友好的字段名來代替編號的名稱:比如用 Customer Name 代替 txtCNaM。這樣,當你用向導程序 [Wizards,臺灣人稱為‘精靈’] 創建表單和報表時,其名字會讓那些不是程序員的人更容易閱讀。
          不活躍或者不采用的指示符
          增加一個字段表示所在記錄是否在業務中不再活躍挺有用的。不管是客戶、員工還是其他什么人,這樣做都能有助于再運行查詢的時候過濾活躍或者不活躍狀態。同時還消除了新用戶在采用數據時所面臨的一些問題,比如,某些記錄可能不再為他們所用,再刪除的時候可以起到一定的防范作用。
          使用角色實體定義屬于某類別的列[字段]
          在需要對屬于特定類別或者具有特定角色的事物做定義時,可以用角色實體來創建特定的時間關聯關系,從而可以實現自我文檔化。
          這里的含義不是讓 PERSON 實體帶有 Title 字段,而是說,為什么不用 PERSON 實體和 PERSON_TYPE 實體來描述人員呢?比方說,當 John Smith, Engineer 提升為 John Smith, Director 乃至最后爬到 John Smith, CIO 的高位,而所有你要做的不過是改變兩個表 PERSON 和 PERSON_TYPE 之間關系的鍵值,同時增加一個日期/時間字段來知道變化是何時發生的。這樣,你的 PERSON_TYPE 表就包含了所有 PERSON 的可能類型,比如 Associate、Engineer、Director、CIO 或者 CEO 等。
          還有個替代辦法就是改變 PERSON 記錄來反映新頭銜的變化,不過這樣一來在時間上無法跟蹤個人所處位置的具體時間。
          采用常用實體命名機構數據
          組織數據的最簡單辦法就是采用常用名字,比如:PERSON、ORGANIZATION、ADDRESS 和 PHONE 等等。當你把這些常用的一般名字組合起來或者創建特定的相應副實體時,你就得到了自己用的特殊版本。開始的時候采用一般術語的主要原因在于所有的具體用戶都能對抽象事物具體化。
          有了這些抽象表示,你就可以在第 2 級標識中采用自己的特殊名稱,比如,PERSON 可能是 Employee、Spouse、Patient、Client、Customer、Vendor 或者 Teacher 等。同樣的,ORGANIZATION 也可能是 MyCompany、MyDepartment、Competitor、Hospital、Warehouse、Government 等。最后 ADDRESS 可以具體為 Site、Location、Home、Work、Client、Vendor、Corporate 和 FieldOffice 等。
          采用一般抽象術語來標識“事物”的類別可以讓你在關聯數據以滿足業務要求方面獲得巨大的靈活性,同時這樣做還可以顯著降低數據存儲所需的冗余量。
          用戶來自世界各地
          在設計用到網絡或者具有其他國際特性的數據庫時,一定要記住大多數國家都有不同的字段格式,比如郵政編碼等,有些國家,比如新西蘭就沒有郵政編碼一說。
          數據重復需要采用分立的數據表
          如果你發現自己在重復輸入數據,請創建新表和新的關系。
          每個表中都應該添加的 3 個有用的字段
          * dRecordCreationDate,在 VB 下默認是 Now(),而在 SQL Server 下默認為 GETDATE()
          * sRecordCreator,在 SQL Server 下默認為 NOT NULL DEFAULT USER
          * nRecordVersion,記錄的版本標記;有助于準確說明記錄中出現 null 數據或者丟失數據的原因
          對地址和電話采用多個字段
          描述街道地址就短短一行記錄是不夠的。Address_Line1、Address_Line2 和 Address_Line3 可以提供更大的靈活性。還有,電話號碼和郵件地址最好擁有自己的數據表,其間具有自身的類型和標記類別。

          過分標準化可要小心,這樣做可能會導致性能上出現問題。雖然地址和電話表分離通常可以達到最佳狀態,但是如果需要經常訪問這類信息,或許在其父表中存放“首選”信息(比如 Customer 等)更為妥當些。非標準化和加速訪問之間的妥協是有一定意義的。
          使用多個名稱字段
          我覺得很吃驚,許多人在數據庫里就給 name 留一個字段。我覺得只有剛入門的開發人員才會這么做,但實際上網上這種做法非常普遍。我建議應該把姓氏和名字當作兩個字段來處理,然后在查詢的時候再把他們組合起來。

          我最常用的是在同一表中創建一個計算列[字段],通過它可以自動地連接標準化后的字段,這樣數據變動的時候它也跟著變。不過,這樣做在采用建模軟件時得很機靈才行。總之,采用連接字段的方式可以有效的隔離用戶應用和開發人員界面。
          提防大小寫混用的對象名和特殊字符
          過去最令我惱火的事情之一就是數據庫里有大小寫混用的對象名,比如 CustomerData。這一問題從 Access 到 Oracle 數據庫都存在。我不喜歡采用這種大小寫混用的對象命名方法,結果還不得不手工修改名字。想想看,這種數據庫/應用程序能混到采用更強大數據庫的那一天嗎?采用全部大寫而且包含下劃符的名字具有更好的可讀性(CUSTOMER_DATA),絕對不要在對象名的字符之間留空格。
          小心保留詞
          要保證你的字段名沒有和保留詞、數據庫系統或者常用訪問方法沖突,比如,最近我編寫的一個 ODBC 連接程序里有個表,其中就用了 DESC 作為說明字段名。后果可想而知!DESC 是 DESCENDING 縮寫后的保留詞。表里的一個 SELECT * 語句倒是能用,但我得到的卻是一大堆毫無用處的信息。
          保持字段名和類型的一致性
          在命名字段并為其指定數據類型的時候一定要保證一致性。假如字段在某個表中叫做“agreement_number”,你就別在另一個表里把名字改成“ref1”。假如數據類型在一個表里是整數,那在另一個表里可就別變成字符型了。記住,你干完自己的活了,其他人還要用你的數據庫呢。
          仔細選擇數字類型
          在 SQL 中使用 smallint 和 tinyint 類型要特別小心,比如,假如你想看看月銷售總額,你的總額字段類型是 smallint,那么,如果總額超過了 $32,767 你就不能進行計算操作了。
          刪除標記
          在表中包含一個“刪除標記”字段,這樣就可以把行標記為刪除。在關系數據庫里不要單獨刪除某一行;最好采用清除數據程序而且要仔細維護索引整體性。
          避免使用觸發器
          觸發器的功能通常可以用其他方式實現。在調試程序時觸發器可能成為干擾。假如你確實需要采用觸發器,你最好集中對它文檔化。
          包含版本機制
          建議你在數據庫中引入版本控制機制來確定使用中的數據庫的版本。無論如何你都要實現這一要求。時間一長,用戶的需求總是會改變的。最終可能會要求修改數據庫結構。雖然你可以通過檢查新字段或者索引來確定數據庫結構的版本,但我發現把版本信息直接存放到數據庫中不更為方便嗎?。
          給文本字段留足余量
          ID 類型的文本字段,比如客戶 ID 或定單號等等都應該設置得比一般想象更大,因為時間不長你多半就會因為要添加額外的字符而難堪不已。比方說,假設你的客戶 ID 為 10 位數長。那你應該把數據庫表字段的長度設為 12 或者 13 個字符長。這算浪費空間嗎?是有一點,但也沒你想象的那么多:一個字段加長 3 個字符在有 1 百萬條記錄,再加上一點索引的情況下才不過讓整個數據庫多占據 3MB 的空間。但這額外占據的空間卻無需將來重構整個數據庫就可以實現數據庫規模的增長了。身份證的號碼從 15 位變成 18 位就是最好和最慘痛的例子。
          列[字段]命名技巧
          我們發現,假如你給每個表的列[字段]名都采用統一的前綴,那么在編寫 SQL 表達式的時候會得到大大的簡化。這樣做也確實有缺點,比如破壞了自動表連接工具的作用,后者把公共列[字段]名同某些數據庫聯系起來,不過就連這些工具有時不也連接錯誤嘛。舉個簡單的例子,假設有兩個表:
          Customer 和 Order。Customer 表的前綴是 cu_,所以該表內的子段名如下:cu_name_id、cu_surname、cu_initials 和cu_address 等。Order 表的前綴是 or_,所以子段名是:
          or_order_id、or_cust_name_id、or_quantity 和 or_description 等。
          這樣從數據庫中選出全部數據的 SQL 語句可以寫成如下所示:
          Select * From Customer, Order Where cu_surname = "MYNAME" ;
          and cu_name_id = or_cust_name_id and or_quantity = 1
          在沒有這些前綴的情況下則寫成這個樣子(用別名來區分):
          Select * From Customer, Order Where Customer.surname = "MYNAME" ;
          and Customer.name_id = Order.cust_name_id and Order.quantity = 1
          第 1 個 SQL 語句沒少鍵入多少字符。但如果查詢涉及到 5 個表乃至更多的列[字段]你就知道這個技巧多有用了。
          第 3 部分 - 選擇鍵和索引
          數據采掘要預先計劃
          我所在的某一客戶部門一度要處理 8 萬多份聯系方式,同時填寫每個客戶的必要數據(這絕對不是小活)。我從中還要確定出一組客戶作為市場目標。當我從最開始設計表和字段的時候,我試圖不在主索引里增加太多的字段以便加快數據庫的運行速度。然后我意識到特定的組查詢和信息采掘既不準確速度也不快。結果只好在主索引中重建而且合并了數據字段。我發現有一個指示計劃相當關鍵——當我想創建系統類型查找時為什么要采用號碼作為主索引字段呢?我可以用傳真號碼進行檢索,但是它幾乎就象系統類型一樣對我來說并不重要。采用后者作為主字段,數據庫更新后重新索引和檢索就快多了。

          可操作數據倉庫(ODS)和數據倉庫(DW)這兩種環境下的數據索引是有差別的。在 DW 環境下,你要考慮銷售部門是如何組織銷售活動的。他們并不是數據庫管理員,但是他們確定表內的鍵信息。這里設計人員或者數據庫工作人員應該分析數據庫結構從而確定出性能和正確輸出之間的最佳條件。
          使用系統生成的主鍵
          這類同技巧 1,但我覺得有必要在這里重復提醒大家。假如你總是在設計數據庫的時候采用系統生成的鍵作為主鍵,那么你實際控制了數據庫的索引完整性。這樣,數據庫和非人工機制就有效地控制了對存儲數據中每一行的訪問。
          采用系統生成鍵作為主鍵還有一個優點:當你擁有一致的鍵結構時,找到邏輯缺陷很容易。
          分解字段用于索引
          為了分離命名字段和包含字段以支持用戶定義的報表,請考慮分解其他字段(甚至主鍵)為其組成要素以便用戶可以對其進行索引。索引將加快 SQL 和報表生成器腳本的執行速度。比方說,我通常在必須使用 SQL LIKE 表達式的情況下創建報表,因為 case number 字段無法分解為 year、serial number、case type 和 defendant code 等要素。性能也會變壞。假如年度和類型字段可以分解為索引字段那么這些報表運行起來就會快多了。
          鍵設計 4 原則
          * 為關聯字段創建外鍵。
          * 所有的鍵都必須唯一。
          * 避免使用復合鍵。
          * 外鍵總是關聯唯一的鍵字段。
          別忘了索引
          索引是從數據庫中獲取數據的最高效方式之一。95% 的數據庫性能問題都可以采用索引技術得到解決。作為一條規則,我通常對邏輯主鍵使用唯一的成組索引,對系統鍵(作為存儲過程)采用唯一的非成組索引,對任何外鍵列[字段]采用非成組索引。不過,索引就象是鹽,太多了菜就咸了。你得考慮數據庫的空間有多大,表如何進行訪問,還有這些訪問是否主要用作讀寫。

          大多數數據庫都索引自動創建的主鍵字段,但是可別忘了索引外鍵,它們也是經常使用的鍵,比如運行查詢顯示主表和所有關聯表的某條記錄就用得上。還有,不要索引 memo/note 字段,不要索引大型字段(有很多字符),這樣作會讓索引占用太多的存儲空間。
          不要索引常用的小型表
          不要為小型數據表設置任何鍵,假如它們經常有插入和刪除操作就更別這樣作了。對這些插入和刪除操作的索引維護可能比掃描表空間消耗更多的時間。
          不要把社會保障號碼(SSN)或身份證號碼(ID)選作鍵
          永遠都不要使用 SSN 或 ID 作為數據庫的鍵。除了隱私原因以外,須知政府越來越趨向于不準許把 SSN 或 ID 用作除收入相關以外的其他目的,SSN 或 ID 需要手工輸入。永遠不要使用手工輸入的鍵作為主鍵,因為一旦你輸入錯誤,你唯一能做的就是刪除整個記錄然后從頭開始。

          我在破解他人的程序時候,我看到很多人把 SSN 或 ID 還曾被用做系列號,當然盡管這么做是非法的。而且人們也都知道這是非法的,但他們已經習慣了。后來,隨著盜取身份犯罪案件的增加,我現在的同行正痛苦地從一大攤子數據中把 SSN 或 ID 刪除。
          不要用用戶的鍵
          在確定采用什么字段作為表的鍵的時候,可一定要小心用戶將要編輯的字段。通常的情況下不要選擇用戶可編輯的字段作為鍵。這樣做會迫使你采取以下兩個措施:
          * 在創建記錄之后對用戶編輯字段的行為施加限制。假如你這么做了,你可能會發現你的應用程序在商務需求突然發生變化,而用戶需要編輯那些不可編輯的字段時缺乏足夠的靈活性。當用戶在輸入數據之后直到保存記錄才發現系統出了問題他們該怎么想?刪除重建?假如記錄不可重建是否讓用戶走開?
          * 提出一些檢測和糾正鍵沖突的方法。通常,費點精力也就搞定了,但是從性能上來看這樣做的代價就比較大了。還有,鍵的糾正可能會迫使你突破你的數據和商業/用戶界面層之間的隔離。
          所以還是重提一句老話:你的設計要適應用戶而不是讓用戶來適應你的設計。

          不讓主鍵具有可更新性的原因是在關系模式下,主鍵實現了不同表之間的關聯。比如,Customer 表有一個主鍵 CustomerID,而客戶的定單則存放在另一個表里。Order 表的主鍵可能是 OrderNo 或者 OrderNo、CustomerID 和日期的組合。不管你選擇哪種鍵設置,你都需要在 Order 表中存放 CustomerID 來保證你可以給下定單的用戶找到其定單記錄。
          假如你在 Customer 表里修改了 CustomerID,那么你必須找出 Order 表中的所有相關記錄對其進行修改。否則,有些定單就會不屬于任何客戶——數據庫的完整性就算完蛋了。
          如果索引完整性規則施加到表一級,那么在不編寫大量代碼和附加刪除記錄的情況下幾乎不可能改變某一條記錄的鍵和數據庫內所有關聯的記錄。而這一過程往往錯誤叢生所以應該盡量避免。
          可選鍵(候選鍵)有時可做主鍵
          記住,查詢數據的不是機器而是人。
          假如你有可選鍵,你可能進一步把它用做主鍵。那樣的話,你就擁有了建立強大索引的能力。這樣可以阻止使用數據庫的人不得不連接數據庫從而恰當的過濾數據。在嚴格控制域表的數據庫上,這種負載是比較醒目的。如果可選鍵真正有用,那就是達到了主鍵的水準。
          我的看法是,假如你有可選鍵,比如國家表內的 state_code,你不要在現有不能變動的唯一鍵上創建后續的鍵。你要做的無非是創建毫無價值的數據。如你因為過度使用表的后續鍵[別名]建立這種表的關聯,操作負載真得需要考慮一下了。
          別忘了外鍵
          大多數數據庫索引自動創建的主鍵字段。但別忘了索引外鍵字段,它們在你想查詢主表中的記錄及其關聯記錄時每次都會用到。還有,不要索引 memo/notes 字段而且不要索引大型文本字段(許多字符),這樣做會讓你的索引占據大量的數據庫空間。
          第 4 部分 - 保證數據的完整性
          用約束而非商務規則強制數據完整性
          如果你按照商務規則來處理需求,那么你應當檢查商務層次/用戶界面:如果商務規則以后發生變化,那么只需要進行更新即可。假如需求源于維護數據完整性的需要,那么在數據庫層面上需要施加限制條件。如果你在數據層確實采用了約束,你要保證有辦法把更新不能通過約束檢查的原因采用用戶理解的語言通知用戶界面。除非你的字段命名很冗長,否則字段名本身還不夠。

          只要有可能,請采用數據庫系統實現數據的完整性。這不但包括通過標準化實現的完整性而且還包括數據的功能性。在寫數據的時候還可以增加觸發器來保證數據的正確性。不要依賴于商務層保證數據完整性;它不能保證表之間(外鍵)的完整性所以不能強加于其他完整性規則之上。
          分布式數據系統
          對分布式系統而言,在你決定是否在各個站點復制所有數據還是把數據保存在一個地方之前應該估計一下未來 5 年或者 10 年的數據量。當你把數據傳送到其他站點的時候,最好在數據庫字段中設置一些標記。在目的站點收到你的數據之后更新你的標記。為了進行這種數據傳輸,請寫下你自己的批處理或者調度程序以特定時間間隔運行而不要讓用戶在每天的工作后傳輸數據。本地拷貝你的維護數據,比如計算常數和利息率等,設置版本號保證數據在每個站點都完全一致。
          強制指示完整性(參照完整性?)
          沒有好辦法能在有害數據進入數據庫之后消除它,所以你應該在它進入數據庫之前將其剔除。激活數據庫系統的指示完整性特性。這樣可以保持數據的清潔而能迫使開發人員投入更多的時間處理錯誤條件。
          關系
          如果兩個實體之間存在多對一關系,而且還有可能轉化為多對多關系,那么你最好一開始就設置成多對多關系。從現有的多對一關系轉變為多對多關系比一開始就是多對多關系要難得多。
          采用視圖
          為了在你的數據庫和你的應用程序代碼之間提供另一層抽象,你可以為你的應用程序建立專門的視圖而不必非要應用程序直接訪問數據表。這樣做還等于在處理數據庫變更時給你提供了更多的自由。
          給數據保有和恢復制定計劃
          考慮數據保有策略并包含在設計過程中,預先設計你的數據恢復過程。采用可以發布給用戶/開發人員的數據字典實現方便的數據識別同時保證對數據源文檔化。編寫在線更新來“更新查詢”供以后萬一數據丟失可以重新處理更新。
          用存儲過程讓系統做重活
          解決了許多麻煩來產生一個具有高度完整性的數據庫解決方案之后,我決定封裝一些關聯表的功能組,提供一整套常規的存儲過程來訪問各組以便加快速度和簡化客戶程序代碼的開發。數據庫不只是一個存放數據的地方,它也是簡化編碼之地。
          使用查找
          控制數據完整性的最佳方式就是限制用戶的選擇。只要有可能都應該提供給用戶一個清晰的價值列表供其選擇。這樣將減少鍵入代碼的錯誤和誤解同時提供數據的一致性。某些公共數據特別適合查找:國家代碼、狀態代碼等。
          第 5 部分 - 各種小技巧
          文檔、文檔、文檔
          對所有的快捷方式、命名規范、限制和函數都要編制文檔。

          采用給表、列[字段]、觸發器等加注釋的數據庫工具。是的,這有點費事,但從長遠來看,這樣做對開發、支持和跟蹤修改非常有用。

          取決于你使用的數據庫系統,可能有一些軟件會給你一些供你很快上手的文檔。你可能希望先開始在說,然后獲得越來越多的細節。或者你可能希望周期性的預排,在輸入新數據同時隨著你的進展對每一部分細節化。不管你選擇哪種方式,總要對你的數據庫文檔化,或者在數據庫自身的內部或者單獨建立文檔。這樣,當你過了一年多時間后再回過頭來做第 2 個版本,你犯錯的機會將大大減少。
          使用常用英語(或者其他任何語言)而不要使用編碼
          為什么我們經常采用編碼(比如 9935A 可能是‘青島啤酒’的供應代碼,4XF788-Q 可能是帳目編碼)?理由很多。但是用戶通常都用英語進行思考而不是編碼。工作 5 年的會計或許知道 4XF788-Q 是什么東西,但新來的可就不一定了。在創建下拉菜單、列表、報表時最好按照英語名排序。假如你需要編碼,那你可以在編碼旁附上用戶知道的英語。
          保存常用信息
          讓一個表專門存放一般數據庫信息非常有用。我常在這個表里存放數據庫當前版本、最近檢查/修復(對 FoxPro)、關聯設計文檔的名稱、客戶等信息。這樣可以實現一種簡單機制跟蹤數據庫,當客戶抱怨他們的數據庫沒有達到希望的要求而與你聯系時,這樣做對非客戶機/服務器環境特別有用。
          測試、測試、反復測試
          建立或者修訂數據庫之后,必須用用戶新輸入的數據測試數據字段。最重要的是,讓用戶進行測試并且同用戶一道保證你選擇的數據類型滿足商業要求。測試需要在把新數據庫投入實際服務之前完成。
          檢查設計
          在開發期間檢查數據庫設計的常用技術是通過其所支持的應用程序原型檢查數據庫。換句話說,針對每一種最終表達數據的原型應用,保證你檢查了數據模型并且查看如何取出數據。
          Microsoft Visual FoxPro 設計技巧
          對復雜的 Microsoft Visual FoxPro 數據庫應用程序而言,可以把所有的主表放在一個數據庫容器文件里,然后增加其他數據庫表文件和裝載同原有數據庫有關的特殊文件。根據需要用這些文件連接到主文件中的主表。比如數據輸入、數據索引、統計分析、向管理層或者政府部門提供報表以及各類只讀查詢等。這一措施簡化了用戶和組權限的分配,而且有利于應用程序函數(存儲過程)的分組和劃分,從而在程序必須修改的時候易于管理。

          謹把本文獻給戰斗在狐貍戰線上的同志們!!!


          -- 夜來香
          2002/05/30

          ?

          posted @ 2006-03-23 13:18 點滴鑄就輝煌 閱讀(151) | 評論 (0)編輯 收藏
           
               摘要: 侯捷觀點 Java反射機制   摘要 Reflection 是Java被視為動態(或準動態)語言的一個關鍵性質。這個機制允許程序在運行時透過Reflection APIs取得任何一個已知名稱的class的內部信息,包括其modifiers(諸如public, static 等等)、superclass(例如Object)、實現之interfaces(例如Cloneable),也包...  閱讀全文
          posted @ 2006-03-10 18:34 點滴鑄就輝煌 閱讀(167) | 評論 (0)編輯 收藏
           

          March 5th, 2006

          原文:Thinking in Web 2.0: Sixteen Ways
          作者:Dion Hinchcliffe

          1、在你開始之前,先定一個簡單的目標。無論你是一個Web 2.0應用的創建者還是用戶,請清晰的構思你的目標。就像“我需要保存一個書簽”或者“我準備幫助人們創建可編輯的、共享的頁面”這樣的目標,讓你保持最基礎的需求。很多Web 2.0應用的最初吸引之處就是它的簡單,避免并隱藏了那些多余的復雜性。站在創建者的立場,可以想象Google的幾乎沒有內容的主頁,還有del.icio.us的簡單的線條。從最終用戶的角度來看,與之齊名的就是Diggdot.us所提供的初始化頁面。你能夠并且希望加入更多功能,但是先做好最開始的。在一個時候只做一個特性,完成一個目標。這聽起來很太過于單純化了,但它將使你更加專注,而且你也會明白我的意思。

          2、鏈接是最基礎的思想。這就是我們稱之為Web的一個理由。鏈接是把Web中各種實體連接起來的最基本的元素。你的信息、你的關系、你的導航,甚至是能夠被寫成URL的任何內容。這里有一個鏈接應該遵循的規則(其實你也不必嚴格的遵守):

          1. Web上的任何東西都是可以被URI或者是URL所連接的。
          2. 把所有的鏈接都保存為他的原始出處,這樣可以讓你與任何人、在任何地方、任何時候都能分享它。
          3. 第二條中任何時候的前提是鏈接必須是持久的,它不會在沒有任何緣由的情況下被改變或者是消失。
          4. 鏈接應該是人類可讀的、穩定的、并且能夠自我詮釋的。

          3、數據應該屬于創建它的人。是的,你聽我的。任何用戶創建的、貢獻的或分享的都是他們自己的,除非他們很明顯的放棄這個權力來讓你自由處置。他們貢獻到Web上的任何信息都應該是可編輯的、能被刪除的、并且能夠取消共享,無論在任何時候,只要用戶愿意。這也包含了那些間接的數據,像他們所關心的記錄、日志、瀏覽歷史、網站訪問信息,或者是任何可以被跟蹤的信息。所有的網站必須清晰簡單的陳訴那些信息是用戶創建的,并且提供他們停止創建的方法,甚至是清除的方法。

          4、數據優先,體驗與功能其次。無論它是文本、圖片、音頻還是視頻,Web最終還是把這些解析為數據。換句話說,你無法脫離數據去呈現內容。所有這些數據都通過那些易于發現的URL來定位(參見第2條)。通過另一種形式來看待這些,Web最終是名詞優先,動詞其次,雖然最近正在向動詞偏移。來看看名詞的例子:日歷的條目、家庭照片、股票價格。還有一些動詞的例子:定一個約會、共享一張圖片、買一份股票。

          5、做好積極分享一切的準備。盡可能的分享一切,你所擁有的所有數據,你所提供的所有服務。鼓勵不遵循原有意圖的使用,提倡貢獻,不要那些需要分享的內容堅持設置為私有的。在分享與發現之后,提供易于使用的瀏覽方式是顯而易見的需求。為什么呢:話說回來,你會從別人的共享之中受益匪淺。注意:這里沒有許可讓你去侵犯版權保護的法律,你不能夠去分享你刻錄的DVD或者是擁有商業版權音樂,因為你已經同意不會去分享這些東西。但是你可以發現并分享那些完全開放的媒體內容。一個小小的建議,你可以學習一下Creative Commons license(共創協議).

          6、Web是一個平臺;要讓它成長。當然,我們還有很多其他的平臺(Windows、Linux、Mac),但是那些已經不是重點了。換句話說,Web是無法脫離的平臺,不會中斷的平臺,你可以通過各種方式去擴展的平臺。你在Web上提供的數據與服務將會成為Web一部分,最終你會在Web平臺的某一處扮演你的角色。扮演好你的角色并照顧好后來者。

          7、理解與信奉“階梯性”。現在的Web越來越大,幾乎蔓延到了全世界的所有國家,并且已經擁有了10億用戶。我的觀點是Web的各個組成部分存在著細微的區別和不同,就像不同地方的用戶那樣。例如Web的設計部分:易用性永遠優先于速度、可靠性、重用性與可集成性。你也應該提供同樣的體驗給你的用戶。它已經被一次又一次的被人們在文檔中強調,忠誠的用戶很快會成為專業的用戶,他們期待更快的速度還有更多。退一步支持他們。同樣,也有很多很多的用戶會進入這個階梯的底端,如你所期待的那樣。他們可能不會說你的語言,不熟悉你的文化,甚至不知道是如何到這里的。所以你需要向他們表達清楚。

          8、任何東西都是可編輯的。或者是它應該被編織的更好。要確定的是,只有很少的東西是不能被編輯的,剩下的都可以,這是一個可寫的Web。這并不意味著原始內容的丟失,而通常被理解為用戶能夠很容易的對內容加以評論,或者評注內容是在那里發現的。如果你對此應用的好,他們能夠比你所想象的做的更多(把內容串起來并且給予原始內容來創建自己的,等等)。

          9、Web上的身份是神圣的。不幸的是,這并不意味著你能夠得到更多的隱私(這完全是上個世紀的想法)。但對身份的驗證是必要的,你應該感謝那些只需一個郵件地址就能確定你身份的服務。這意味只要你對你的用戶承諾了,你就必須保證他們的隱私安全。必要的時候,在這個世界的某處你還得為你的用戶挺身而出,向當地的權威挑戰。如果你沒有打算那樣做,你就得把實際情況告訴你的用戶。另一方面,如果身份是必須的,不要試圖偽裝它,不然在某一天我們將會在Web上放棄我們的最后一點點隱私的權利。

          10、了解流行的標準并且使用他們。從一個消費者或者是創作者的立場來看,數據將會以不同的格式與任何一個人交換。同時這樣的數據也會反過來促進標準的完善與采納。這通常意味像RSS、 OPML、XHTML、Simple XML、JSON等簡單標準的流行,而避免SOAP、XSD,還有RDF、ATOM也一樣,使用它們會給我的內心帶來痛苦。請你也為你所鐘愛的標準投上一票來支持它們。

          11、遵循無意使用的規律。如果你把非常有趣的數據和服務用廣泛使用的格式開放和共享出去,你將會得到你所應得的,其他人也將會基于你的那一塊Web平臺來構建。或許還會從別人那里得到更多,所以為這個做一下準備比較好。我已記不清有多少次我看到一個播客(podcasting)服務因為流行過渡而導致服務垮掉,就是因為他們被 Slashdot和del.icio.us給收錄了。這一點要知道:網絡上的大量化意味著如果一個內容非常有趣,即使是一個很小的角落也會得到驚人的訪問量。鼓勵使用這種方式,它還是非常有價值的,前提是你要有所準備。

          12、粒化你的數據與服務。我們應該在很早以前就明白這些,大規模集成的數據僅僅適用于無需管理的下載與批量操作。分解你的數據,讓他們獨立成可描述的URL地址,對你的服務也一樣。反過來說,你不要創建一些巨大的、復雜的、像圣誕樹那樣的數據結構和服務。保持簡單,要非常的簡單。讓這些分離的片斷能夠容易的被重組和發現。

          13、提供用戶能夠單獨受益的數據和服務。漸漸依賴于這種社會化參與是存在風險的,你需要讓你的用戶有一點點動機來貢獻時間、熱情和信息,除非他們能夠直接受益。社會化分享比個體行為的利益大很多,除非你能夠激發用戶的個人動機,否這你將無法享受這份厚禮。

          14、讓用戶組織并過濾信息。不一定是必須的,但卻是非常重要的。讓用戶以他們自己的方式來標注和組織數據,因為你自己是永遠無法及時的處理他們的。用戶會按照他們自己理解的最佳方式來處理并構建。要保證你的Web服務能夠按照用戶所需所想的方式來工作。這也是標簽(tagging)和通俗分類(folksonomies )的方式如此成功的主要因素。

          15、提供豐富的用戶體驗。Web一直都在和本地的應用程序進行著激烈的競爭。為什么?因為本地程序還是感覺上好一些,速度也快一些。但是這不會長久的(確信在5年或者15年后,這種競爭就不存在了)。是的,我在談論Rich Internet Applications, Ajax, 還有那些不可思議的交互應用。他們讓Web成為了一個真正的“無平臺”的平臺,如果你知道我是怎么想的。

          16、信奉并支持快速改進和反饋。這個通常意味著加快步伐,但也意味著使用輕量級的工具、技術和不要做出那些適得其反的痛苦決定(例如使用一個被層層環繞的Ajax框架來代替可以通過混合來實現的,或者用C++來構建所有的東西,其實使用Ruby會更好一些)。這同時也意味著需要一個非常快速的方式來處理錯誤報告,修復Bug,釋放新版本。從一個用戶的角度來看,報告你所發現的任何問題,還有那些你經常抱怨的地方,甚至那些都不是一個Bug。

          當然,Web 2.0是一個極其廣泛和深奧的話題,沒有一個人能夠列舉出它的所有重點和特征。如果你對此充滿了興趣,請花一點時間來補充我沒有提到的地方。我想這就是Web 2.0的參與性吧!

          原作者的這個標題借鑒了Bruce Eckel的兩本暢銷書的名字:《Thinking in C++》和《Thinking in Java》,《C++編程思想》與《Java編程思想》,在此說明一下為什么要這樣翻譯這個題目:)

          indigo 翻譯整理

          posted @ 2006-03-09 12:02 點滴鑄就輝煌 閱讀(165) | 評論 (0)編輯 收藏
          僅列出標題
          共4頁: 上一頁 1 2 3 4 下一頁 
           
          主站蜘蛛池模板: 榆中县| 蕲春县| 津南区| 固始县| 阜阳市| 德昌县| 台东市| 太谷县| 许昌县| 中山市| 双柏县| 边坝县| 德阳市| 青海省| 太白县| 苍梧县| 富阳市| 泰兴市| 清河县| 鄂温| 武城县| 徐闻县| 吉首市| 南昌市| 蒲城县| 民县| 屏东县| 会同县| 南京市| 汉阴县| 甘南县| 子长县| 吉林省| 兖州市| 恭城| 瓦房店市| 松溪县| 柘城县| 普宁市| 兴山县| 政和县|