﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-&amp;nbsp;感悟人生　享受生活&lt;br&gt;&amp;nbsp;分享技术　编写未来-随笔分类-道听途说</title><link>http://www.blogjava.net/dybjsun/category/16010.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 08 Mar 2008 23:13:38 GMT</lastBuildDate><pubDate>Sat, 08 Mar 2008 23:13:38 GMT</pubDate><ttl>60</ttl><item><title>Tomcat启动分析</title><link>http://www.blogjava.net/dybjsun/archive/2008/01/10/174216.html</link><dc:creator>dybjsun</dc:creator><author>dybjsun</author><pubDate>Thu, 10 Jan 2008 03:24:00 GMT</pubDate><guid>http://www.blogjava.net/dybjsun/archive/2008/01/10/174216.html</guid><wfw:comment>http://www.blogjava.net/dybjsun/comments/174216.html</wfw:comment><comments>http://www.blogjava.net/dybjsun/archive/2008/01/10/174216.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dybjsun/comments/commentRss/174216.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dybjsun/services/trackbacks/174216.html</trackback:ping><description><![CDATA[
		<h3>1 - Tomcat Server的组成部分 </h3>
		<p>
				<strong>1.1 - Server </strong>
		</p>
		<p>A Server element represents the entire Catalina servlet container. (Singleton) </p>
		<p>
				<strong>1.2 - Service </strong>
		</p>
		<p>A Service element represents the combination of one or more Connector components that share a single Engine<br />Service是这样一个集合：它由一个或者多个Connector组成，以及一个Engine，负责处理所有Connector所获得的客户请求<br /></p>
		<p>
				<strong>1.3 - Connector </strong>
		</p>
		<p>一个Connector将在某个指定端口上侦听客户请求，并将获得的请求交给Engine来处理，从Engine处获得回应并返回客户<br />TOMCAT有两个典型的Connector，一个直接侦听来自browser的http请求，一个侦听来自其它WebServer的请求<br />Coyote Http/1.1 Connector 在端口8080处侦听来自客户browser的http请求<br />Coyote JK2 Connector 在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求<br /></p>
		<p>
				<strong>1.4 - Engine </strong>
		</p>
		<p>The Engine element represents the entire request processing machinery associated with a particular Service<br />It receives and processes all requests from one or more Connectors<br />and returns the completed response to the Connector for ultimate transmission back to the client<br />Engine下可以配置多个虚拟主机Virtual Host，每个虚拟主机都有一个域名<br />当Engine获得一个请求时，它把该请求匹配到某个Host上，然后把该请求交给该Host来处理<br />Engine有一个默认虚拟主机，当请求无法匹配到任何一个Host上的时候，将交给该默认Host来处理<br /></p>
		<p>
				<strong>1.5 - Host </strong>
		</p>
		<p> </p>
		<p>代表一个Virtual Host，虚拟主机，每个虚拟主机和某个网络域名Domain Name相匹配<br />每个虚拟主机下都可以部署(deploy)一个或者多个Web App，每个Web App对应于一个Context，有一个Context path<br />当Host获得一个请求时，将把该请求匹配到某个Context上，然后把该请求交给该Context来处理<br />匹配的方法是“最长匹配”，所以一个path==""的Context将成为该Host的默认Context<br />所有无法和其它Context的路径名匹配的请求都将最终和该默认Context匹配<br /></p>
		<p>
				<strong>1.6 - Context </strong>
		</p>
		<p>一个Context对应于一个Web Application，一个Web Application由一个或者多个Servlet组成<br />Context在创建的时候将根据配置文件$CATALINA_HOME/conf/web.xml和$WEBAPP_HOME/WEB-INF/web.xml载入Servlet类<br />当Context获得请求时，将在自己的映射表(mapping table)中寻找相匹配的Servlet类<br />如果找到，则执行该类，获得请求的回应，并返回<br /></p>
		<h3>2 - Tomcat Server的结构图<img src="http://www.blogjava.net/images/blogjava_net/dybjsun/27213/links/01-startup.gif" alt="01-startup.gif" border="0" height="313" width="747" /></h3>
		<img src="file:///C:/DOCUME%7E1/dybjsun/LOCALS%7E1/Temp/moz-screenshot.jpg" alt="" />
		<img alt="" src="file:///C:/Documents%20and%20Settings/dybjsun/My%20Documents/1451535.aspx_files/01-startup.gif" tppabs="http://zooo.51.net/heavyz/cs/tomcat/notes/01-startup.gif" />
		<h3>3 - 配置文件$CATALINA_HOME/conf/server.xml的说明 </h3>
		<p>该文件描述了如何启动Tomcat Server </p>
		<p> </p>
		<h3>4 - Context的部署配置文件web.xml的说明 </h3>
		<p>一个Context对应于一个Web App，每个Web App是由一个或者多个servlet组成的<br />当一个Web App被初始化的时候，它将用自己的ClassLoader对象载入“部署配置文件web.xml”中定义的每个servlet类<br />它首先载入在$CATALINA_HOME/conf/web.xml中部署的servlet类<br />然后载入在自己的Web App根目录下的WEB-INF/web.xml中部署的servlet类<br />web.xml文件有两部分：servlet类定义和servlet映射定义<br />每个被载入的servlet类都有一个名字，且被填入该Context的映射表(mapping table)中，和某种URL PATTERN对应<br />当该Context获得请求时，将查询mapping table，找到被请求的servlet，并执行以获得请求回应<br /></p>
		<p>分析一下所有的Context共享的web.xml文件，在其中定义的servlet被所有的Web App载入<br /></p>
		<p> </p>
		<h3>5 - Tomcat Server处理一个http请求的过程 </h3>
		<p>假设来自客户的请求为：<br />http://localhost:8080/wsota/wsota_index.jsp<br /></p>1) 请求被发送到本机端口8080，被在那里侦听的Coyote HTTP/1.1 Connector获得<br />2) Connector把该请求交给它所在的Service的Engine来处理，并等待来自Engine的回应<br />3) Engine获得请求localhost/wsota/wsota_index.jsp，匹配它所拥有的所有虚拟主机Host<br />4) Engine匹配到名为localhost的Host（即使匹配不到也把请求交给该Host处理，因为该Host被定义为该Engine的默认主机）<br />5) localhost Host获得请求/wsota/wsota_index.jsp，匹配它所拥有的所有Context<br />6) Host匹配到路径为/wsota的Context（如果匹配不到就把该请求交给路径名为""的Context去处理）<br />7) path="/wsota"的Context获得请求/wsota_index.jsp，在它的mapping table中寻找对应的servlet<br />8) Context匹配到URL PATTERN为*.jsp的servlet，对应于JspServlet类<br />9) 构造HttpServletRequest对象和HttpServletResponse对象，作为参数调用JspServlet的doGet或doPost方法<br />10)Context把执行完了之后的HttpServletResponse对象返回给Host<br />11)Host把HttpServletResponse对象返回给Engine<br />12)Engine把HttpServletResponse对象返回给Connector<br />13)Connector把HttpServletResponse对象返回给客户browser<pre><font color="#008000"><br />&lt;!-----------------------------------------------------------------------------------------------&gt;<br /></font><br /><br />&lt;web-app&gt;<br /><br /><font color="#008000"><br />  &lt;!-- 概述：<br />       该文件是所有的WEB APP共用的部署配置文件，<br />       每当一个WEB APP被DEPLOY，该文件都将先被处理，然后才是WEB APP自己的/WEB-INF/web.xml<br />       --&gt;<br /></font><br /><br /><font color="#008000"><br />  &lt;!--  +-------------------------+  --&gt;<br />  &lt;!--  |    servlet类定义部分    |  --&gt;<br />  &lt;!--  +-------------------------+  --&gt;<br /></font><br /><br /><font color="#008000"><br />  &lt;!-- DefaultServlet<br />       当用户的HTTP请求无法匹配任何一个servlet的时候，该servlet被执行<br />       URL PATTERN MAPPING : /<br />       --&gt;<br /></font><br />    &lt;servlet&gt;<br />        &lt;servlet-name&gt;default&lt;/servlet-name&gt;<br />        &lt;servlet-class&gt;<br />          org.apache.catalina.servlets.DefaultServlet<br />        &lt;/servlet-class&gt;<br />        &lt;init-param&gt;<br />            &lt;param-name&gt;debug&lt;/param-name&gt;<br />            &lt;param-value&gt;0&lt;/param-value&gt;<br />        &lt;/init-param&gt;<br />        &lt;init-param&gt;<br />            &lt;param-name&gt;listings&lt;/param-name&gt;<br />            &lt;param-value&gt;true&lt;/param-value&gt;<br />        &lt;/init-param&gt;<br />        &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;<br />    &lt;/servlet&gt;<br /><br /><font color="#008000"><br />  &lt;!-- InvokerServlet<br />       处理一个WEB APP中的匿名servlet<br />       当一个servlet被编写并编译放入/WEB-INF/classes/中，却没有在/WEB-INF/web.xml中定义的时候<br />       该servlet被调用，把匿名servlet映射成/servlet/ClassName的形式<br />       URL PATTERN MAPPING : /servlet/*<br />       --&gt;<br /></font><br />    &lt;servlet&gt;<br />        &lt;servlet-name&gt;invoker&lt;/servlet-name&gt;<br />        &lt;servlet-class&gt;<br />          org.apache.catalina.servlets.InvokerServlet<br />        &lt;/servlet-class&gt;<br />        &lt;init-param&gt;<br />            &lt;param-name&gt;debug&lt;/param-name&gt;<br />            &lt;param-value&gt;0&lt;/param-value&gt;<br />        &lt;/init-param&gt;<br />        &lt;load-on-startup&gt;2&lt;/load-on-startup&gt;<br />    &lt;/servlet&gt;<br /><br /><font color="#008000"><br />  &lt;!-- JspServlet<br />       当请求的是一个JSP页面的时候（*.jsp）该servlet被调用<br />       它是一个JSP编译器，将请求的JSP页面编译成为servlet再执行<br />       URL PATTERN MAPPING : *.jsp<br />       --&gt;<br /></font><br />    &lt;servlet&gt;<br />        &lt;servlet-name&gt;jsp&lt;/servlet-name&gt;<br />        &lt;servlet-class&gt;org.apache.jasper.servlet.JspServlet&lt;/servlet-class&gt;<br />        &lt;init-param&gt;<br />            &lt;param-name&gt;logVerbosityLevel&lt;/param-name&gt;<br />            &lt;param-value&gt;WARNING&lt;/param-value&gt;<br />        &lt;/init-param&gt;<br />        &lt;load-on-startup&gt;3&lt;/load-on-startup&gt;<br />    &lt;/servlet&gt;<br /><br /><br /><font color="#008000"><br />  &lt;!--  +---------------------------+  --&gt;<br />  &lt;!--  |    servlet映射定义部分    |  --&gt;<br />  &lt;!--  +---------------------------+  --&gt;<br /></font><br /><br />    &lt;servlet-mapping&gt;<br />        &lt;servlet-name&gt;default&lt;/servlet-name&gt;<br />        &lt;url-pattern&gt;/&lt;/url-pattern&gt;<br />    &lt;/servlet-mapping&gt;<br /><br />    &lt;servlet-mapping&gt;<br />        &lt;servlet-name&gt;invoker&lt;/servlet-name&gt;<br />        &lt;url-pattern&gt;/servlet/*&lt;/url-pattern&gt;<br />    &lt;/servlet-mapping&gt;<br /><br />    &lt;servlet-mapping&gt;<br />        &lt;servlet-name&gt;jsp&lt;/servlet-name&gt;<br />        &lt;url-pattern&gt;*.jsp&lt;/url-pattern&gt;<br />    &lt;/servlet-mapping&gt;<br /><br /><font color="#008000"><br />  &lt;!--  +------------------------+  --&gt;<br />  &lt;!--  |    其它部分，略去先    |  --&gt;<br />  &lt;!--  +------------------------+  --&gt;<br /></font><br />    ... ... ... ...<br /><br />&lt;/web-app&gt;<br /><br /><font color="#008000"><br />&lt;!-----------------------------------------------------------------------------------------------&gt;<br /></font></pre><pre><font color="#008000"><br />&lt;!-----------------------------------------------------------------------------------------------&gt;<br /></font><br /><br /><font color="#008000"><br />&lt;!-- 启动Server<br />     在端口8005处等待关闭命令<br />     如果接受到"SHUTDOWN"字符串则关闭服务器<br />     --&gt;<br /></font><br />&lt;Server port="8005" shutdown="SHUTDOWN" debug="0"&gt;<br /><br /><font color="#008000"><br />  &lt;!-- Listener ???<br />       目前没有看到这里<br />       --&gt;<br /></font><br />  &lt;Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" debug="0"/&gt;<br />  &lt;Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" debug="0"/&gt;<br /><br /><font color="#008000"><br />  &lt;!-- Global JNDI resources ???<br />       目前没有看到这里，先略去<br />       --&gt;<br /></font><br />  &lt;GlobalNamingResources&gt;<br />    ... ... ... ...<br />  &lt;/GlobalNamingResources&gt;<br /><br /><font color="#008000"><br />  &lt;!-- Tomcat的Standalone Service<br />       Service是一组Connector的集合<br />       它们共用一个Engine来处理所有Connector收到的请求<br />       --&gt;<br /></font><br />  &lt;Service name="Tomcat-Standalone"&gt;<br /><br /><font color="#008000"><br />    &lt;!-- Coyote HTTP/1.1 Connector<br />         className : 该Connector的实现类是org.apache.coyote.tomcat4.CoyoteConnector<br />         port : 在端口号8080处侦听来自客户browser的HTTP1.1请求<br />         minProcessors : 该Connector先创建5个线程等待客户请求，每个请求由一个线程负责<br />         maxProcessors : 当现有的线程不够服务客户请求时，若线程总数不足75个，则创建新线程来处理请求<br />         acceptCount : 当现有线程已经达到最大数75时，为客户请求排队<br />                       当队列中请求数超过100时，后来的请求返回Connection refused错误<br />         redirectport : 当客户请求是https时，把该请求转发到端口8443去<br />         其它属性略<br />         --&gt;<br /></font><br />    &lt;Connector className="org.apache.coyote.tomcat4.CoyoteConnector" <br />               port="8080" <br />               minProcessors="5" maxProcessors="75" acceptCount="100" <br />               enableLookups="true" <br />               redirectPort="8443" <br />               debug="0" <br />               connectionTimeout="20000" <br />               useURIValidationHack="false" <br />               disableUploadTimeout="true" /&gt;<br /><br /><font color="#008000"><br />    &lt;!-- Engine用来处理Connector收到的Http请求<br />         它将匹配请求和自己的虚拟主机，并把请求转交给对应的Host来处理<br />         默认虚拟主机是localhost<br />         --&gt;<br /></font><br />    &lt;Engine name="Standalone" defaultHost="localhost" debug="0"&gt;<br /><br /><font color="#008000"><br />      &lt;!-- 日志类，目前没有看到，略去先 --&gt;<br /></font><br />      &lt;Logger className="org.apache.catalina.logger.FileLogger" .../&gt;<br /><font color="#008000"><br />      &lt;!-- Realm，目前没有看到，略去先 --&gt;<br /></font><br />      &lt;Realm className="org.apache.catalina.realm.UserDatabaseRealm" .../&gt;<br /><br /><font color="#008000"><br />      &lt;!-- 虚拟主机localhost<br />           appBase : 该虚拟主机的根目录是webapps/<br />           它将匹配请求和自己的Context的路径，并把请求转交给对应的Context来处理<br />           --&gt;<br /></font><br />      &lt;Host name="localhost" debug="0" appBase="webapps" unpackWARs="true" autoDeploy="true"&gt;<br /><br /><font color="#008000"><br />        &lt;!-- 日志类，目前没有看到，略去先 --&gt;<br /></font><br />        &lt;Logger className="org.apache.catalina.logger.FileLogger" .../&gt;<br /><br /><font color="#008000"><br />        &lt;!-- Context，对应于一个Web App<br />             path : 该Context的路径名是""，故该Context是该Host的默认Context<br />             docBase : 该Context的根目录是webapps/mycontext/<br />             --&gt;<br /></font><br />        &lt;Context path="" docBase="mycontext" debug="0"/&gt;<br /><br /><font color="#008000"><br />        &lt;!-- 另外一个Context，路径名是/wsota --&gt;<br /></font><br />        &lt;Context path="/wsota" docBase="wsotaProject" debug="0"/&gt;<br /><br /><br />      &lt;/Host&gt;<br /><br />    &lt;/Engine&gt;<br /><br />  &lt;/Service&gt;<br /><br />&lt;/Server&gt;<br /><br /><font color="#008000"><br />&lt;!-----------------------------------------------------------------------------------------------&gt;<br /></font></pre><img src ="http://www.blogjava.net/dybjsun/aggbug/174216.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dybjsun/" target="_blank">dybjsun</a> 2008-01-10 11:24 <a href="http://www.blogjava.net/dybjsun/archive/2008/01/10/174216.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>全文搜索 lucene使用与优化</title><link>http://www.blogjava.net/dybjsun/archive/2008/01/09/174065.html</link><dc:creator>dybjsun</dc:creator><author>dybjsun</author><pubDate>Wed, 09 Jan 2008 09:17:00 GMT</pubDate><guid>http://www.blogjava.net/dybjsun/archive/2008/01/09/174065.html</guid><wfw:comment>http://www.blogjava.net/dybjsun/comments/174065.html</wfw:comment><comments>http://www.blogjava.net/dybjsun/archive/2008/01/09/174065.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dybjsun/comments/commentRss/174065.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dybjsun/services/trackbacks/174065.html</trackback:ping><description><![CDATA[1 lucene简介<br />1.1 什么是lucene<br />Lucene是一个全文搜索框架，而不是应用产品。因此它并不像www.baidu.com 或者google Desktop那么拿来就能用，它只是提供了一种工具让你能实现这些产品。<br /><br />1.2 lucene能做什么<br />要回答这个问题，先要了解lucene的本质。实际上lucene的功能很单一，说到底，就是你给它若干个字符串，然后它为你提供一个全文搜索服务，告诉你你要搜索的关键词出现在哪里。知道了这个本质，你就可以发挥想象做任何符合这个条件的事情了。你可以把站内新闻都索引了，做个资料库；你可以把一个数据库表的若干个字段索引起来，那就不用再担心因为“%like%”而锁表了；你也可以写个自己的搜索引擎……<br /><br />1.3 你该不该选择lucene<br />下面给出一些测试数据，如果你觉得可以接受，那么可以选择。<br />测试一：250万记录，300M左右文本，生成索引380M左右，800线程下平均处理时间300ms。<br />测试二：37000记录，索引数据库中的两个varchar字段，索引文件2.6M，800线程下平均处理时间1.5ms。<br /><br />2 lucene的工作方式<br />lucene提供的服务实际包含两部分：一入一出。所谓入是写入，即将你提供的源（本质是字符串）写入索引或者将其从索引中删除；所谓出是读出，即向用户提供全文搜索服务，让用户可以通过关键词定位源。<br /><br />2.1写入流程<br />源字符串首先经过analyzer处理，包括：分词，分成一个个单词；去除stopword（可选）。<br />将源中需要的信息加入Document的各个Field中，并把需要索引的Field索引起来，把需要存储的Field存储起来。<br />将索引写入存储器，存储器可以是内存或磁盘。<br /><br />2.2读出流程<br />用户提供搜索关键词，经过analyzer处理。<br />对处理后的关键词搜索索引找出对应的Document。<br />用户根据需要从找到的Document中提取需要的Field。<br /><br />3 一些需要知道的概念<br />lucene用到一些概念，了解它们的含义，有利于下面的讲解。<br /><br />3.1 analyzer<br />Analyzer 是分析器，它的作用是把一个字符串按某种规则划分成一个个词语，并去除其中的无效词语，这里说的无效词语是指英文中的“of”、 “the”，中文中的 “的”、“地”等词语，这些词语在文章中大量出现，但是本身不包含什么关键信息，去掉有利于缩小索引文件、提高效率、提高命中率。<br />分词的规则千变万化，但目的只有一个：按语义划分。这点在英文中比较容易实现，因为英文本身就是以单词为单位的，已经用空格分开；而中文则必须以某种方法将连成一片的句子划分成一个个词语。具体划分方法下面再详细介绍，这里只需了解分析器的概念即可。<br /><br />3.2 document<br />用户提供的源是一条条记录，它们可以是文本文件、字符串或者数据库表的一条记录等等。一条记录经过索引之后，就是以一个Document的形式存储在索引文件中的。用户进行搜索，也是以Document列表的形式返回。<br /><br />3.3 field<br />一个Document可以包含多个信息域，例如一篇文章可以包含“标题”、“正文”、“最后修改时间”等信息域，这些信息域就是通过Field在Document中存储的。<br />Field有两个属性可选：存储和索引。通过存储属性你可以控制是否对这个Field进行存储；通过索引属性你可以控制是否对该Field进行索引。这看起来似乎有些废话，事实上对这两个属性的正确组合很重要，下面举例说明：<br />还是以刚才的文章为例子，我们需要对标题和正文进行全文搜索，所以我们要把索引属性设置为真，同时我们希望能直接从搜索结果中提取文章标题，所以我们把标题域的存储属性设置为真，但是由于正文域太大了，我们为了缩小索引文件大小，将正文域的存储属性设置为假，当需要时再直接读取文件；我们只是希望能从搜索解果中提取最后修改时间，不需要对它进行搜索，所以我们把最后修改时间域的存储属性设置为真，索引属性设置为假。上面的三个域涵盖了两个属性的三种组合，还有一种全为假的没有用到，事实上Field不允许你那么设置，因为既不存储又不索引的域是没有意义的。<br /><br />3.4 term<br />term是搜索的最小单位，它表示文档的一个词语，term由两部分组成：它表示的词语和这个词语所出现的field。<br /><br />3.5 tocken<br />tocken是term的一次出现，它包含trem文本和相应的起止偏移，以及一个类型字符串。一句话中可以出现多次相同的词语，它们都用同一个term表示，但是用不同的tocken，每个tocken标记该词语出现的地方。<br /><br />3.6 segment<br />添加索引时并不是每个document都马上添加到同一个索引文件，它们首先被写入到不同的小文件，然后再合并成一个大索引文件，这里每个小文件都是一个segment。<br /><br />4 lucene的结构<br />lucene包括core和sandbox两部分，其中core是lucene稳定的核心部分，sandbox包含了一些附加功能，例如highlighter、各种分析器。<br />Lucene core有七个包：analysis，document，index，queryParser，search，store，util。<br />4.1 analysis<br />Analysis包含一些内建的分析器，例如按空白字符分词的WhitespaceAnalyzer，添加了stopwrod过滤的StopAnalyzer，最常用的StandardAnalyzer。<br />4.2 document<br />Document包含文档的数据结构，例如Document类定义了存储文档的数据结构，Field类定义了Document的一个域。<br />4.3 index<br />Index 包含了索引的读写类，例如对索引文件的segment进行写、合并、优化的IndexWriter类和对索引进行读取和删除操作的  IndexReader类，这里要注意的是不要被IndexReader这个名字误导，以为它是索引文件的读取类，实际上删除索引也是由它完成，  IndexWriter只关心如何将索引写入一个个segment，并将它们合并优化；IndexReader则关注索引文件中各个文档的组织形式。<br />4.4 queryParser<br />QueryParser 包含了解析查询语句的类，lucene的查询语句和sql语句有点类似，有各种保留字，按照一定的语法可以组成各种查询。 Lucene有很多种 Query类，它们都继承自Query，执行各种特殊的查询，QueryParser的作用就是解析查询语句，按顺序调用各种 Query类查找出结果。<br />4.5 search<br />Search包含了从索引中搜索结果的各种类，例如刚才说的各种Query类，包括TermQuery、BooleanQuery等就在这个包里。<br />4.6 store<br />Store包含了索引的存储类，例如Directory定义了索引文件的存储结构，FSDirectory为存储在文件中的索引，RAMDirectory为存储在内存中的索引，MmapDirectory为使用内存映射的索引。<br />4.7 util<br />Util包含一些公共工具类，例如时间和字符串之间的转换工具。<br /><br />5 如何建索引<br />5.1 最简单的能完成索引的代码片断<br /><br />IndexWriter writer = new IndexWriter(“/data/index/”, new StandardAnalyzer(), true);<br />Document doc = new Document();<br />doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));<br />doc.add(new Field("content", "lucene works well", Field.Store.YES, Field.Index.TOKENIZED));<br />writer.addDocument(doc);<br />writer.optimize();<br />writer.close();<br /><br />下面我们分析一下这段代码。<br />首先我们创建了一个writer，并指定存放索引的目录为“/data/index”，使用的分析器为StandardAnalyzer，第三个参数说明如果已经有索引文件在索引目录下，我们将覆盖它们。<br />然后我们新建一个document。<br />我们向document添加一个field，名字是“title”，内容是“lucene introduction”，对它进行存储并索引。<br />再添加一个名字是“content”的field，内容是“lucene works well”，也是存储并索引。<br />然后我们将这个文档添加到索引中，如果有多个文档，可以重复上面的操作，创建document并添加。<br />添加完所有document，我们对索引进行优化，优化主要是将多个segment合并到一个，有利于提高索引速度。<br />随后将writer关闭，这点很重要。<br /><br />对，创建索引就这么简单！<br />当然你可能修改上面的代码获得更具个性化的服务。<br /><br />5.2 将索引直接写在内存<br />你需要首先创建一个RAMDirectory，并将其传给writer，代码如下：<br /><br />Directory dir = new RAMDirectory();<br />IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);<br />Document doc = new Document();<br />doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));<br />doc.add(new Field("content", "lucene works well", Field.Store.YES, Field.Index.TOKENIZED));<br />writer.addDocument(doc);<br />writer.optimize();<br />writer.close();<br /><br />5.3 索引文本文件<br />如果你想把纯文本文件索引起来，而不想自己将它们读入字符串创建field，你可以用下面的代码创建field：<br /><br />Field field = new Field("content", new FileReader(file));<br /><br />这里的file就是该文本文件。该构造函数实际上是读去文件内容，并对其进行索引，但不存储。<br /><br />6 如何维护索引<br />索引的维护操作都是由IndexReader类提供。<br /><br />6.1 如何删除索引<br />lucene提供了两种从索引中删除document的方法，一种是<br /><br />void deleteDocument(int docNum)<br /><br />这种方法是根据document在索引中的编号来删除，每个document加进索引后都会有个唯一编号，所以根据编号删除是一种精确删除，但是这个编号是索引的内部结构，一般我们不会知道某个文件的编号到底是几，所以用处不大。另一种是<br /><br />void deleteDocuments(Term term)<br /><br />这种方法实际上是首先根据参数term执行一个搜索操作，然后把搜索到的结果批量删除了。我们可以通过这个方法提供一个严格的查询条件，达到删除指定document的目的。<br />下面给出一个例子：<br /><br />Directory dir = FSDirectory.getDirectory(PATH, false);<br />IndexReader reader = IndexReader.open(dir);<br />Term term = new Term(field, key);<br />reader.deleteDocuments(term);<br />reader.close();<br /><br />6.2 如何更新索引<br />lucene并没有提供专门的索引更新方法，我们需要先将相应的document删除，然后再将新的document加入索引。例如：<br /><br />Directory dir = FSDirectory.getDirectory(PATH, false);<br />IndexReader reader = IndexReader.open(dir);<br />Term term = new Term(“title”, “lucene introduction”);<br />reader.deleteDocuments(term);<br />reader.close();<br /><br />IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);<br />Document doc = new Document();<br />doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));<br />doc.add(new Field("content", "lucene is funny", Field.Store.YES, Field.Index.TOKENIZED));<br />writer.addDocument(doc);<br />writer.optimize();<br />writer.close();<br /><br /><br />7 如何搜索<br />lucene 的搜索相当强大，它提供了很多辅助查询类，每个类都继承自Query类，各自完成一种特殊的查询，你可以像搭积木一样将它们任意组合使用，完成一些复杂操作；另外lucene还提供了Sort类对结果进行排序，提供了Filter类对查询条件进行限制。你或许会不自觉地拿它跟SQL语句进行比较： “lucene能执行and、or、order by、where、like ‘%xx%’操作吗？”回答是：“当然没问题！”<br /><br />7.1 各种各样的Query<br />下面我们看看lucene到底允许我们进行哪些查询操作：<br /><br />7.1.1 TermQuery<br />首先介绍最基本的查询，如果你想执行一个这样的查询：“在content域中包含‘lucene’的document”，那么你可以用TermQuery：<br /><br />Term t = new Term("content", " lucene";<br />Query query = new TermQuery(t);<br /><br />7.1.2 BooleanQuery<br />如果你想这么查询：“在content域中包含java或perl的document”，那么你可以建立两个TermQuery并把它们用BooleanQuery连接起来：<br /><br />TermQuery termQuery1 = new TermQuery(new Term("content", "java");<br />TermQuery termQuery 2 = new TermQuery(new Term("content", "perl");<br />BooleanQuery booleanQuery = new BooleanQuery();<br />booleanQuery.add(termQuery 1, BooleanClause.Occur.SHOULD);<br />booleanQuery.add(termQuery 2, BooleanClause.Occur.SHOULD);<br /><br />7.1.3 WildcardQuery<br />如果你想对某单词进行通配符查询，你可以用WildcardQuery，通配符包括’?’匹配一个任意字符和’*’匹配零个或多个任意字符，例如你搜索’use*’，你可能找到’useful’或者’useless’：<br /><br />Query query = new WildcardQuery(new Term("content", "use*");<br /><br />7.1.4 PhraseQuery<br />你可能对中日关系比较感兴趣，想查找‘中’和‘日’挨得比较近（5个字的距离内）的文章，超过这个距离的不予考虑，你可以：<br /><br />PhraseQuery query = new PhraseQuery();<br />query.setSlop(5);<br />query.add(new Term("content ", “中”));<br />query.add(new Term(“content”, “日”));<br /><br />那么它可能搜到“中日合作……”、“中方和日方……”，但是搜不到“中国某高层领导说日本欠扁”。<br /><br />7.1.5 PrefixQuery<br />如果你想搜以‘中’开头的词语，你可以用PrefixQuery：<br /><br />PrefixQuery query = new PrefixQuery(new Term("content ", "中");<br /><br />7.1.6 FuzzyQuery<br />FuzzyQuery用来搜索相似的term，使用Levenshtein算法。假设你想搜索跟‘wuzza’相似的词语，你可以：<br /><br />Query query = new FuzzyQuery(new Term("content", "wuzza");<br /><br />你可能得到‘fuzzy’和‘wuzzy’。<br /><br />7.1.7 RangeQuery<br />另一个常用的Query是RangeQuery，你也许想搜索时间域从20060101到20060130之间的document，你可以用RangeQuery：<br /><br />RangeQuery query = new RangeQuery(new Term(“time”, “20060101”), new Term(“time”, “20060130”), true);<br /><br />最后的true表示用闭合区间。<br /><br />7.2 QueryParser<br />看了这么多Query，你可能会问：“不会让我自己组合各种Query吧，太麻烦了！”当然不会，lucene提供了一种类似于SQL语句的查询语句，我们姑且叫它lucene语句，通过它，你可以把各种查询一句话搞定，lucene会自动把它们查分成小块交给相应Query执行。下面我们对应每种 Query演示一下：<br />TermQuery可以用“field:key”方式，例如“content:lucene”。<br />BooleanQuery中‘与’用‘+’，‘或’用‘ ’，例如“content:java contenterl”。<br />WildcardQuery仍然用‘?’和‘*’，例如“content:use*”。<br />PhraseQuery用‘~’，例如“content:"中日"~5”。<br />PrefixQuery用‘*’，例如“中*”。<br />FuzzyQuery用‘~’，例如“content: wuzza ~”。<br />RangeQuery用‘[]’或‘{}’，前者表示闭区间，后者表示开区间，例如“time:[20060101 TO 20060130]”，注意TO区分大小写。<br />你可以任意组合query string，完成复杂操作，例如“标题或正文包括lucene，并且时间在20060101到20060130之间的文章”可以表示为：“+ (title:lucene content:lucene) +time:[20060101 TO 20060130]”。代码如下：<br /><br />Directory dir = FSDirectory.getDirectory(PATH, false);<br />IndexSearcher is = new IndexSearcher(dir);<br />QueryParser parser = new QueryParser("content", new StandardAnalyzer());<br />Query query = parser.parse("+(title:lucene content:lucene) +time:[20060101 TO 20060130]";<br />Hits hits = is.search(query);<br />for (int i = 0; i &lt; hits.length(); i++)<br />{<br />Document doc = hits.doc(i);<br />System.out.println(doc.get("title");<br />}<br />is.close();<br /><br />首先我们创建一个在指定文件目录上的IndexSearcher。<br />然后创建一个使用StandardAnalyzer作为分析器的QueryParser，它默认搜索的域是content。<br />接着我们用QueryParser来parse查询字串，生成一个Query。<br />然后利用这个Query去查找结果，结果以Hits的形式返回。<br />这个Hits对象包含一个列表，我们挨个把它的内容显示出来。<br /><br />7.3 Filter<br />filter 的作用就是限制只查询索引的某个子集，它的作用有点像SQL语句里的where，但又有区别，它不是正规查询的一部分，只是对数据源进行预处理，然后交给查询语句。注意它执行的是预处理，而不是对查询结果进行过滤，所以使用filter的代价是很大的，它可能会使一次查询耗时提高一百倍。<br />最常用的filter是RangeFilter和QueryFilter。RangeFilter是设定只搜索指定范围内的索引；QueryFilter是在上次查询的结果中搜索。<br />Filter的使用非常简单，你只需创建一个filter实例，然后把它传给searcher。继续上面的例子，查询“时间在20060101到20060130之间的文章”除了将限制写在query string中，你还可以写在RangeFilter中：<br /><br />Directory dir = FSDirectory.getDirectory(PATH, false);<br />IndexSearcher is = new IndexSearcher(dir);<br />QueryParser parser = new QueryParser("content", new StandardAnalyzer());<br />Query query = parser.parse("title:lucene content:lucene";<br />RangeFilter filter = new RangeFilter("time", "20060101", "20060230", true, true);<br />Hits hits = is.search(query, filter);<br />for (int i = 0; i &lt; hits.length(); i++)<br />{<br />Document doc = hits.doc(i);<br />System.out.println(doc.get("title");<br />}<br />is.close();<br /><br />7.4 Sort<br />有时你想要一个排好序的结果集，就像SQL语句的“order by”，lucene能做到：通过Sort。<br />Sort sort = new Sort(“time”); //相当于SQL的“order by time”<br />Sort sort = new Sort(“time”, true); // 相当于SQL的“order by time desc”<br />下面是一个完整的例子：<br /><br />Directory dir = FSDirectory.getDirectory(PATH, false);<br />IndexSearcher is = new IndexSearcher(dir);<br />QueryParser parser = new QueryParser("content", new StandardAnalyzer());<br />Query query = parser.parse("title:lucene content:lucene";<br />RangeFilter filter = new RangeFilter("time", "20060101", "20060230", true, true);<br />Sort sort = new Sort(“time”);<br />Hits hits = is.search(query, filter, sort);<br />for (int i = 0; i &lt; hits.length(); i++)<br />{<br />Document doc = hits.doc(i);<br />System.out.println(doc.get("title");<br />}<br />is.close();<br /><br />8 分析器<br />在前面的概念介绍中我们已经知道了分析器的作用，就是把句子按照语义切分成一个个词语。英文切分已经有了很成熟的分析器：  StandardAnalyzer，很多情况下StandardAnalyzer是个不错的选择。甚至你会发现StandardAnalyzer也能对中文进行分词。<br />但是我们的焦点是中文分词，StandardAnalyzer能支持中文分词吗？实践证明是可以的，但是效果并不好，搜索“如果” 会把“牛奶不如果汁好喝”也搜索出来，而且索引文件很大。那么我们手头上还有什么分析器可以使用呢？core里面没有，我们可以在sandbox里面找到两个： ChineseAnalyzer和CJKAnalyzer。但是它们同样都有分词不准的问题。相比之下用StandardAnalyzer和  ChineseAnalyzer建立索引时间差不多，索引文件大小也差不多，CJKAnalyzer表现会差些，索引文件大且耗时比较长。<br />要解决问题，首先分析一下这三个分析器的分词方式。StandardAnalyzer和ChineseAnalyzer都是把句子按单个字切分，也就是说  “牛奶不如果汁好喝”会被它们切分成“牛 奶 不 如 果 汁 好 喝”；而CJKAnalyzer则会切分成“牛奶 奶不 不如 如果 果汁 汁好好喝”。这也就解释了为什么搜索“果汁”都能匹配这个句子。<br />以上分词的缺点至少有两个：匹配不准确和索引文件大。我们的目标是将上面的句子分解成 “牛奶 不如 果汁好喝”。这里的关键就是语义识别，我们如何识别“牛奶”是一个词而“奶不”不是词语？我们很自然会想到基于词库的分词法，也就是我们先得到一个词库，里面列举了大部分词语，我们把句子按某种方式切分，当得到的词语与词库中的项匹配时，我们就认为这种切分是正确的。这样切词的过程就转变成匹配的过程，而匹配的方式最简单的有正向最大匹配和逆向最大匹配两种，说白了就是一个从句子开头向后进行匹配，一个从句子末尾向前进行匹配。基于词库的分词词库非常重要，词库的容量直接影响搜索结果，在相同词库的前提下，据说逆向最大匹配优于正向最大匹配。<br />当然还有别的分词方法，这本身就是一个学科，我这里也没有深入研究。回到具体应用，我们的目标是能找到成熟的、现成的分词工具，避免重新发明车轮。经过网上搜索，用的比较多的是中科院的 ICTCLAS和一个不开放源码但是免费的JE-Analysis。ICTCLAS有个问题是它是一个动态链接库， java调用需要本地方法调用，不方便也有安全隐患，而且口碑也确实不大好。JE-Analysis效果还不错，当然也会有分词不准的地方，相比比较方便放心。<br /><br />9 性能优化<br />一直到这里，我们还是在讨论怎么样使lucene跑起来，完成指定任务。利用前面说的也确实能完成大部分功能。但是测试表明lucene的性能并不是很好，在大数据量大并发的条件下甚至会有半分钟返回的情况。另外大数据量的数据初始化建立索引也是一个十分耗时的过程。那么如何提高lucene的性能呢？下面从优化创建索引性能和优化搜索性能两方面介绍。<br /><br />9.1 优化创建索引性能<br />这方面的优化途径比较有限，IndexWriter提供了一些接口可以控制建立索引的操作，另外我们可以先将索引写入RAMDirectory，再批量写入FSDirectory，不管怎样，目的都是尽量少的文件IO，因为创建索引的最大瓶颈在于磁盘IO。另外选择一个较好的分析器也能提高一些性能。<br /><br />9.1.1 通过设置IndexWriter的参数优化索引建立<br />setMaxBufferedDocs(int maxBufferedDocs)<br />控制写入一个新的segment前内存中保存的document的数目，设置较大的数目可以加快建索引速度，默认为10。<br />setMaxMergeDocs(int maxMergeDocs)<br />控制一个segment中可以保存的最大document数目，值较小有利于追加索引的速度，默认Integer.MAX_VALUE，无需修改。<br />setMergeFactor(int mergeFactor)<br />控制多个segment合并的频率，值较大时建立索引速度较快，默认是10，可以在建立索引时设置为100。<br /><br />9.1.2 通过RAMDirectory缓写提高性能<br />我们可以先把索引写入RAMDirectory，达到一定数量时再批量写进FSDirectory，减少磁盘IO次数。<br /><br />FSDirectory fsDir = FSDirectory.getDirectory("/data/index", true);<br />RAMDirectory ramDir = new RAMDirectory();<br />IndexWriter fsWriter = new IndexWriter(fsDir, new StandardAnalyzer(), true);<br />IndexWriter ramWriter = new IndexWriter(ramDir, new StandardAnalyzer(), true);<br />while (there are documents to index)<br />{<br />... create Document ...<br />ramWriter.addDocument(doc);<br />if (condition for flushing memory to disk has been met)<br />{<br />fsWriter.addIndexes(new Directory[] { ramDir });<br />ramWriter.close();<br />ramWriter = new IndexWriter(ramDir, new StandardAnalyzer(), true);<br />}<br />}<br /><br />9.1.3 选择较好的分析器<br />这个优化主要是对磁盘空间的优化，可以将索引文件减小将近一半，相同测试数据下由600M减少到380M。但是对时间并没有什么帮助，甚至会需要更长时间，因为较好的分析器需要匹配词库，会消耗更多cpu，测试数据用StandardAnalyzer耗时133分钟；用MMAnalyzer耗时150分钟。<br /><br />9.2 优化搜索性能<br />虽然建立索引的操作非常耗时，但是那毕竟只在最初创建时才需要，平时只是少量的维护操作，更何况这些可以放到一个后台进程处理，并不影响用户搜索。我们创建索引的目的就是给用户搜索，所以搜索的性能才是我们最关心的。下面就来探讨一下如何提高搜索性能。<br /><br />9.2.1 将索引放入内存<br />这是一个最直观的想法，因为内存比磁盘快很多。Lucene提供了RAMDirectory可以在内存中容纳索引：<br /><br />Directory fsDir = FSDirectory.getDirectory(“/data/index/”, false);<br />Directory ramDir = new RAMDirectory(fsDir);<br />Searcher searcher = new IndexSearcher(ramDir);<br /><br />但是实践证明RAMDirectory和FSDirectory速度差不多，当数据量很小时两者都非常快，当数据量较大时（索引文件400M）RAMDirectory甚至比FSDirectory还要慢一点，这确实让人出乎意料。<br />而且lucene的搜索非常耗内存，即使将400M的索引文件载入内存，在运行一段时间后都会out of memory，所以个人认为载入内存的作用并不大。<br /><br />9.2.2 优化时间范围限制<br />既然载入内存并不能提高效率，一定有其它瓶颈，经过测试发现最大的瓶颈居然是时间范围限制，那么我们可以怎样使时间范围限制的代价最小呢？<br />当需要搜索指定时间范围内的结果时，可以：<br />1、用RangeQuery，设置范围，但是RangeQuery的实现实际上是将时间范围内的时间点展开，组成一个个BooleanClause加入到  BooleanQuery中查询，因此时间范围不可能设置太大，经测试，范围超过一个月就会抛 BooleanQuery.TooManyClauses，可以通过设置 BooleanQuery.setMaxClauseCount (int maxClauseCount)扩大，但是扩大也是有限的，并且随着maxClauseCount扩大，占用内存也扩大<br />2、用 RangeFilter代替RangeQuery，经测试速度不会比RangeQuery慢，但是仍然有性能瓶颈，查询的90%以上时间耗费在  RangeFilter，研究其源码发现RangeFilter实际上是首先遍历所有索引，生成一个BitSet，标记每个document，在时间范围内的标记为true，不在的标记为false，然后将结果传递给Searcher查找，这是十分耗时的。<br />3、进一步提高性能，这个又有两个思路：<br />a、缓存Filter结果。既然RangeFilter的执行是在搜索之前，那么它的输入都是一定的，就是IndexReader，而  IndexReader是由Directory决定的，所以可以认为RangeFilter的结果是由范围的上下限决定的，也就是由具体的  RangeFilter对象决定，所以我们只要以RangeFilter对象为键，将filter结果BitSet缓存起来即可。lucene API 已经提供了一个CachingWrapperFilter类封装了Filter及其结果，所以具体实施起来我们可以 cache CachingWrapperFilter对象，需要注意的是，不要被CachingWrapperFilter的名字及其说明误导，  CachingWrapperFilter看起来是有缓存功能，但的缓存是针对同一个filter的，也就是在你用同一个filter过滤不同  IndexReader时，它可以帮你缓存不同IndexReader的结果，而我们的需求恰恰相反，我们是用不同filter过滤同一个  IndexReader，所以只能把它作为一个封装类。<br />b、降低时间精度。研究Filter的工作原理可以看出，它每次工作都是遍历整个索引的，所以时间粒度越大，对比越快，搜索时间越短，在不影响功能的情况下，时间精度越低越好，有时甚至牺牲一点精度也值得，当然最好的情况是根本不作时间限制。<br />下面针对上面的两个思路演示一下优化结果（都采用800线程随机关键词随即时间范围）：<br />第一组，时间精度为秒：<br />方式 直接用RangeFilter 使用cache 不用filter<br />平均每个线程耗时 10s 1s 300ms<br /><br />第二组，时间精度为天<br />方式 直接用RangeFilter 使用cache 不用filter<br />平均每个线程耗时 900ms 360ms 300ms<br /><br />由以上数据可以得出结论：<br />1、 尽量降低时间精度，将精度由秒换成天带来的性能提高甚至比使用cache还好，最好不使用filter。<br />2、 在不能降低时间精度的情况下，使用cache能带了10倍左右的性能提高。<br /><br />9.2.3 使用更好的分析器<br />这个跟创建索引优化道理差不多，索引文件小了搜索自然会加快。当然这个提高也是有限的。较好的分析器相对于最差的分析器对性能的提升在20%以下。<br /><br />10 一些经验<br /><br />10.1关键词区分大小写<br />or AND TO等关键词是区分大小写的，lucene只认大写的，小写的当做普通单词。<br /><br />10.2 读写互斥性<br />同一时刻只能有一个对索引的写操作，在写的同时可以进行搜索<br /><br />10.3 文件锁<br />在写索引的过程中强行退出将在tmp目录留下一个lock文件，使以后的写操作无法进行，可以将其手工删除<br /><br />10.4 时间格式<br />lucene只支持一种时间格式yyMMddHHmmss，所以你传一个yy-MM-dd HH:mm:ss的时间给lucene它是不会当作时间来处理的<br /><br />10.5 设置boost<br />有些时候在搜索时某个字段的权重需要大一些，例如你可能认为标题中出现关键词的文章比正文中出现关键词的文章更有价值，你可以把标题的boost设置的更大，那么搜索结果会优先显示标题中出现关键词的文章（没有使用排序的前题下）。使用方法：<br />Field. setBoost(float boost);默认值是1.0，也就是说要增加权重的需要设置得比1大。<br /><br /><img src ="http://www.blogjava.net/dybjsun/aggbug/174065.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dybjsun/" target="_blank">dybjsun</a> 2008-01-09 17:17 <a href="http://www.blogjava.net/dybjsun/archive/2008/01/09/174065.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>log4j</title><link>http://www.blogjava.net/dybjsun/archive/2007/12/24/170000.html</link><dc:creator>dybjsun</dc:creator><author>dybjsun</author><pubDate>Mon, 24 Dec 2007 03:46:00 GMT</pubDate><guid>http://www.blogjava.net/dybjsun/archive/2007/12/24/170000.html</guid><wfw:comment>http://www.blogjava.net/dybjsun/comments/170000.html</wfw:comment><comments>http://www.blogjava.net/dybjsun/archive/2007/12/24/170000.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dybjsun/comments/commentRss/170000.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dybjsun/services/trackbacks/170000.html</trackback:ping><description><![CDATA[在强调可重用组件开发的今天，除了自己从头到尾开发一个可重用的日志操作类外，Apache为我们提供了一个强有力的日志操作包-Log4j。<br /><br />Log4j是Apache的一个开放源代码项目，通过使用Log4j，我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等；我们也可以控制每一条日志的输出格式；通过定义每一条日志信息的级别，我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是，这些可以通过一个配置文件来灵活地进行配置，而不需要修改应用的代码。<br /><br />此外，通过Log4j其他语言接口，您可以在C、C++、.Net、PL/SQL程序中使用Log4j，其语法和用法与在Java程序中一样，使得多语言分布式系统得到一个统一一致的日志组件模块。而且，通过使用各种第三方扩展，您可以很方便地将Log4j集成到J2EE、JINI甚至是SNMP应用中。<br /><br />说明：下面分为三部分，第一部分讲解如何配置log4j，第二部分为对log4j.properties配置文件中的各个属性的讲解，第三部分为对 log4j的详细讲解，如果只想配置上log4j，那么只需要看前两个部分就可以，如果想对log4j深入了解，则还需看第三部分。<br /><br />一、Log4j配置<br /><br />第一步：加入log4j-1.2.8.jar到lib下。<br /><br />第二步：在CLASSPATH下建立log4j.properties。内容如下：<br /><br />1 log4j.rootCategory=INFO, stdout , R<br /><br />2<br /><br />3 log4j.appender.stdout=org.apache.log4j.ConsoleAppender<br /><br />4 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout<br /><br />5 log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n<br /><br />6<br /><br />7 log4j.appender.R=org.apache.log4j.DailyRollingFileAppender<br /><br />8 log4j.appender.R.File=D:\\Tomcat 5.5\\logs\\qc.log<br /><br />9 log4j.appender.R.layout=org.apache.log4j.PatternLayout<br /><br />10 log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n<br /><br />11<br /><br />12 log4j.logger.com.neusoft=DEBUG<br /><br />13 log4j.logger.com.opensymphony.oscache=ERROR<br /><br />14 log4j.logger.net.sf.navigator=ERROR<br /><br />15 log4j.logger.org.apache.commons=ERROR<br /><br />16 log4j.logger.org.apache.struts=WARN<br /><br />17 log4j.logger.org.displaytag=ERROR<br /><br />18 log4j.logger.org.springframework=DEBUG<br /><br />19 log4j.logger.com.ibatis.db=WARN<br /><br />20 log4j.logger.org.apache.velocity=FATAL<br /><br />21<br /><br />22 log4j.logger.com.canoo.webtest=WARN<br /><br />23<br /><br />24 log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN<br /><br />25 log4j.logger.org.hibernate=DEBUG<br /><br />26 log4j.logger.org.logicalcobwebs=WARN<br /><br />第三步：相应的修改其中属性，修改之前就必须知道这些都是干什么的，在第二部分讲解。<br /><br />第四步：在要输出日志的类中加入相关语句：<br /><br />定义属性：protected final Log log = LogFactory.getLog(getClass());<br /><br />在相应的方法中：<br /><br />if (log.isDebugEnabled())<br /><br />{<br /><br />log.debug(“System …..”);<br /><br />}<br /><br />二、Log4j说明<br /><br />1 log4j.rootCategory=INFO, stdout , R<br /><br />此句为将等级为INFO的日志信息输出到stdout和R这两个目的地，stdout和R的定义在下面的代码，可以任意起名。等级可分为OFF、 FATAL、ERROR、WARN、INFO、DEBUG、ALL，如果配置OFF则不打出任何信息，如果配置为INFO这样只显示INFO, WARN, ERROR的log信息，而DEBUG信息不会被显示，具体讲解可参照第三部分定义配置文件中的logger。<br /><br />3 log4j.appender.stdout=org.apache.log4j.ConsoleAppender<br /><br />此句为定义名为stdout的输出端是哪种类型，可以是<br /><br />org.apache.log4j.ConsoleAppender（控制台），<br /><br />org.apache.log4j.FileAppender（文件），<br /><br />org.apache.log4j.DailyRollingFileAppender（每天产生一个日志文件），<br /><br />org.apache.log4j.RollingFileAppender（文件大小到达指定尺寸的时候产生一个新的文件）<br /><br />org.apache.log4j.WriterAppender（将日志信息以流格式发送到任意指定的地方）<br /><br />具体讲解可参照第三部分定义配置文件中的Appender。<br /><br />4 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout<br /><br />此句为定义名为stdout的输出端的layout是哪种类型，可以是<br /><br />org.apache.log4j.HTMLLayout（以HTML表格形式布局），<br /><br />org.apache.log4j.PatternLayout（可以灵活地指定布局模式），<br /><br />org.apache.log4j.SimpleLayout（包含日志信息的级别和信息字符串），<br /><br />org.apache.log4j.TTCCLayout（包含日志产生的时间、线程、类别等等信息）<br /><br />具体讲解可参照第三部分定义配置文件中的Layout。<br /><br />5 log4j.appender.stdout.layout.ConversionPattern= [QC] %p [%t] %C.%M(%L) | %m%n<br /><br />如果使用pattern布局就要指定的打印信息的具体格式ConversionPattern，打印参数如下：<br /><br />%m 输出代码中指定的消息<br /><br />%p 输出优先级，即DEBUG，INFO，WARN，ERROR，FATAL<br /><br />%r 输出自应用启动到输出该log信息耗费的毫秒数<br /><br />%c 输出所属的类目，通常就是所在类的全名<br /><br />%t 输出产生该日志事件的线程名<br /><br />%n 输出一个回车换行符，Windows平台为“rn”，Unix平台为“n”<br /><br />%d 输出日志时间点的日期或时间，默认格式为ISO8601，也可以在其后指定格式，比如：%d{yyyy MMM dd HH:mm:ss,SSS}，输出类似：2002年10月18日 22：10：28，921<br /><br />%l 输出日志事件的发生位置，包括类目名、发生的线程，以及在代码中的行数。<br /><br />[QC]是log信息的开头，可以为任意字符，一般为项目简称。<br /><br />输出的信息<br /><br />[TS] DEBUG [main] AbstractBeanFactory.getBean(189) | Returning cached instance of singleton bean 'MyAutoProxy'<br /><br />具体讲解可参照第三部分定义配置文件中的格式化日志信息。<br /><br />7 log4j.appender.R=org.apache.log4j.DailyRollingFileAppender<br /><br />此句与第3行一样。定义名为R的输出端的类型为每天产生一个日志文件。<br /><br />8 log4j.appender.R.File=D:\\Tomcat 5.5\\logs\\qc.log<br /><br />此句为定义名为R的输出端的文件名为D:\\Tomcat 5.5\\logs\\qc.log<br /><br />可以自行修改。<br /><br />9 log4j.appender.R.layout=org.apache.log4j.PatternLayout<br /><br />与第4行相同。<br /><br />10 log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n<br /><br />与第5行相同。<br /><br />12 log4j.logger.com. neusoft =DEBUG<br /><br />指定com.neusoft包下的所有类的等级为DEBUG。<br /><br />可以把com.neusoft改为自己项目所用的包名。<br /><br />13 log4j.logger.com.opensymphony.oscache=ERROR<br /><br />14 log4j.logger.net.sf.navigator=ERROR<br /><br />这两句是把这两个包下出现的错误的等级设为ERROR，如果项目中没有配置EHCache，则不需要这两句。<br /><br />15 log4j.logger.org.apache.commons=ERROR<br /><br />16 log4j.logger.org.apache.struts=WARN<br /><br />这两句是struts的包。<br /><br />17 log4j.logger.org.displaytag=ERROR<br /><br />这句是displaytag的包。（QC问题列表页面所用）<br /><br />18 log4j.logger.org.springframework=DEBUG<br /><br />此句为Spring的包。<br /><br />24 log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN<br /><br />25 log4j.logger.org.hibernate=DEBUG<br /><br />此两句是hibernate的包。<br /><br />以上这些包的设置可根据项目的实际情况而自行定制。<br /><br />三、log4j详解<br /><br />1、定义配置文件<br /><br />Log4j支持两种配置文件格式，一种是XML格式的文件，一种是Java特性文件log4j.properties（键=值）。下面将介绍使用log4j.properties文件作为配置文件的方法:<br /><br />①、配置根Logger<br /><br />Logger 负责处理日志记录的大部分操作。<br /><br />其语法为：<br /><br />log4j.rootLogger = [ level ] , appenderName, appenderName, …<br /><br />其中，level 是日志记录的优先级，分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者自定义的级别。Log4j建议只使用四个级别，优先级从高到低分别是ERROR、WARN、INFO、DEBUG。通过在这里定义的级别，您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定义了INFO级别，只有等于及高于这个级别的才进行处理，则应用程序中所有DEBUG级别的日志信息将不被打印出来。ALL:打印所有的日志，OFF：关闭所有的日志输出。 appenderName就是指定日志信息输出到哪个地方。可同时指定多个输出目的地。<br /><br />②、配置日志信息输出目的地 Appender<br /><br />Appender 负责控制日志记录操作的输出。<br /><br />其语法为：<br /><br />log4j.appender.appenderName = fully.qualified.name.of.appender.class<br /><br />log4j.appender.appenderName.option1 = value1<br /><br />…<br /><br />log4j.appender.appenderName.optionN = valueN<br /><br />这里的appenderName为在①里定义的，可任意起名。<br /><br />其中，Log4j提供的appender有以下几种：<br /><br />org.apache.log4j.ConsoleAppender（控制台），<br /><br />org.apache.log4j.FileAppender（文件），<br /><br />org.apache.log4j.DailyRollingFileAppender（每天产生一个日志文件），<br /><br />org.apache.log4j.RollingFileAppender（文件大小到达指定尺寸的时候产生一个新的文件），可通过 log4j.appender.R.MaxFileSize=100KB设置文件大小，还可通过 log4j.appender.R.MaxBackupIndex=1设置为保存一个备份文件。<br /><br />org.apache.log4j.WriterAppender（将日志信息以流格式发送到任意指定的地方）<br /><br />例如：log4j.appender.stdout=org.apache.log4j.ConsoleAppender<br /><br />定义一个名为stdout的输出目的地，ConsoleAppender为控制台。<br /><br />③、配置日志信息的格式（布局）Layout<br /><br />Layout 负责格式化Appender的输出。<br /><br />其语法为：<br /><br />log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class<br /><br />log4j.appender.appenderName.layout.option1 = value1<br /><br />…<br /><br />log4j.appender.appenderName.layout.optionN = valueN<br /><br />其中，Log4j提供的layout有以下几种：<br /><br />org.apache.log4j.HTMLLayout（以HTML表格形式布局），<br /><br />org.apache.log4j.PatternLayout（可以灵活地指定布局模式），<br /><br />org.apache.log4j.SimpleLayout（包含日志信息的级别和信息字符串），<br /><br />org.apache.log4j.TTCCLayout（包含日志产生的时间、线程、类别等等信息）<br /><br />2、格式化日志信息<br /><br />Log4J采用类似C语言中的printf函数的打印格式格式化日志信息，打印参数如下：<br /><br />%m 输出代码中指定的消息<br /><br />%p 输出优先级，即DEBUG，INFO，WARN，ERROR，FATAL<br /><br />%r 输出自应用启动到输出该log信息耗费的毫秒数<br /><br />%c 输出所属的类目，通常就是所在类的全名<br /><br />%t 输出产生该日志事件的线程名<br /><br />%n 输出一个回车换行符，Windows平台为“rn”，Unix平台为“n”<br /><br />%d 输出日志时间点的日期或时间，默认格式为ISO8601，也可以在其后指定格式，比如：%d{yyyy MMM dd HH:mm:ss,SSS}，输出类似：2002年10月18日 22：10：28，921<br /><br />%l 输出日志事件的发生位置，包括类目名、发生的线程，以及在代码中的行数。<br /><br />3、在代码中使用Log4j<br /><br />我们在需要输出日志信息的类中做如下的三个工作：<br /><br />1、导入所有需的commongs-logging类：<br /><br />import org.apache.commons.logging.Log;<br /><br />import org.apache.commons.logging.LogFactory;<br /><br />2、在自己的类中定义一个org.apache.commons.logging.Log类的私有静态类成员：<br /><br />private final Log log = LogFactory.getLog(getClass());<br /><br />LogFactory.getLog()方法的参数使用的是当前类的class。<br /><br />3、使用org.apache.commons.logging.Log类的成员方法输出日志信息：<br /><br />if (log.isDebugEnabled())<br /><br />{<br /><br />log.debug("111");<br /><br />}<br /><br />if (log.isInfoEnabled())<br /><br />{<br /><br />log.info("222");<br /><br />}<br /><br />if (log.isWarnEnabled())<br /><br />{<br /><br />log.warn("333");<br /><br />}<br /><br />if (log.isErrorEnabled())<br /><br />{<br /><br />log.error("444");<br /><br />}<br /><br />if (log.isFatalEnabled())<br /><br />{<br /><br />log.fatal("555")<br /><br />}<br /><img src ="http://www.blogjava.net/dybjsun/aggbug/170000.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dybjsun/" target="_blank">dybjsun</a> 2007-12-24 11:46 <a href="http://www.blogjava.net/dybjsun/archive/2007/12/24/170000.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>常见软件测试的技巧</title><link>http://www.blogjava.net/dybjsun/archive/2007/12/06/165716.html</link><dc:creator>dybjsun</dc:creator><author>dybjsun</author><pubDate>Thu, 06 Dec 2007 01:33:00 GMT</pubDate><guid>http://www.blogjava.net/dybjsun/archive/2007/12/06/165716.html</guid><wfw:comment>http://www.blogjava.net/dybjsun/comments/165716.html</wfw:comment><comments>http://www.blogjava.net/dybjsun/archive/2007/12/06/165716.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dybjsun/comments/commentRss/165716.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dybjsun/services/trackbacks/165716.html</trackback:ping><description><![CDATA[软件测试虽然辛苦，但是掌握了一定的技巧之后将使你事半功倍。<br /><br />　　(1) 边界测试，测试用户输入框中的数值的最大数和最小数，以及为空时的情况。<br /><br />　　(2) 非法测试，例如在输入数字的地方输入字母。<br /><br />　　(3) 跟踪测试，跟踪一条数据的流程,保证数据的正确性。<br /><br />　　(4) 在开始测试时应保证数据的正确性，然后在从系统中找出各种BUG。<br /><br />　　(5) 接口测试，程序往往在接口的地方很容易发生错误，要在此模块测试勿掉以轻心。<br /><br />　　(6) 代码重用测试，在开发过程中有些模块功能几乎相同，程序员在重用代码时可能忘记在原有代码上修改或修改不全面，而造成的错误。<br /><br />　　(7) 突发事件测试，服务器上可能发生意外情况的测试。<br /><br />　　(8) 外界环境测试，有些系统在开发时依赖于另外一个系统,当另外一个系统发生错误时, 这个系统所受到的影响的情况。<br /><br />　　(9) 在程序员刚修复Bug之后的地方,再找一找，往往程序员只修复报告出来的缺陷而不去考虑别的功能在修改时可能会重新造成错误。<br /><br />　　(10) 认真做好测试记录在做完一天的测试记录之后,第二天再根据第一天的测试记录重复测试你会发现有未修正的错误。<br /><br />　　(11) 文字测试，如果在系统中有用词不当的地方，我想这是不应该的。<br /><br />　　(12) 系统兼容测试，例如有些程序在IE6能运行正常，到IE5下不能运行。有些程序在WIN2000下能运行，而到WIN98却不能运行。像一些很特别的用户去使用系统，你很有可能发现BUG。<br /><br />　　(13) 用户的易用性测试，往往用户的需求是不断的变化的，而其中的一部份变化的原因，是有用户操作上不方便引起的。<br /><br />　　软件测试是软件开发中的重中之重，没有一点可以马虎的，在项目管理过程，我强调的是每个过程的每一个环节都要进行测试，保证系统在每个阶段可以控制。因为软件测试中考虑的问题基本上是项目管理中考虑的问题。<br /><br />　　我认为在项目管理中考虑的一些问题应该是在软件测试时有些体现，体现的内容是软件测试的一些侧重点，具体说，软件测试是事务性的，而项目管理是策略性，一些策略性的东西必须在一些事务性的事务上来实现。<br /><br />　　软件测试是一门新兴的行业，现在软件测试在我国的地位虽说还不是很高，但这几年却是逐渐转好，国内对软件测试的重视程度也慢慢高起来了。大家如果对IT有兴趣的话，不烦了解一下，相信也会有所感悟的。我就经常在网上看一些软件测试方面的文章或技巧，这对我的成长也是具大的。大家有时间不烦去一下北大测试的网站，那里面就有很多软件测试技巧和行业新闻，对我启发很多。<img src ="http://www.blogjava.net/dybjsun/aggbug/165716.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dybjsun/" target="_blank">dybjsun</a> 2007-12-06 09:33 <a href="http://www.blogjava.net/dybjsun/archive/2007/12/06/165716.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SESSION机制</title><link>http://www.blogjava.net/dybjsun/archive/2007/11/09/159237.html</link><dc:creator>dybjsun</dc:creator><author>dybjsun</author><pubDate>Thu, 08 Nov 2007 17:45:00 GMT</pubDate><guid>http://www.blogjava.net/dybjsun/archive/2007/11/09/159237.html</guid><wfw:comment>http://www.blogjava.net/dybjsun/comments/159237.html</wfw:comment><comments>http://www.blogjava.net/dybjsun/archive/2007/11/09/159237.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/dybjsun/comments/commentRss/159237.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dybjsun/services/trackbacks/159237.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 虽然session机制在web应用程序中被采用已经很长时间了，但是仍然有很多人不清楚session机制的本质，以至不能正确的应用这一技术。本文将详细讨论session的工作机制并且对在Java web application中应用session机制时常见的问题作出解答。&nbsp;&nbsp;<a href='http://www.blogjava.net/dybjsun/archive/2007/11/09/159237.html'>阅读全文</a><img src ="http://www.blogjava.net/dybjsun/aggbug/159237.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dybjsun/" target="_blank">dybjsun</a> 2007-11-09 01:45 <a href="http://www.blogjava.net/dybjsun/archive/2007/11/09/159237.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>印度人是怎样开发软件的</title><link>http://www.blogjava.net/dybjsun/archive/2006/10/18/75780.html</link><dc:creator>dybjsun</dc:creator><author>dybjsun</author><pubDate>Tue, 17 Oct 2006 17:54:00 GMT</pubDate><guid>http://www.blogjava.net/dybjsun/archive/2006/10/18/75780.html</guid><wfw:comment>http://www.blogjava.net/dybjsun/comments/75780.html</wfw:comment><comments>http://www.blogjava.net/dybjsun/archive/2006/10/18/75780.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/dybjsun/comments/commentRss/75780.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dybjsun/services/trackbacks/75780.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 我在工作中，接触到印度软件公司开发出来的软件：整个体系架构非常清晰，按照我们的要求实现了全部功能，而且相当稳定。但是打开具体的代码一看，拖沓冗长，水平不咋样。我们自己的一些程序员就有怪话了，说他们水平真低。但是！印度人能够把软件整体把握得很好，能够完成软件，并得到相当好的设计文档。而中国人在那里琢磨数据结构、算法，界面人员就还没编码就想着是Outlook式的还是VisualStudio式的界面。到最后就成为Code高手，对某些特定的开发工具精通，但是就是不能保证能够把一个软件稳当、完整的开发出来。&nbsp;&nbsp;<a href='http://www.blogjava.net/dybjsun/archive/2006/10/18/75780.html'>阅读全文</a><img src ="http://www.blogjava.net/dybjsun/aggbug/75780.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dybjsun/" target="_blank">dybjsun</a> 2006-10-18 01:54 <a href="http://www.blogjava.net/dybjsun/archive/2006/10/18/75780.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在 Linux 上安装 PostgreSQL</title><link>http://www.blogjava.net/dybjsun/archive/2006/10/13/75046.html</link><dc:creator>dybjsun</dc:creator><author>dybjsun</author><pubDate>Fri, 13 Oct 2006 11:27:00 GMT</pubDate><guid>http://www.blogjava.net/dybjsun/archive/2006/10/13/75046.html</guid><wfw:comment>http://www.blogjava.net/dybjsun/comments/75046.html</wfw:comment><comments>http://www.blogjava.net/dybjsun/archive/2006/10/13/75046.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dybjsun/comments/commentRss/75046.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dybjsun/services/trackbacks/75046.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: MySQL 是一条轻快的小海豚，但是缺少很多现代关系数据库应有的特色，例如：引用完整性，视图，触发器等。因此，如果你需要开发一个电子商务的网站，需要这些功能的话，你或许应该考虑 PostgreSQL 了。本文将通过其在 Red Hat 7.1 上安装过程，简要介绍其用法。&nbsp;&nbsp;<a href='http://www.blogjava.net/dybjsun/archive/2006/10/13/75046.html'>阅读全文</a><img src ="http://www.blogjava.net/dybjsun/aggbug/75046.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dybjsun/" target="_blank">dybjsun</a> 2006-10-13 19:27 <a href="http://www.blogjava.net/dybjsun/archive/2006/10/13/75046.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于Java栈与堆的思考</title><link>http://www.blogjava.net/dybjsun/archive/2006/10/10/74213.html</link><dc:creator>dybjsun</dc:creator><author>dybjsun</author><pubDate>Tue, 10 Oct 2006 01:24:00 GMT</pubDate><guid>http://www.blogjava.net/dybjsun/archive/2006/10/10/74213.html</guid><wfw:comment>http://www.blogjava.net/dybjsun/comments/74213.html</wfw:comment><comments>http://www.blogjava.net/dybjsun/archive/2006/10/10/74213.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/dybjsun/comments/commentRss/74213.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/dybjsun/services/trackbacks/74213.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 每一个Java应用都唯一对应一个JVM实例，每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同，Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的，但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存，在堆中分配的内存实际建立这个对象，而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。&nbsp;&nbsp;<a href='http://www.blogjava.net/dybjsun/archive/2006/10/10/74213.html'>阅读全文</a><img src ="http://www.blogjava.net/dybjsun/aggbug/74213.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/dybjsun/" target="_blank">dybjsun</a> 2006-10-10 09:24 <a href="http://www.blogjava.net/dybjsun/archive/2006/10/10/74213.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>