至于其他的wwWmodi ocrmvCOm也别想了,怎么也不会象modi ocrmv刚开始

&&&本资源为百度网盘资源搜索结果, 为方便用户您可以直接在本站下载文件,下载地址为地址,可高速下载,当然您也可以把文件保存到您的百度网盘中。本站仅提供 Ella(陈嘉桦) - 想念自己(mv)[wwwmodimvcom]rmvb 的搜索结果,文件的安全性和完整性需要您自行判断。非常感谢您对 搜盘盘的支持。搜盘盘-专业提供 百度网盘搜索服务
本站资源来自百度网盘
搜盘盘 (C)
All Rights Reserved
闽ICP备号-1/*** &p&读取Excel表格,拷贝、更新Excel工作薄 &/p&* &p&Description: 可以读取Excel文件的内容,更新Excel工作薄* &/p&* &p&Copyright: Copyright (c) Corparation 2005&/p&* &p&程序开发环境为eclipse&/p&* @author Walker* @version 1.0*/package cn.com.yitong.import java.io.Fimport java.io.FileInputSimport java.io.InputSimport java.util.Vimport cn.com.yitong.ChartIimport cn.com.yitong.VireOimport cn.com.yitong.platform.log.YTLimport jxl.CellTimport jxl.Wimport jxl.format.CellFimport jxl.format.Cimport jxl.format.UnderlineSimport jxl.write.Fimport jxl.write.Limport jxl.write.Nimport jxl.write.WritableCimport jxl.write.WritableCellFimport jxl.write.WritableFimport jxl.write.WritableIimport jxl.write.WritableSimport jxl.write.WritableWimport jxl.write.WriteEimport jxl.write.biff.RowsExceededEpublic class XLSDemo{
private static final int TITLE_LENGTH = 7;
private static final int SHEET_WIDTH = 32;
private static final int SHEET_HEIGHT = 116;
* 创建Excel
private void makeXls()
Workbook workbook =
// 构建Workbook对象, 只读Workbook对象
// 直接从本地文件创建Workbook, 从输入流创建Workbook
InputStream ins = new FileInputStream("D:/Workspace/testproj/source.xls");
workbook = Workbook.getWorkbook(ins);
// 利用已经创建的Excel工作薄创建新的可写入的Excel工作薄
File outFile = new File("D:/Workspace/testproj/test.xls");
WritableWorkbook wwb = Workbook.createWorkbook(outFile, workbook);
// 读取第一张工作表
WritableSheet dataSheet = wwb.getSheet(0);
设置冻结单元格
dataSheet.getSettings().setVerticalFreeze(7);
dataSheet.getSettings().setHorizontalFreeze(2);
// 测试模拟数据
Vector vecData = new Vector();
for(int i = 0; i & 50; i ++)
VireObj obj = new VireObj();
obj.setOrgNo("00" + i + "0");
obj.setOrgName("机构" + (i + 1));
obj.setOpenAcc((int)(100 * Math.random()));
obj.setDestoryAcc((int)(10 * Math.random()));
obj.setTotalAcc((int)(500 * Math.random()));
obj.setMonthInCount((int)(500 * Math.random()));
obj.setMonthInMoney(500 * Math.random());
obj.setMonthOutCount((int)(500 * Math.random()));
obj.setMonthOutMoney(500 * Math.random());
vecData.add(obj);
// 插入数据
insertData(wwb, dataSheet, vecData);
// 插入模拟图像数据
Vector vecImg = new Vector();
for(int i = 0; i & 3; i ++)
ChartImg img = new ChartImg();
img.setImgTitle("图像" + (i + 1));
img.setImgName("D:/Workspace/testproj/images/barchart.png");
vecImg.add(img);
// 插入图表
insertImgsheet(wwb, vecImg);
//写入Excel对象
wwb.write();
wwb.close();
} catch (Exception e)
YTLogger.logDebug(e);
// 操作完成时,关闭对象,释放占用的内存空间
workbook.close();
* 插入数据
* @param wwb WritableWorkbook : 工作簿
* @param dataSheet WritableSheet : 工作表
* @throws RowsExceededException
* @throws WriteException
private void insertData(WritableWorkbook wwb, WritableSheet dataSheet, Vector vecData) throws RowsExceededException, WriteException
// 获得标题单元格对象
modiStrCell(dataSheet, 2, 0, "工商银行江苏省分行 个人网上银行业务种类/开销户明细报表(2005-12)", null);
// 修改数据单元格数据
for(int i = 0; i & vecData.size(); i ++)
VireObj obj = (VireObj)vecData.get(i);
modiStrCell(dataSheet, 0, TITLE_LENGTH + i, obj.getOrgNo(), null);
modiStrCell(dataSheet, 1, TITLE_LENGTH + i, obj.getOrgName(), null);
modiNumCell(dataSheet, 2, TITLE_LENGTH + i, obj.getOpenAcc(), null);
modiNumCell(dataSheet, 3, TITLE_LENGTH + i, obj.getDestoryAcc(), null);
modiNumCell(dataSheet, 4, TITLE_LENGTH + i, obj.getTotalAcc(), null);
modiNumCell(dataSheet, 5, TITLE_LENGTH + i, obj.getMonthInCount(), null);
modiNumCell(dataSheet, 6, TITLE_LENGTH + i, obj.getTotalInMoney(), null);
modiNumCell(dataSheet, 7, TITLE_LENGTH + i, obj.getMonthOutCount(), null);
modiNumCell(dataSheet, 8, TITLE_LENGTH + i, obj.getMonthOutMoney(), null);
// 删除空行
for (int j = vecData.size() + TITLE_LENGTH; j & SHEET_HEIGHT; j++)
dataSheet.removeRow(vecData.size() + TITLE_LENGTH);
// 插入公式
for(int i = 2; i & SHEET_WIDTH; i ++)
modiFormulaCell(dataSheet, i, vecData.size() + TITLE_LENGTH, 8, vecData.size() + TITLE_LENGTH, null);
* 修改字符单元格的值
* @param dataSheet WritableSheet : 工作表
* @param col int : 列
* @param row int : 行
* @param str String : 字符
* @param format CellFormat : 单元格的样式
* @throws RowsExceededException
* @throws WriteException
private void modiStrCell(WritableSheet dataSheet, int col, int row, String str, CellFormat format) throws RowsExceededException, WriteException
// 获得单元格对象
WritableCell cell = dataSheet.getWritableCell(col, row);
// 判断单元格的类型, 做出相应的转化
if (cell.getType() == CellType.EMPTY)
Label lbl = new Label(col, row, str);
if(null != format)
lbl.setCellFormat(format);
lbl.setCellFormat(cell.getCellFormat());
dataSheet.addCell(lbl);
} else if (cell.getType() == CellType.LABEL)
Label lbl = (Label)
lbl.setString(str);
} else if (cell.getType() == CellType.NUMBER)
// 数字单元格修改
Number n1 = (Number)
n1.setValue(42.05);
* 修改数字单元格的值
* @param dataSheet WritableSheet : 工作表
* @param col int : 列
* @param row int : 行
* @param num double : 数值
* @param format CellFormat : 单元格的样式
* @throws RowsExceededException
* @throws WriteException
private void modiNumCell(WritableSheet dataSheet, int col, int row, double num, CellFormat format) throws RowsExceededException, WriteException
// 获得单元格对象
WritableCell cell = dataSheet.getWritableCell(col, row);
// 判断单元格的类型, 做出相应的转化
if (cell.getType() == CellType.EMPTY)
Number lbl = new Number(col, row, num);
if(null != format)
lbl.setCellFormat(format);
lbl.setCellFormat(cell.getCellFormat());
dataSheet.addCell(lbl);
} else if (cell.getType() == CellType.NUMBER)
// 数字单元格修改
Number lbl = (Number)
lbl.setValue(num);
} else if (cell.getType() == CellType.LABEL)
Label lbl = (Label)
lbl.setString(String.valueOf(num));
* 修改公式单元格的值
* @param dataSheet WritableSheet : 工作表
* @param col int : 列
* @param row int : 行
* @param startPos int : 开始位置
* @param endPos int : 结束位置
* @param format
* @throws RowsExceededException
* @throws WriteException
private void modiFormulaCell(WritableSheet dataSheet, int col, int row, int startPos, int endPos, CellFormat format) throws RowsExceededException, WriteException
String f = getFormula(col, row, startPos, endPos);
// 插入公式(只支持插入,不支持修改)
WritableCell cell = dataSheet.getWritableCell(col, row);
if (cell.getType() == CellType.EMPTY)
// 公式单元格
Formula lbl = new Formula(col, row, f);
if(null != format)
lbl.setCellFormat(format);
lbl.setCellFormat(cell.getCellFormat());
dataSheet.addCell(lbl);
} else if (cell.getType() == CellType.STRING_FORMULA)
YTLogger.logWarn("Formula modify not supported!");
* 得到公式
* @param col int : 列
* @param row int : 行
* @param startPos int : 开始位置
* @param endPos int : 结束位置
* @return String
* @throws RowsExceededException
* @throws WriteException
private String getFormula(int col, int row, int startPos, int endPos)
throws RowsExceededException, WriteException
char base = 'A';
StringBuffer formula = new StringBuffer(128);
// 组装公式
formula.append("SUM(");
if (col &= 25)
c1 = (char) (col % 26 + base);
formula.append(c1).append(startPos).append(":")
.append(c1).append(endPos).append(")");
} else if (col & 25)
char c2 = (char) ((col - 26) / 26 + base);
c1 = (char) ((col - 26) % 26 + base);
formula.append(c2).append(c1).append(startPos).append(":")
.append(c2).append(c1).append(endPos).append(")");
return formula.toString();
* 插入图表工作表
* @param wwb WritableWorkbook : 工作簿
* @param vecImg Vector : 图像链表
* @throws RowsExceededException
* @throws WriteException
private void insertImgsheet(WritableWorkbook wwb, Vector vecImg)
throws RowsExceededException, WriteException
// 插入图像
WritableSheet imgS
if((wwb.getSheets()).length & 2)
imgSheet = wwb.createSheet("图表", 1);
imgSheet = wwb.getSheet(1);
for (int i = 0; i & vecImg.size(); i++)
ChartImg chart = (ChartImg) vecImg.get(i);
// 插入图像标题
Label lbl = new Label(0, 2 + 20 * i, chart.getImgTitle());
WritableFont font = new WritableFont(WritableFont.ARIAL,
WritableFont.DEFAULT_POINT_SIZE, WritableFont.NO_BOLD, false,
UnderlineStyle.NO_UNDERLINE, Colour.DARK_BLUE2);
WritableCellFormat background = new WritableCellFormat(font);
background.setWrap(true);
background.setBackground(Colour.GRAY_25);
imgSheet.mergeCells(0, 2 + 20 * i, 9, 2 + 20 * i);
lbl.setCellFormat(background);
imgSheet.addCell(lbl);
// 插入图像单元格
insertImgCell(imgSheet, 2, 4 + 20 * i, 8, 15, chart.getImgName());
* 插入图像到单元格(图像格式只支持png)
* @param dataSheet WritableSheet : 工作表
* @param col int : 列
* @param row int : 行
* @param width int : 宽
* @param height int : 高
* @param imgName String : 图像的全路径
* @throws RowsExceededException
* @throws WriteException
private void insertImgCell(WritableSheet dataSheet, int col, int row, int width,
int height, String imgName) throws RowsExceededException, WriteException
File imgFile = new File(imgName);
WritableImage img = new WritableImage(col, row, width, height, imgFile);
dataSheet.addImage(img);
* @param args
public static void main(String[] args)
XLSDemo demo = new XLSDemo();
demo.makeXls();
jxl不错,简单易用import jxl.*;import jxl.write.*;import java.io.*;import java.io.File.*;import java.util.*;public class excel{public static void main(String[] args) {String targetfile = "c:/out.xls";//输出的excel文件名String worksheet = "List";//输出的excel文件工作表名String[] title = {"ID","NAME","DESCRIB"};//excel工作表的标题WritableWtry{//创建可写入的Excel工作薄,运行生成的文件在tomcat/bin下//workbook = Workbook.createWorkbook(new File("output.xls")); System.out.println("begin");OutputStream os=new FileOutputStream(targetfile); workbook=Workbook.createWorkbook(os); WritableSheet sheet = workbook.createSheet(worksheet, 0); //添加第一个工作表//WritableSheet sheet1 = workbook.createSheet("MySheet1", 1); //可添加第二个工作/*jxl.write.Label label = new jxl.write.Label(0, 2, "A label record"); //put a label in cell A3, Label(column,row)sheet.addCell(label); */jxl.write.Lfor (int i=0; i&title. i++){//Label(列号,行号 ,内容 )label = new jxl.write.Label(i, 0, title[i]); //put the title in row1 sheet.addCell(label); }//下列添加的对字体等的设置均调试通过,可作参考用//添加数字jxl.write.Number number = new jxl.write.Number(3, 4, 3.14159); //put the number 3.14159 in cell D5sheet.addCell(number);//添加带有字型Formatting的对象 jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES,10,WritableFont.BOLD,true); jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf); jxl.write.Label labelCF = new jxl.write.Label(4,4,"文本",wcfF); sheet.addCell(labelCF); //添加带有字体颜色,带背景颜色 Formatting的对象 jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL,10,WritableFont.BOLD,false,jxl.format.UnderlineStyle.NO_UNDERLINE,jxl.format.Colour.RED); jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc); wcfFC.setBackground(jxl.format.Colour.BLUE);jxl.write.Label labelCFC = new jxl.write.Label(1,5,"带颜色",wcfFC); sheet.addCell(labelCFC); //添加带有formatting的Number对象 jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##"); jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf); jxl.write.Number labelNF = new jxl.write.Number(1,1,3.1415926,wcfN); sheet.addCell(labelNF); //3.添加Boolean对象 jxl.write.Boolean labelB = new jxl.write.Boolean(0,2,false); sheet.addCell(labelB); //4.添加DateTime对象 jxl.write.DateTime labelDT = new jxl.write.DateTime(0,3,new java.util.Date()); sheet.addCell(labelDT); //添加带有formatting的DateFormat对象 jxl.write.DateFormat df = new jxl.write.DateFormat("ddMMyyyyhh:mm:ss"); jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df); jxl.write.DateTime labelDTF = new jxl.write.DateTime(1,3,new java.util.Date(),wcfDF); sheet.addCell(labelDTF); //和宾单元格//sheet.mergeCells(int col1,int row1,int col2,int row2);//左上角到右下角sheet.mergeCells(4,5,8,10);//左上角到右下角wfc = new jxl.write.WritableFont(WritableFont.ARIAL,40,WritableFont.BOLD,false,jxl.format.UnderlineStyle.NO_UNDERLINE,jxl.format.Colour.GREEN); jxl.write.WritableCellFormat wchB = new jxl.write.WritableCellFormat(wfc); wchB.setAlignment(jxl.format.Alignment.CENTRE);labelCFC = new jxl.write.Label(4,5,"单元合并",wchB); sheet.addCell(labelCFC); ////设置边框jxl.write.WritableCellFormat wcsB = new jxl.write.WritableCellFormat(); wcsB.setBorder(jxl.format.Border.ALL,jxl.format.BorderLineStyle.THICK);labelCFC = new jxl.write.Label(0,6,"边框设置",wcsB); sheet.addCell(labelCFC); workbook.write(); workbook.close();}catch(Exception e) { e.printStackTrace(); } System.out.println("end");Runtime r=Runtime.getRuntime(); Process p= //String cmd[]={"notepad","exec.java"}; String cmd[]={"C:\\Program Files\\Microsoft Office\\Office\\EXCEL.EXE","out.xls"}; try{ p=r.exec(cmd); } catch(Exception e){ System.out.println("error executing: "+cmd[0]); }}}
Java中合并XML文档的设计与实现
作者: 凌宗虎 李先国
出处: 计算机与信息技术
责任编辑: 方舟
  摘 要:介绍了XML应用中合并XML的方法与应用,在基于XML的应用中,有着广泛的应用前景。  关键词:XML文档 解析器 元素  在XML应用中,最常用也最实用的莫过于的读写。由于XML语义比较严格,起始标记必须配对,所以合并XML文档并不像合并普通文件那样简单。在JAVA中,如何合并XML文档,下面介绍一种方法。  设计思想  应用.xml.parsers包中的解析器解析得到两个XML文件的根元素,再采用的方式逐一复制被合并文件的元素。   实现过程  为了读写XML文件,需要如下JAVA包,"//"后为注释说明,笔者的环境是 1.3.1,在JDK 1.4.0中测试也通过。 Import java.io. *; //Java基础包,包含各种IO操作 Import java.util. *; //Java基础包,包含各种标准数据结构操作 Import javax.xml.parsers. *; //XML解析器接口 Import org.w3c.. *; //XML的DOM实现 import org.apache.crimson.tree.XmlD//写XML文件要用到Import javax.xml.transform. *;Import javax.xml.transform.dom. *;Import javax.xml.transform.stream. *;  下面介绍合并XML文档的过程。先说明一下各个方法的作用。方法 Merging()有两个(分别是目标XML和被合并的XML文件名),调用JAVA的解析器,获得两个要合并的XML文档的Document结构和根元素,并调用方法duplicate()和方法write To()。当然,在XML文档的合并过程中,可以加入另外的一些判断条件,比如,当被合并XML文档不存在时,将如何处理,等等。Private Boolean is Merging (String mainFileName, String
Filename) throws Exception { Boolean isOver = DocumentBuilderFactory
= DocumentBuilderFactory.newInstance(); Document Builder db = Try {  Db = dbf.newDocumentBuilder (); } Catch (ParserConfigurationException pce) {  System.err.println(pce); //出现异常时,输出异常信息 } Document doc_main = null,doc_ = //获取两个XML文件的Document。 Try {  Doc_main = db.parse (mainFileName);  Doc_vice = db.parse (sub Filename); } Catch (DOM Exception dom) {  System.err.println (dom.getMessage ()); } Catch (Exception ioe) {  System.err.println (ioe); } //获取两个文件的根元素。 Element root_main = doc_main.getDocumentElement (); Element root_vice = doc_vice.getDocumentElement (); //下面被合并文件根节点下的每个元素 Novelist message Items = root_vice.getChildNodes (); Int item_number = messageItems.getLength (); //如果根节点下的第一个元素,比如&所属管理系统& ,那么i从3开始。否则i从1开始。 For (int i=1; i & item_ i=i+2 ) {  //调用dupliate(),依次复制被合并XML文档中根节点下的元素。   Element messageItem = (Element) messageItems.item (i);  IsOver = dupliate (doc_main, root_main, messageItem); } //调用 write To(),将合并得到的Document写入目标XML文档。 Boolean isWritten = write To (doc_main, mainFileName); Return isOver && isW }  方法dupliate ()有三个参数(分别是目标XML文档的Document,目标XML文档中要添加节点的父节点和被合并XML文档的复制节点),采用递归的形式,将一个XML文档中的元素复制到另一个XML文档中。Private Boolean dupliate (Document doc_dup, Element father, Element son) throws Exception { Boolean is done = String son_name = son.getNodeName (); Element sub ITEM = doc_dup.createElement (son_name); //复制节点的属性 If (son.hasAttributes ()){  NamedNodeMap attributes = son.getAttributes ();  For (int i=0; i & attributes.getLength () ; i ++){   String attribute_name = attributes. Item (i). GetNodeName ();   String attribute_ = attributes. Item (i). GetNodeValue ();   SubITEM.setAttribute (attribute_name, attribute_value);  } } Father.appendChild (sub ITEM); //复制节点的值  value son = (Text) son.getFirstChild (); String nodevalue_root = ""; If (value_son! = null && value_son.getLength () & 0) nodevalue_root = (String) value_son.getNodeValue (); Text valuenode_root = If ((nodevalue_root! = null)&&(nodevalue_root.length () & 0)) valuenode_root = doc_dup.createTextNode (nodevalue_root); If (valuenode_root! = null && valuenode_root.getLength () & 0) subITEM.appendChild (valuenode_root); //复制子结点 Novelist sub_messageItems = son.getChildNodes (); int sub_item_number = sub_messageItems.getLength(); if (sub_item_number & 2){  //如果没有子节点,则返回  Is done = } Else {  For (int j = 1; j & sub_item_ j=j+2) {   //如果有子节点,则递归调用本方法    Element sub_messageItem = (Element) sub_messageItems.item (j);   Is done = dupliate (doc_dup, subITEM, sub_messageItem);  } } R}  方法writeTo()有两个参数(分别是目标XML文档的Document和文件名),将所得目标XML文档写入文件。Private Boolean write To (Document doc, String fileName) throws Exception { Boolean isOver = DOM Source doms = new DOM Source (doc); File f = new File (fileName); Stream Result sr = new Stream Result (f); Try {  Transformer Factory tf=TransformerFactory.newInstance ();  Transformer t=tf.newTransformer ();  Properties properties = t.getOutputProperties ();  Properties.setProperty (OutputKeys.ENCODING,"GB2312");  T.setOutputProperties (properties);  T.transform (doms, sr);  IsOver = } Catch (TransformerConfigurationException tce) {  Tce.printStackTrace (); } Catch (Transformer Exception te) {  Te.printStackTrace (); } Return isO}  最后使用测试函数进行测试。对于两个已经存在的XML文件(比如,存在文件D:/a.xml和D:/b.xml,要将b.xml合并到a.xml中),可以测试如下: static
main (String [] args) throws Exception { Boolean is done = is Merging ("D:/a.xml","D:/b.xml"); If (is Done) System.out.println ("XML files have been merged."); Else System.out.println ("XML files have
been merged.");}  总结  本文介绍了如何利用JAVA中的XML解析器,合并两个XML文档。当然,在合并的过程中,还可以加入其他的约束条件,比如要求过滤掉特定的元素等。另外,复制元素的插入位置也可以加以限制。
在应用中加入全文检索功能
——基于Java的全文索引引擎Lucene简介
作者: 车东 Email: chedongATbigfoot.com/chedongATchedong.com
写于:2002/08 最后更新:
02/22/:55 ()
版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
关键词:Lucene java full-text search engine Chinese word segment
内容摘要:
Lucene是一个基于Java的全文索引工具包。
Lucene不是一个完整的全文索引应用,而是是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。
Lucene的作者:Lucene的贡献者是一位资深全文索引/检索专家,曾经是V-Twin搜索引擎(Apple的Copland操作系统的成就之一)的主要开发者,后在Excite担任高级系统架构设计师,目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。
Lucene的发展历程:早先发布在作者自己的,后来发布在,2001年年底成为APACHE基金会jakarta的一个子项目:
已经有很多Java项目都使用了Lucene作为其后台的全文索引引擎,比较著名的有:
:WEB论坛系统;
:邮件列表HTML归档/浏览/查询系统,本文的主要参考文档“”作者就是EyeBrows系统的主要开发者之一,而EyeBrows已经成为目前APACHE项目的主要邮件列表归档系统。
:基于XML的web发布框架,全文检索部分使用了Lucene
:基于Java的开放开发平台,帮助部分的全文索引使用了Lucene
对于中文用户来说,最关心的问题是其是否支持中文的全文检索。但通过后面对于Lucene的结构的介绍,你会了解到由于Lucene良好架构设计,对中文的支持只需对其语言词法分析接口进行扩展就能实现对中文检索的支持。
Lucene的API接口设计的比较通用,输入输出结构都很像数据库的表==&记录==&字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统。
比较一下Lucene和数据库:
索引数据源:doc(field1,field2...) doc(field1,field2...)
_____________
| Lucene Index|
--------------
/ searcher \ 结果输出:Hits(doc(field1,field2) doc(field1...))
索引数据源:record(field1,field2...) record(field1..)
SQL: insert/
_____________
-------------
/ SQL: select \结果输出:results(record(field1,field2..) record(field1...))
Document:一个需要进行索引的“单元”一个Document由多个字段组成
Record:记录,包含多个字段
Field:字段
Field:字段
Hits:查询结果集,由匹配的Document组成
RecordSet:查询结果集,由多个Record组成
全文检索 ≠ like "%keyword%"
通常比较厚的书籍后面常常附关键词索引表(比如:北京:12, 34页,上海:3,77页……),它能够帮助读者比较快地找到相关内容的页码。而数据库索引能够大大提高查询的速度原理也是一样,想像一下通过书后面的索引查找的速度要比一页一页地翻内容高多少倍……而索引之所以效率高,另外一个原因是它是排好序的。对于检索系统来说核心是一个排序问题。
由于数据库索引不是为全文索引设计的,因此,使用like "%keyword%"时,数据库索引是不起作用的,在使用like查询时,搜索过程又变成类似于一页页翻书的遍历过程了,所以对于含有模糊查询的数据库服务来说,LIKE对性能的危害是极大的。如果是需要对多个关键词进行模糊匹配:like"%keyword1%" and like "%keyword2%" ...其效率也就可想而知了。
所以建立一个高效检索系统的关键是建立一个类似于科技索引一样的反向索引机制,将数据源(比如多篇文章)排序顺序存储的同时,有另外一个排好序的关键词列表,用于存储关键词==&文章映射关系,利用这样的映射关系索引:[关键词==&出现关键词的文章编号,出现次数(甚至包括位置:起始偏移量,结束偏移量),出现频率],检索过程就是把模糊查询变成多个可以利用索引的精确查询的逻辑组合的过程。从而大大提高了多关键词查询的效率,所以,全文检索问题归结到最后是一个排序问题。
由此可以看出模糊查询相对数据库的精确查询是一个非常不确定的问题,这也是大部分数据库对全文检索支持有限的原因。Lucene最核心的特征是通过特殊的索引结构实现了传统数据库不擅长的全文索引机制,并提供了扩展接口,以方便针对不同应用的定制。
可以通过一下表格对比一下数据库的模糊查询:
Lucene全文索引引擎
将数据源中的数据都通过全文索引一一建立反向索引
对于LIKE查询来说,数据传统的索引是根本用不上的。数据需要逐个便利记录进行GREP式的模糊匹配,比有索引的搜索速度要有多个数量级的下降。
通过词元(term)进行匹配,通过语言分析接口的实现,可以实现对中文等非英语的支持。
使用:like "%net%" 会把netherlands也匹配出来,多个关键词的模糊匹配:使用like "%com%net%":就不能匹配词序颠倒的xxx.net..xxx.com
有匹配度算法,将匹配程度(相似度)比较高的结果排在前面。
没有匹配程度的控制:比如有记录中net出现5词和出现1次的,结果是一样的。
通过特别的算法,将最匹配度最高的头100条结果输出,结果集是缓冲式的小批量读取的。
返回所有的结果集,在匹配条目非常多的时候(比如上万条)需要大量的内存存放这些临时结果集。
通过不同的语言分析接口实现,可以方便的定制出符合应用需要的索引规则(包括对中文的支持)
没有接口或接口复杂,无法定制
高负载的模糊查询应用,需要负责的模糊查询的规则,索引的资料量比较大
使用率低,模糊匹配规则简单或者需要模糊查询的资料量少
全文检索和数据库应用最大的不同在于:让
头100条结果满足98%以上用户的需求
Lucene的创新之处:
大部分的搜索(数据库)引擎都是用B树结构来维护索引,索引的更新会导致大量的IO操作,Lucene在实现中,对此稍微有所改进:不是维护一个索引文件,而是在扩展索引的时候不断创建新的索引文件,然后定期的把这些新的小索引文件合并到原先的大索引中(针对不同的更新策略,批次的大小可以调整),这样在不影响检索的效率的前提下,提高了索引的效率。
Lucene和其他一些全文检索系统/应用的比较:
其他开源全文检索系统
增量索引和批量索引
可以进行增量的索引(Append),可以对于大量数据进行批量索引,并且接口设计用于优化批量索引和小批量的增量索引。
很多系统只支持批量的索引,有时数据源有一点增加也需要重建索引。
Lucene没有定义具体的数据源,而是一个文档的结构,因此可以非常灵活的适应各种应用(只要前端有合适的转换器把数据源转换成相应结构),
很多系统只针对网页,缺乏其他格式文档的灵活性。
索引内容抓取
Lucene的文档是由多个字段组成的,甚至可以控制那些字段需要进行索引,那些字段不需要索引,近一步索引的字段也分为需要分词和不需要分词的类型:
需要进行分词的索引,比如:标题,文章内容字段
不需要进行分词的索引,比如:作者/日期字段
缺乏通用性,往往将文档整个索引了
通过语言分析器的不同扩展实现:可以过滤掉不需要的词:an the of 等,西文语法分析:将jumps jumped jumper都归结成jump进行索引/检索非英文支持:对亚洲语言,阿拉伯语言的索引支持
缺乏通用接口实现
通过查询分析接口的实现,可以定制自己的查询语法规则:比如: 多个关键词之间的 + - and or关系等
能够支持多用户的使用
对于中文来说,全文索引首先还要解决一个语言分析的问题,对于英文来说,语句中单词之间是天然通过空格分开的,但亚洲语言的中日韩文语句中的字是一个字挨一个,所有,首先要把语句中按“词”进行索引的话,这个词如何切分出来就是一个很大的问题。
首先,肯定不能用单个字符作(si-gram)为索引单元,否则查“上海”时,不能让含有“海上”也匹配。
但一句话:“北京天安门”,计算机如何按照中文的语言习惯进行切分呢?“北京 天安门” 还是“北 京 天安门”?让计算机能够按照语言习惯进行切分,往往需要机器有一个比较丰富的词库才能够比较准确的识别出语句中的单词。
另外一个解决的办法是采用自动切分算法:将单词按照2元语法(bigram)方式切分出来,比如:"北京天安门" ==& "北京 京天 天安 安门"。
这样,在查询的时候,无论是查询"北京" 还是查询"天安门",将查询词组按同样的规则进行切分:"北京","天安安门",多个关键词之间按与"and"的关系组合,同样能够正确地映射到相应的索引中。这种方式对于其他亚洲语言:韩文,日文都是通用的。
基于自动切分的最大优点是没有词表维护成本,实现简单,缺点是索引效率低,但对于中小型应用来说,基于2元语法的切分还是够用的。基于2元切分后的索引一般大小和源文件差不多,而对于英文,索引文件一般只有原文件的30%-40%不同,
实现非常简单
增加了查询分析的复杂程度,
适于实现比较复杂的查询语法规则
索引冗余大,索引几乎和原文一样大
索引效率高,为原文大小的30%左右
无词表维护成本
词表维护成本非常高:中日韩等语言需要分别维护。还需要包括词频统计等内容
嵌入式系统:运行环境资源有限分布式系统:无词表同步问题多语言环境:无词表维护成本
对查询和存储效率要求高的专业搜索引擎
目前比较大的搜索引擎的语言分析算法一般是基于以上2个机制的结合。关于中文的语言分析算法,大家可以在Google查关键词"wordsegment search"能找到更多相关的资料。
注意:Lucene中的一些比较复杂的词法分析是用JavaCC生成的(JavaCC:JavaCompilerCompiler,纯Java的词法分析生成器),所以如果从源代码编译或需要修改其中的QueryParser、定制自己的词法分析器,还需要从下载javacc。
lucene的组成结构:对于外部应用来说索引模块(index)和检索模块(search)是主要的外部应用入口
org.apache.Lucene.search/
org.apache.Lucene.index/
org.apache.Lucene.analysis/
语言分析器
org.apache.Lucene.queryParser/
查询分析器
org.apache.Lucene.document/
org.apache.Lucene.store/
底层IO/存储结构
org.apache.Lucene.util/
一些公用的数据结构
简单的例子演示一下Lucene的使用方法:索引过程:从命令行读取文件名(多个),将文件分路径(path字段)和内容(body字段)2个字段进行存储,并对内容进行全文索引:索引的单位是Document对象,每个Document对象包含多个字段Field对象,针对不同的字段属性和数据输出的需求,对字段还可以选择不同的索引/存储字段规则,列表如下:
方法切词索引存储用途Field.Text(String name, String value)YesYesYes切分词索引并存储,比如:标题,内容字段Field.Text(String name, Reader value)YesYesNo切分词索引不存储,比如:META信息,不用于返回显示,但需要进行检索内容Field.Keyword(String name, String value)NoYesYes不切分索引并存储,比如:日期字段Field.UnIndexed(String name, String value)NoNoYes不索引,只存储,比如:文件路径Field.UnStored(String name, String value)YesYesNo只全文索引,不存储public class IndexFiles {
//使用方法:: IndexFiles [索引输出目录] [索引的文件列表] ...
public static void main(String[] args) throws Exception {
String indexPath = args[0];
//用指定的语言分析器构造一个新的写索引器(第3个参数表示是否为追加索引)
writer = new IndexWriter(indexPath, new SimpleAnalyzer(), false);
for (int i=1; i&args. i++) {
System.out.println("Indexing file " + args[i]);
InputStream is = new FileInputStream(args[i]);
//构造包含2个字段Field的Document对象
//一个是路径path字段,不索引,只存储
//一个是内容body字段,进行全文索引,并存储
Document doc = new Document();
doc.add(Field.UnIndexed("path", args[i]));
doc.add(Field.Text("body", (Reader) new InputStreamReader(is)));
//将文档写入索引
writer.addDocument(doc);
is.close();
//关闭写索引器
writer.close();
}} 索引过程中可以看到:语言分析器提供了抽象的接口,因此语言分析(Analyser)是可以定制的,虽然lucene缺省提供了2个比较通用的分析器SimpleAnalyser和StandardAnalyser,这2个分析器缺省都不支持中文,所以要加入对中文语言的切分规则,需要修改这2个分析器。
Lucene并没有规定数据源的格式,而只提供了一个通用的结构(Document对象)来接受索引的输入,因此输入的数据源可以是:数据库,WORD文档,PDF文档,HTML文档……只要能够设计相应的解析转换器将数据源构造成成Docuement对象即可进行索引。
对于大批量的数据索引,还可以通过调整IndexerWrite的文件合并频率属性(mergeFactor)来提高批量索引的效率。 检索过程和结果显示:搜索结果返回的是Hits对象,可以通过它再访问Document==&Field中的内容。假设根据body字段进行全文检索,可以将查询结果的path字段和相应查询的匹配度(score)打印出来,public class Search {
public static void main(String[] args) throws Exception {
String indexPath = args[0], queryString = args[1];
//指向索引目录的搜索器
Searcher searcher = new IndexSearcher(indexPath);
//查询解析器:使用和索引同样的语言分析器
Query query = QueryParser.parse(queryString, "body",
new SimpleAnalyzer());
//搜索结果使用Hits存储
Hits hits = searcher.search(query);
//通过hits可以访问到相应字段的数据和查询的匹配度
for (int i=0; i&hits.length(); i++) {
System.out.println(hits.doc(i).get("path") + "; Score: " +
hits.score(i));
}}在整个检索过程中,语言分析器,查询分析器,甚至搜索器(Searcher)都是提供了抽象的接口,可以根据需要进行定制。
简化的查询分析器个人感觉lucene成为JAKARTA项目后,画在了太多的时间用于调试日趋复杂QueryParser,而其中大部分是大多数用户并不很熟悉的,目前LUCENE支持的语法:Query ::= ( Clause )*Clause ::= ["+", "-"] [&TERM& ":"] ( &TERM& | "(" Query ")")中间的逻辑包括:and or + - &&||等符号,而且还有"短语查询"和针对西文的前缀/模糊查询等,个人感觉对于一般应用来说,这些功能有一些华而不实,其实能够实现目前类似于Google的查询语句分析功能其实对于大多数用户来说已经够了。所以,Lucene早期版本的QueryParser仍是比较好的选择。添加修改删除指定记录(Document)Lucene提供了索引的扩展机制,因此索引的动态扩展应该是没有问题的,而指定记录的修改也似乎只能通过记录的删除,然后重新加入实现。如何删除指定的记录呢?删除的方法也很简单,只是需要在索引时根据数据源中的记录ID专门另建索引,然后利用IndexReader.delete(Termterm)方法通过这个记录ID删除相应的Document。根据某个字段值的排序功能lucene缺省是按照自己的相关度算法(score)进行结果排序的,但能够根据其他字段进行结果排序是一个在LUCENE的开发邮件列表中经常提到的问题,很多原先基于数据库应用都需要除了基于匹配度(score)以外的排序功能。而从全文检索的原理我们可以了解到,任何不基于索引的搜索过程效率都会导致效率非常的低,如果基于其他字段的排序需要在搜索过程中访问存储字段,速度回大大降低,因此非常是不可取的。但这里也有一个折中的解决方法:在搜索过程中能够影响排序结果的只有索引中已经存储的docID和score这2个参数,所以,基于score以外的排序,其实可以通过将数据源预先排好序,然后根据docID进行排序来实现。这样就避免了在LUCENE搜索结果外对结果再次进行排序和在搜索过程中访问不在索引中的某个字段值。这里需要修改的是IndexSearcher中的HitCollector过程:... scorer.score(new HitCollector() { private float minScore = 0.0f; public final void collect(int doc, float score) {
if (score & 0.0f &&
// ignore zeroed buckets
(bits==null || bits.get(doc))) {
// skip docs not in bits
totalHits[0]++;
if (score &= minScore) {
/* 原先:Lucene将docID和相应的匹配度score例入结果命中列表中:
* hq.put(new ScoreDoc(doc, score));
// update hit queue
* 如果用doc 或 1/doc 代替 score,就实现了根据docID顺排或逆排
* 假设数据源索引时已经按照某个字段排好了序,而结果根据docID排序也就实现了
* 针对某个字段的排序,甚至可以实现更复杂的score和docID的拟合。
hq.put(new ScoreDoc(doc, (float) 1/doc ));
if (hq.size() & nDocs) {
// if hit queue overfull
// remove lowest in hit queue
minScore = ((ScoreDoc)hq.top()). // reset minScore
}, reader.maxDoc());更通用的输入输出接口虽然lucene没有定义一个确定的输入文档格式,但越来越多的人想到使用一个标准的中间格式作为Lucene的数据导入接口,然后其他数据,比如PDF只需要通过解析器转换成标准的中间格式就可以进行数据索引了。这个中间格式主要以XML为主,类似实现已经不下4,5个:数据源: WORD
XML中间格式
Lucene INDEX目前还没有针对MSWord文档的解析器,因为Word文档和基于ASCII的RTF文档不同,需要使用COM对象机制解析。这个是我在Google上查的相关资料:另外一个办法就是把Word文档转换成text:索引过程优化索引一般分2种情况,一种是小批量的索引扩展,一种是大批量的索引重建。在索引过程中,并不是每次新的DOC加入进去索引都重新进行一次索引文件的写入操作(文件I/O是一件非常消耗资源的事情)。Lucene先在内存中进行索引操作,并根据一定的批量进行文件的写入。这个批次的间隔越大,文件的写入次数越少,但占用内存会很多。反之占用内存少,但文件IO操作频繁,索引速度会很慢。在IndexWriter中有一个MERGE_FACTOR参数可以帮助你在构造索引器后根据应用环境的情况充分利用内存减少文件的操作。根据我的使用经验:缺省Indexer是每20条记录索引后写入一次,每将MERGE_FACTOR增加50倍,索引速度可以提高1倍左右。搜索过程优化lucene支持内存索引:这样的搜索比基于文件的I/O有数量级的速度提升。而尽可能减少IndexSearcher的创建和对搜索结果的前台的缓存也是必要的。Lucene面向全文检索的优化在于首次索引检索后,并不把所有的记录(Document)具体内容读取出来,而起只将所有结果中匹配度最高的头100条结果(TopDocs)的ID放到结果集缓存中并返回,这里可以比较一下数据库检索:如果是一个10,000条的数据库检索结果集,数据库是一定要把所有记录内容都取得以后再开始返回给应用结果集的。所以即使检索匹配总数很多,Lucene的结果集占用的内存空间也不会很多。对于一般的模糊检索应用是用不到这么多的结果的,头100条已经可以满足90%以上的检索需求。如果首批缓存结果数用完后还要读取更后面的结果时Searcher会再次检索并生成一个上次的搜索缓存数大1倍的缓存,并再重新向后抓取。所以如果构造一个Searcher去查1-120条结果,Searcher其实是进行了2次搜索过程:头100条取完后,缓存结果用完,Searcher重新检索再构造一个200条的结果缓存,依此类推,400条缓存,800条缓存。由于每次Searcher对象消失后,这些缓存也访问那不到了,你有可能想将结果记录缓存下来,缓存数尽量保证在100以下以充分利用首次的结果缓存,不让Lucene浪费多次检索,而且可以分级进行结果缓存。Lucene的另外一个特点是在收集结果的过程中将匹配度低的结果自动过滤掉了。这也是和数据库应用需要将搜索的结果全部返回不同之处。:支持中文的Tokenizer:这里有2个版本,一个是通过JavaCC生成的,对CJK部分按一个字符一个TOKEN索引,另外一个是从SimpleTokenizer改写的,对英文支持数字和字母TOKEN,对中文按迭代索引。
基于XML数据源的索引器:XMLIndexer,因此所有数据源只要能够按照DTD转换成指定的XML,就可以用XMLIndxer进行索引了。
根据某个字段排序:按记录索引顺序排序结果的搜索器:IndexOrderSearcher,因此如果需要让搜索结果根据某个字段排序,可以让数据源先按某个字段排好序(比如:PriceField),这样索引后,然后在利用这个按记录的ID顺序检索的搜索器,结果就是相当于是那个字段排序的结果了。 Luene的确是一个面对对象设计的典范所有的问题都通过一个额外抽象层来方便以后的扩展和重用:你可以通过重新实现来达到自己的目的,而对其他模块而不需要;
简单的应用入口Searcher, Indexer,并调用底层一系列组件协同的完成搜索任务;
所有的对象的任务都非常专一:比如搜索过程:QueryParser分析将查询语句转换成一系列的精确查询的组合(Query),通过底层的索引读取结构IndexReader进行索引的读取,并用相应的打分器给搜索结果进行打分/排序等。所有的功能模块原子化程度非常高,因此可以通过重新实现而不需要修改其他模块。
除了灵活的应用接口设计,Lucene还提供了一些适合大多数应用的语言分析器实现(SimpleAnalyser,StandardAnalyser),这也是新用户能够很快上手的重要原因之一。 这些优点都是非常值得在以后的开发中学习借鉴的。作为一个通用工具包,Lunece的确给予了需要将全文检索功能嵌入到应用中的开发者很多的便利。此外,通过对Lucene的学习和使用,我也更深刻地理解了为什么很多数据库优化设计中要求,比如:尽可能对字段进行索引来提高查询速度,但过多的索引会对数据库表的更新操作变慢,而对结果过多的排序条件,实际上往往也是性能的杀手之一。
很多商业数据库对大批量的数据插入操作会提供一些优化参数,这个作用和索引器的merge_factor的作用是类似的,
20%/80%原则:查的结果多并不等于质量好,尤其对于返回结果集很大,如何优化这头几十条结果的质量往往才是最重要的。
尽可能让应用从数据库中获得比较小的结果集,因为即使对于大型数据库,对结果集的随机访问也是一个非常消耗资源的操作。参考资料:Apache: Lucene ProjectLucene开发/用户邮件列表归档The Lucene search engine: Powerful, flexible, and freeLucene TutorialNotes on distributed searching with Lucene中文语言的切分词搜索引擎工具介绍Lucene作者Cutting的几篇论文和专利 Lucene的.NET实现:dotLuceneLucene作者Cutting的另外一个项目:基于Java的搜索引擎Nutch
关于基于词表和N-Gram的切分词比较
留言簿(10)
阅读排行榜
评论排行榜

我要回帖

更多关于 modi japanese人母 的文章

 

随机推荐