??xml version="1.0" encoding="utf-8" standalone="yes"?>
radic 发表?2006-12-15 12:24:05
作?Radic 来源:sun
评论?5 点击?592 投票d?6 投票Mh?2
关键?Java;安全~码 摘要:
本文是来自Sun官方站点的一关于如何编写安全的Java代码的指?开发者在~写一般代码时Q可以参照本文的指南
• 静态字D?br />• 羃?yu)作用?br />• 公共方法和字段
• 保护包
• equalsҎ(gu)
• 如果可能对象不可改变
• 不要返回指向包含敏感数据的内部数组的引?br />• 不要直接存储用h供的数组
• 序列化
• 原生函?br />• 清除敏感信?/span>
静态字D?/b>
• 避免用非final的公共静态变?br />应尽可能地避免用非final公共静态变量,因ؓ无法判断代码有无权限改变q些变量倹{?br />• 一般地Q应谨慎使用易变的静态状态,因ؓq可能导致设想中怺独立的子pȝ之间发生不可预知的交互?br />
~小作用?/b>
作ؓ一个惯例,可能羃?yu)方法和字段的作用域。检查包讉K权限的成员能否改成私有的Q保护类型的成员可否Ҏ(gu)包访问权限的或者私有的Q等{?br />
公共Ҏ(gu)/字段
避免使用公共变量Q而是使用讉K器方法访问这些变量。用q种方式Q如果需要,可能增加集中安全控制?br />对于M公共Ҏ(gu)Q如果它们能够访问或修改M敏感内部状态,务必使它们包含安全控制?br />参考如下代码段Q该代码D中不可信Q代码可能讄TimeZone的|private static TimeZone defaultZone = null;
public static synchronized void setDefault(TimeZone zone)
{
defaultZone = zone;
}
保护?/b>
有时需要在全局防止包被不可信Q代码讉KQ本节描qC一些防护技术:
• 防止包注入Q如果不可信M码想要访问类的包保护成员Q可以尝试在被攻ȝ包内定义自己的新cȝ以获取这些成员的讉K权。防止这cLȝ方式有两U:
1. 通过向java.security.properties文g中加入如下文字防止包内被注入恶意cR?br /> ...
package.definition=Package#1 [,Package#2,...,Package#n]
...
q会D当试囑֜包内定义新类时类装蝲器的defineClassҎ(gu)会抛出异常,除非赋予代码一下权限:...
RuntimePermission("defineClassInPackage."+package)
...
2. 另一U方式是通过包内的cd入到装的Jar文g里?br />Q参看http://java.sun.com/j2se/sdk/1.2/docs/guide/extensions/spec.htmlQ?br /> 通过使用q种技巧,代码无法获得扩展包的权限Q因此也无须修改java.security.properties文g?br />• 防止包讉KQ通过限制包访问ƈ仅赋予特定代码访问权限防止不可信M码对包成员的讉K。通过向java.security.properties文g中加入如下文字可以达到这一目的Q?br /> ...
package.access=Package#1 [,Package#2,...,Package#n]
...
q会D当试囑֜包内定义新类时类装蝲器的defineClassҎ(gu)会抛出异常,除非赋予代码一下权限:...
RuntimePermission("defineClassInPackage."+package)
...
如果可能使对象不可改?/b>
如果可能Q对象不可改变。如果不可能Q得它们可以被克隆q返回一个副本。如果返回的对象是数l、向量或哈希表等Q牢记这些对象不能被改变Q调用者修改这些对象的内容可能D安全漏洞。此外,因ؓ不用上锁Q不可改变性能够提高ƈ发性。参考Clear sensitive information了解该惯例的例外情况?br />
不要q回指向包含敏感数据的内部数l的引用
该惯例仅仅是不可变惯例的变型Q在q儿提出是因为常常在q里犯错。即使数l中包含不可变的对象Q如字符ԌQ也要返回一个副本这栯用者不能修Ҏ(gu)l中的字W串。不要传回一个数l,而是数组的拷贝?br />
不要直接在用h供的数组里存?/b>
该惯例仅仅是不可变惯例的另一个变型。用对象数l的构造器和方法,比如说PubicKey数组Q应当在数l存储到内部之前克隆数组Q而不是直接将数组引用赋给同样cd的内部变量。缺这个警惕,用户对外部数l做得Q何变动(在用讨Z的构造器创徏对象后)可能意外地更改对象的内部状态,即该对象可能是无法改变?br />
序列?/b>
当对对象序列化时Q直到它被反序列化,它不在Javaq行时环境的控制之下Q因此也不在Javaq_提供的安全控制范围内?br />在实现Serializable时务必将以下事宜牢记在心Q?br />• transient
在包含系l资源的直接句柄和相对地址I间信息的字D前使用transient关键字?如果资源Q如文g句柄Q不被声明ؓtransientQ该对象在序列化状态下可能会被修改Q从而得被反序列化后获取对资源的不当访问?br />
• 特定类的序列化/反序列化Ҏ(gu)
Z保反序列化对象不包含违反一些不变量集合的状态,cd该定义自q反序列化Ҏ(gu)q用ObjectInputValidation接口验证q些变量?br />
如果一个类定义了自q序列化方法,它就不能向Q何DataInput/DataOuputҎ(gu)传递内部数l。所有的DataInput/DataOuputҎ(gu)都能被重写。注意默认序列化不会向DataInput/DataOuput字节数组Ҏ(gu)暴露U有字节数组字段?br />
如果Serializablecȝ接向DataOutput(write(byte [] b))Ҏ(gu)传递了一个私有数l,那么黑客可以创徏ObjectOutputStream的子cdƈ覆盖write(byte [] b)Ҏ(gu)Q这样他可以讉Kq修改私有数l。下面示例说明了q个问题?br />你的c? public class YourClass implements Serializable {
private byte [] internalArray;
....
private synchronized void writeObject(ObjectOutputStream stream) {
...
stream.write(internalArray);
...
}
}
黑客代码 public class HackerObjectOutputStream extends ObjectOutputStream{
public void write (byte [] b) {
Modify b
}
}
...
YourClass yc = new YourClass();
...
HackerObjectOutputStream hoos = new HackerObjectOutputStream();
hoos.writeObject(yc);
• 字节流加密
保护虚拟机外的字节流的另一方式是对序列化包产生的流q行加密。字节流加密防止解码或读取被序列化的对象的私有状态。如果决定加密,应该理好密钥,密钥的存攑֜点以及将密钥交付l反序列化程序的方式{?br />
• 需要提防的其他事宜
如果不可信Q代码无法创徏对象Q务必确保不可信M码也不能反序列化对象。切记对对象反序列化是创建对象的另一途径?br />比如_如果一个applet创徏了一个frameQ在该frame上创Z警告标签。如果该frame被另一应用E序序列化ƈ被一个applet反序列化Q务必该frame出现时带有同一个警告标{?br />
原生Ҏ(gu)
应从以下几个斚w查原生方法:
• 它们返回什?br />• 它们需要什么参?br />• 它们是否绕q了安全?br />• 它们是否是公共的,U有的等
• 它们是否包含能l过包边界的Ҏ(gu)调用Q从而绕q包保护
清除敏感信息
当保存敏感信息时Q如机密Q尽量保存在如数l这L可变数据cd中,而不是保存在字符串这L不可变对象中Q这样得敏感信息可以尽早显式地被清除。不要指望Javaq_的自动垃圑֛收来做这U清除,因ؓ回收器可能不会清除这D内存,或者很久后才会回收。尽早清除信息得来自虚拟机外部的堆查攻d得困难?br />
]]>
数据库同步复制功能的讄都在mysql的设|文件中体现。mysql的配|文Ӟ一般是my.cnfQ,在unix环境下在/etc/mysql/my.cnf 或者在mysql用户的home目录下的my.cnf?
window环境中,如果c:根目录下有my.cnf文g则取该配|文件。当q行mysql的winmysqladmin.exe工具时候,该工具会把c:根目录下的my.cnf 命名为mycnf.bak。ƈ在winnt目录下创建my.ini。mysql服务器启动时候会读该配置文g。所以可以把my.cnf中的内容拯到my.ini文g中,用my.ini文g作ؓmysql服务器的配置文g?
讄Ҏ(gu)Q?
讄范例环境Q?
操作pȝQwindow2000 professional
mysqlQ?.0.4-beta-max-nt-log
A ip:10.10.10.22
B ip:10.10.10.53
A:讄
1.增加一个用h为同步的用户帐号Q?
|
2.增加一个数据库作ؓ同步数据库:
|
B:讄
1.增加一个用h为同步的用户帐号Q?
|
2.增加一个数据库作ؓ同步数据库:
|
M模式QA->B
A为master
修改A mysql的my.ini文g。在mysqld配置中加入下面配置Q?
server-id=1log-bin#讄需要记录log 可以讄log-bin=c:mysqlbakmysqllog 讄日志文g的目录,#其中mysqllog是日志文件的名称Qmysql徏立不同扩展名Q文件名为mysqllog的几个日志文件。binlog-do-db=backup #指定需要日志的数据?
重v数据库服务?
用show master status 命o看日志情c?
B为slave
修改B mysql的my.ini文g。在mysqld配置中加入下面配置Q?
|
#同步用户帐号
|
预设重试间隔60Ureplicate-do-db=backup 告诉slave只做backup数据库的更新
重v数据?
用show slave status看同步配|情c?
注意Q由于设|了slave的配|信息,mysql在数据库目录下生成master.infoQ所以如有要修改相关slave的配|要先删除该文g。否则修改的配置不能生效?
双机互备模式?
如果在A加入slave讄Q在B加入master讄Q则可以做B->A的同步?
在A的配|文件中 mysqld 配置加入以下设|:
|
在B的配|文件中 mysqld 配置加入以下设|:
|
注意Q当有错误生时*.err日志文g。同步的U程退出,当纠正错误后要让同步机制q行工作Q运行slave start
重vAB机器Q则可以实现双向的热备?
试Q?
向B(ti)扚w插入大数据量表AAQ?872000Q条QA数据库每U钟可以更新2500条数据?
JpetStore 4.0是ibatis的最新示例程序,ZStruts MVC框架Q注Q非传统Struts开发模式)Q以ibatis作ؓ持久化层。该CZE序设计优雅Q层ơ清晎ͼ可以学习以及作ؓ一个高效率的编E模型参考。本文是在其基础上,采用Spring对其中间层(业务层)q行攚w。开发量q一步减,同时又拥有了Spring的一些好处?/p>
1. 前言
JpetStore 4.0是ibatis的最新示例程序。ibatis是开源的持久层品,包含SQL Maps 2.0 ?Data Access Objects 2.0 框架。JpetStoreCZE序很好的展CZ如何利用ibatis来开发一个典型的J2EE web应用E序。JpetStore有如下特点:
以下是本文用到的关键技术介l,本文假设(zhn)已l对StrutsQSpringFramewokQibatis有一定的了解Q如果不是,请首先查阅附录中的参考资料?/p>
2.1. 背景
最初是Sun公司的J2EE petstoreQ其最主要目的是用于学习J2EEQ但是其~点也很明显Q就是过度设计了。接着Oracle用J2EE petstore来比较各应用服务器的性能。微软推ZZ.Netq_?Pet shopQ用于竞争J2EE petstore。而JpetStore则是l过改良的基于struts的轻便框架J2EE web应用E序Q相比来_JpetStore设计和架构更优良Q各层定义清晎ͼ使用了很多最?jng)_践和模式Q避免了很多"反模?Q如使用存储q程Q在java代码中嵌入SQL语句Q把HTML存储在数据库中等{。最新版本是JpetStore 4.0?/p>
2.2. JpetStore开发运行环境的建立
1、开发环?/p>
2、Eclipse插g
3、示例源E序
? 是JPetStore架构图,更详l的内容请参见JPetStore的白皮书。参照这个架构图Q让我们E微剖析一下源代码Q得出JpetStore 4.0的具体实现图Q见?Q,思\一下子p然开朗了。前a中提到的非传l的struts开发模式,关键在struts Actioncdform beancM?/p>
struts Actioncd有一个:BeanAction。没错,实是一个!与传l的struts~程方式很不同。再仔细研究BeanActionc,发现它其实是一个通用c,利用反射原理Q根据URL来决定调用formbean的哪个方法。BeanAction大大化了struts的编E模式,降低了对struts的依赖(与struts以及WEB容器有关的几个类都放在com.ibatis.struts包下Q其它的c都可以直接复用Q。利用这U模式,我们会很Ҏ(gu)的把它移植到新的框架如JSFQspring?/p>
q样重心?yu)p{Udform bean上了Q它已经不是普通意义上的form bean了。查看源代码Q可以看到它不仅仅有数据和校?重置Ҏ(gu)Q而且已经h了行为,从这个意义上来说Q它更像一个BO(Business Object)。这是前文讲到的,BeanActioncd用反原理,Ҏ(gu)URL来决定调用form bean的哪个方法(行ؓQ。form bean的这些方法的{很简单,例如Q?/p>
|
Ҏ(gu)的返回值直接就是字W串Q对应的是forward的名Uͼ而不再是ActionForward对象Q创建ActionForward对象的Q务已l由BeanActioncM劳了?/p>
另外Q程序还提供了ActionContext工具c,该工L装了request 、response、form parameters、request attributes、session attributes?application attributes中的数据存取操作Q简单而线E安全,form beancM用该工具cd以进一步从表现层框架解耦?/p>
在这里需要特别指出的是,BeanActioncL对struts扩展的一个有益尝试,虽然提供了非常好的应用开发模式,但是它还非常斎ͼ一直在发展中?/p>
2.4. 代码剖析
下面p我们开始进一步分析JpetStore4.0的源代码Qؓ下面的改造铺路?/p>
Form beancM于com.ibatis.jpetstore.presentation包下Q命名规则ؓ***Bean。Form beancd部承于BaseBeanc,而BaseBeancd际承于ActionFormQ因此,Form beancd是Struts?ActionFormQForm beancȝ属性数据就由struts框架自动填充。而实际上QJpetStore4.0扩展了struts中ActionForm的应用: Form beanc还h行ؓQ更像一个BO,其行为(Ҏ(gu)Q由BeanActionҎ(gu)配置Qstruts-config.xmlQ的URL来调用。虽然如此,我们q是把Form beancd位于表现层?/p>
Struts-config.xml的配|里?U映方式,来告诉BeanAction把控制{到哪个form bean对象的哪个方法来处理?/p>
以这个请求连接ؓ例http://localhost/jpetstore4/shop/viewOrder.do
1. URL Pattern
|
此种方式表示Q控制将被{发到"orderBean"q个form bean对象 ?viewOrder"Ҏ(gu)Q行为)来处理。方法名?path"参数的以"/"分隔的最后一部分?/p>
2. Method Parameter
|
此种方式表示Q控制将被{发到"orderBean"q个form bean对象?viewOrder"Ҏ(gu)Q行为)来处理。配|中?parameter"参数表示form beancM的方法?parameter"参数优先?path"参数?/p>
3. No Method call
|
此种方式表示Qform bean上没有Q何方法被调用。如果存?name"属性,则struts把表单参数等数据填充到form bean对象后,把控制{发到"success"。否则,如果name为空Q则直接转发控制?success"?/p>
q就相当于struts内置的org.apache.struts.actions.ForwardAction的功?/p>
|
剩下的部分就比较单了Q请看具体的源代码,非常清晰?/p>
2.5. 需要改造的地方
JpetStore4.0的关键就在struts Actioncdform beancMQ这也是其精华之一Q虽然该实现方式是试验性,待扩充和验证Q,在此ơ改造中我们要保留下来,x制层一点不变,表现层获取相应业务类的方式变了(要加载spring环境Q,其它保持不变。要特别x的改动是业务层和持久层,q运的是JpetStore4.0设计非常好,需要改动的地方非常,而且由模式可循,如下Q?/p>
1. 业务层和数据层用Spring BeanFactory机制理?/p>
2. 业务层的事务由spring 的aop通过声明来完成?/p>
3. 表现层(form beanQ获取业务类的方法改p定义工厂cL实现Q加载spring环境Q?/p>
其中U色部分是要增加的部分,蓝色部分是要修改的部分。下面就让我们逐一剖析?/p>
3.2. Spring Context的加?/strong>
Z在Struts中加载Spring ContextQ一般会在struts-config.xml的最后添加如下部分:
|
Spring在设计时充分考虑C与Struts的协同工作,通过内置的Struts Plug-in在两者之间提供了良好的结合点。但是,因ؓ在这里我们一点也不改动JPetStore的控制层(q是JpetStore4.0的精华之一)Q所以本文不准备采用此方式来加蝲ApplicationContext。我们利用的是spring framework 的BeanFactory机制,采用自定义的工具c(bean工厂c)来加载spring的配|文Ӟ从中可以看出Spring有多灉|Q它提供了各U不同的方式来用其不同的部?层次Q?zhn)只需要用你想用的Q不需要的部分可以不用?/p>
具体的来_是在com.ibatis.spring包下创徏CustomBeanFactoryc,spring的配|文件applicationContext.xml也放在这个目录下。以下就是该cȝ全部代码Q很单:
|
实际上就是封装了Spring 的XMLBeanFactory而已Qƈ且Spring的配|文件只需要加载一ơ,以后可以直接用CustomBeanFactory.getBean("someBean")来获得需要的对象?例如someBean)Q而不需要知道具体的cRCustomBeanFactorycȝ于{耦合1}的解耦?/p>
CustomBeanFactorycd本文中只用于表现层的form bean对象获得servicecȝ对象Q因为我们没有把form bean对象配置在applicationContext.xml中。但是,Z么不把表现层的form beancM配置h呢,q样q不着qCustomBeanFactory个类了,Spring会帮助我们创建需要的一切?问题的答案就在于form beancLstruts的ActionFormc!如果大家熟?zhn)strutsQ就会知道ActionFormcLstruts自动创徏的:在一ơ请求中Qstruts判断Q如果ActionForm实例不存在,创Z个ActionForm对象Q把客户提交的表单数据保存到ActionForm对象中。因此formbeancȝ对象׃能由spring来创建,但是servicecM及数据层的DAOcd以,所以只有他们在spring中配|?/p>
所以,很自然的Q我们就创徏了CustomBeanFactoryc,在表现层来衔接struts和spring。就q么单,实现了另一U方式的{耦合一}的解耦?/p>
3.3. 表现?/strong>
?面分析到Qstruts和spring是在表现层衔接v来的Q那么表现层p做稍微的更改Q即所需要的servicecȝ对象创徏上。以表现层的AccountBeancMؓ例:
原来的源代码如下
|
攚w后的源代码如下
|
其他的几个presentationcM同样方式攚w。这P表现层就完成了。关于表现层的其它部分如JSP{一概不动。也许?zhn)会说Q没有看Z么特别之处的好处啊?你还是额外实C一个工厂类。别着急,帷幕刚刚开启,spring是在表现层引入,但?zhn)发没发现Q?/p>
3.4. 持久?/strong>
在讨Z务层之前Q我们先看一下持久层Q如下图所C:
在上文中Q我们把iface包下的DAO接口归ؓ业务层,在这里不需要做修改。ibatis的sql配置文g也不需要改。要改的是DAO实现c,q在spring的配|文件中配置h?/p>
1、修改基c?/p>
所有的DAO实现c都l承于BaseSqlMapDaocR修改BaseSqlMapDaocd下:
|
使BaseSqlMapDaocL为承于Spring提供的SqlMapClientDaoSupportc,q定义了一个保护属性smcTemplateQ其cd为SqlMapClientTemplate。关于SqlMapClientTemplatecȝ详细说明请参照附录中?Spring中文参考手?
2、修改DAO实现c?/p>
所有的DAO实现c还是承于BaseSqlMapDaoc,实现相应的DAO接口Q但其相应的DAO操作委托SqlMapClientTemplate来执行,以AccountSqlMapDaocMؓ例,部分代码如下Q?/p>
|
p么简单,所有函数的{都是一LQ只需要查找替换就可以了!
3、除d厂类以及相应的配|文?/p>
除去DaoConfig.javaq个DAO工厂cd相应的配|文件dao.xmlQ因为DAO的获取现在要用spring来管理?/p>
4、DAO在Spring中的配置QapplicationContext.xmlQ?/p>
|
具体的语法请参照附录中的"Spring中文参考手?。在q里只简单解释一下:
1. 我们首先创徏一个数据源dataSourceQ在q里配置的是hsqldb数据库。如果是ORACLE数据库,driverClassName的值是"oracle.jdbc.driver.OracleDriver"QURL的值类g"jdbc:oracle:thin:@wugfMobile:1521:cdcf"。数据源现在由spring来管理,那么现在我们可以去掉properties目录下database.propertiesq个配置文g了;q有不要忘记修改sql-map-config.xmlQ去?lt;properties resource="properties/database.properties"/>对它的引用?/p>
2. sqlMapClient节点。这个是针对ibatis SqlMap的SqlMapClientFactoryBean配置。实际上配置了一个sqlMapClient的创建工厂类。configLocation属性配|了ibatis映射文g的名U。dataSource属性指向了使用的数据源Q这h有用sqlMapClient的DAO都默认用了该数据源Q除非在DAO的配|中另外昑ּ指定?/p>
3. TransactionManager节点。定义了事务Q用的是DataSourceTransactionManager?/p>
4. 下面可以定义DAO节点了,如AccountDaoQ它的实现类是com.ibatis.jpetstore.persistence.sqlmapdao.AccountSqlMapDaoQ用的SQL配置从sqlMapClient中读取,数据库连接没有特别列出,那么是默认使用sqlMapClient配置的数据源datasource?/p>
q样Q我们就把持久层攚w完了,其他的DAO配置cM于AccountDao。怎么P单吧。这ơ有接口了:Q?AccountDao接口Q?gt;AccountSqlMapDao实现?/p>
3.5. 业务?/strong>
业务层的位置以及相关c,如下图所C:
在这个例子中只有3个业务类Q我们以OrderServicecMؓ例来攚w,q个cL最复杂的,其中涉及了事务?/p>
1、在ApplicationContext配置文g中增加bean的配|:
|
定义了一个OrderServiceQ还是很Ҏ(gu)懂的。ؓ了简单v见,使用了嵌套beanQ其实现cLcom.ibatis.jpetstore.service.OrderServiceQ分别引用了ItemDaoQOrderDaoQSequenceDao。该bean的insert*实现了事务管?AOP方式)。TransactionProxyFactoryBean自动创徏一个事务advisorQ?该advisor包括一个基于事务属性的pointcut,因此只有事务性的Ҏ(gu)被拦截?/p>
2、业务类的修?/p>
以OrderServiceZQ?/p>
|
U色部分Z攚w分。Spring采用的是Type2的设|依赖注入,所以我们只需要定义属性和相应的设值函数就可以了,ItemDaoQOrderDaoQSequenceDao的值由spring在运行期间注入。构造函数就可以为空了,另外也不需要自q写代码处理事务了Q事务在配置中声明)QdaoManager.startTransaction();{与事务相关的语句也可以L了。和原来的代码比较一下,是不是处理精了很多!可以更关注业务的实现?/p>
4. l束?/strong>
ibatis是一个功能强大实用的SQL Map工具Q可以直接控制SQL,为系l设计提供了更大的自q间。其提供的最新示例程序JpetStore 4.0,设计优雅Q应用了q今为止很多最?jng)_践和设计模式Q非帔R于学习以及在此基础上创量的J2EE WEB应用E序。JpetStore 4.0是基于struts的,本文在此基础上,最大程度保持了原有设计的精华以及最的代码改动量,在业务层和持久化层引入了Spring。在(zhn)阅M本文以及攚w后的源代码后,会深切的感受到Spring带来的种U好处:自然的面向接口的~程Q业务对象的依赖注入Q一致的数据存取框架和声明式的事务处理,l一的配|文件…更重要的是Spring既是全面的又是模块化的,Spring有分层的体系l构Q这意味着(zhn)能选择仅仅使用它Q何一个独立的部分Q就像本文,而它的架构又是内部一致?/p>
获取语言request.getHeader("Accept-Language");
详细信息可以再分?...
在本文章中Q你会学习到如何利用 Lucene 实现高搜烦功能以及如何利用 Lucene 来创?Web 搜烦应用E序。通过q些学习Q你可以利?Lucene 来创q搜烦应用E序?/p>
通常一?Web 搜烦引擎的架构分为前端和后端两部分,像图一中所C。在前端程中,用户在搜索引擎提供的界面中输入要搜烦的关键词Q这里提到的用户界面一般是一个带有输入框?Web 面Q然后应用程序将搜烦的关键词解析成搜索引擎可以理解的形式Qƈ在烦引文件上q行搜烦操作。在排序后,搜烦引擎q回搜烦l果l用戗在后端程中,|络爬虫或者机器h从因特网上获?Web 面Q然后烦引子pȝ解析q些 Web 面q存入烦引文件中。如果你惛_?Lucene 来创Z?Web 搜烦应用E序Q那么它的架构也和上面所描述的类|如图一中所C?/p>
Figure 1. Web 搜烦引擎架构
Lucene 支持多种形式的高U搜索,我们在这一部分中会q行探讨Q然后我会?Lucene ?API 来演C如何实现这些高U搜索功能?/p>
大多数的搜烦引擎都会提供布尔操作W让用户可以l合查询Q典型的布尔操作W有 AND, OR, NOT。Lucene 支持 5 U布?yu)操作符Q分别是 AND, OR, NOT, ?+), ?-)。接下来我会讲述每个操作W的用法?
接下来我们看一下如何利?Lucene 提供?API 来实现布?yu)查询?a >清单1 昄了如果利用布?yu)操作符q行查询的过E?/p>
清单1Q用布?yu)操作?/b>
//Test boolean operator public void testOperator(String indexDirectory) throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); String[] searchWords = {"Java AND Lucene", "Java NOT Lucene", "Java OR Lucene", "+Java +Lucene", "+Java -Lucene"}; Analyzer language = new StandardAnalyzer(); Query query; for(int i = 0; i < searchWords.length; i++){ query = QueryParser.parse(searchWords[i], "title", language); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results for query " + searchWords[i]); } } |
Lucene 支持域搜索,你可以指定一ơ查询是在哪些域(Field)上进行。例如,如果索引的文档包含两个域Q?code>Title ?Content
Q你可以用查?“Title: Lucene AND Content: Java?来返回所有在 Title 域上包含 Lucene q且?Content 域上包含 Java 的文档?a >清单 2 昄了如何利?Lucene ?API 来实现域搜烦?
//Test field search public void testFieldSearch(String indexDirectory) throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); String searchWords = "title:Lucene AND content:Java"; Analyzer language = new StandardAnalyzer(); Query query = QueryParser.parse(searchWords, "title", language); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results for query " + searchWords); } |
Lucene 支持两种通配W:问号Q?Q和星号Q?Q。你可以使用问号Q?Q来q行单字W的通配W查询,或者利用星P*Q进行多字符的通配W查询。例如,如果你想搜烦 tiny 或?tonyQ你可以用查询语?“t?ny”;如果你想查询 Teach, Teacher ?TeachingQ你可以用查询语?“Teach*”?a >清单3 昄了通配W查询的q程?
//Test wildcard search public void testWildcardSearch(String indexDirectory)throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); String[] searchWords = {"tex*", "tex?", "?ex*"}; Query query; for(int i = 0; i < searchWords.length; i++){ query = new WildcardQuery(new Term("title",searchWords[i])); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results for query " + searchWords[i]); } } |
Lucene 提供的模p查询基于编辑距ȝ?Edit distance algorithm)。你可以在搜索词的尾部加上字W?~ 来进行模p查询。例如,查询语句 “think~?q回所有包含和 think cM的关键词的文?a >清单 4 昄了如果利?Lucene ?API q行模糊查询的代码?
//Test fuzzy search public void testFuzzySearch(String indexDirectory)throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); String[] searchWords = {"text", "funny"}; Query query; for(int i = 0; i < searchWords.length; i++){ query = new FuzzyQuery(new Term("title",searchWords[i])); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results for query " + searchWords[i]); } } |
范围搜烦匚w某个域上的值在一定范围的文。例如,查询 “age:[18 TO 35]?q回所?age 域上的值在 18 ?35 之间的文?a >清单5昄了利?Lucene ?API q行q回搜烦的过E?
//Test range search public void testRangeSearch(String indexDirectory)throws Exception{ Directory dir = FSDirectory.getDirectory(indexDirectory,false); IndexSearcher indexSearcher = new IndexSearcher(dir); Term begin = new Term("birthDay","20000101"); Term end = new Term("birthDay","20060606"); Query query = new RangeQuery(begin,end,true); Hits results = indexSearcher.search(query); System.out.println(results.length() + "search results is returned"); } |
![]() ![]() |
![]()
|
接下来我们开发一?Web 应用E序利用 Lucene 来检索存攑֜文g服务器上?HTML 文。在开始之前,需要准备如下环境:
q个例子使用 Eclipse q行 Web 应用E序的开发,最l这?Web 应用E序跑在 Tomcat 5.0 上面。在准备好开发所必需的环境之后,我们接下来进?Web 应用E序的开发?
在我们的设计中,把该pȝ分成如下四个子系l:
?
昄了我们设计的详细信息Q我们将用户接口子系l放?webContent 目录下面。你会看C个名?search.jsp 的页面在q个文g多w面。请求管理子pȝ在包 sample.dw.paper.lucene.servlet
下面Q类 SearchController
负责功能的实现。搜索子pȝ攑֜?sample.dw.paper.lucene.search
当中Q它包含了两个类Q?code>SearchManager ?SearchResultBean
Q第一个类用来实现搜烦功能Q第二个cȝ来描q搜索结果的l构。烦引子pȝ攑֜?sample.dw.paper.lucene.index
当中。类 IndexManager
负责?HTML 文g创徏索引。该子系l利用包 sample.dw.paper.lucene.util
里面的类 HTMLDocParser
提供的方?getTitle
?getContent
来对 HTML 面q行解析?
在分析了pȝ的架构设计之后,我们接下来看pȝ实现的详l信息?
q个JSP的第二部分负责显C搜索结果给用户Q如?所C:
SearchController
?servlet 用来实现该子pȝ?a >清单Q?/font>l出了这个类的源代码?
package sample.dw.paper.lucene.servlet; import java.io.IOException; import java.util.List; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import sample.dw.paper.lucene.search.SearchManager; /** * This servlet is used to deal with the search request * and return the search results to the client */ public class SearchController extends HttpServlet{ private static final long serialVersionUID = 1L; public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ String searchWord = request.getParameter("searchWord"); SearchManager searchManager = new SearchManager(searchWord); List searchResult = null; searchResult = searchManager.search(); RequestDispatcher dispatcher = request.getRequestDispatcher("search.jsp"); request.setAttribute("searchResult",searchResult); dispatcher.forward(request, response); } public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ doPost(request, response); } } |
?a >清单6中,doPost
Ҏ(gu)从客L获取搜烦词ƈ创徏c?SearchManager
的一个实例,其中c?SearchManager
在搜索子pȝ中进行了定义。然后,SearchManager
的方?search 会被调用。最后搜索结果被q回到客L?
SearchResultBean
。第一个类用来实现搜烦功能Q第二个cL个JavaBeanQ用来描q搜索结果的l构?a >清单7l出了类 SearchManager
的源代码?
package sample.dw.paper.lucene.search; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.Hits; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import sample.dw.paper.lucene.index.IndexManager; /** * This class is used to search the * Lucene index and return search results */ public class SearchManager { private String searchWord; private IndexManager indexManager; private Analyzer analyzer; public SearchManager(String searchWord){ this.searchWord = searchWord; this.indexManager = new IndexManager(); this.analyzer = new StandardAnalyzer(); } /** * do search */ public List search(){ List searchResult = new ArrayList(); if(false == indexManager.ifIndexExist()){ try { if(false == indexManager.createIndex()){ return searchResult; } } catch (IOException e) { e.printStackTrace(); return searchResult; } } IndexSearcher indexSearcher = null; try{ indexSearcher = new IndexSearcher(indexManager.getIndexDir()); }catch(IOException ioe){ ioe.printStackTrace(); } QueryParser queryParser = new QueryParser("content",analyzer); Query query = null; try { query = queryParser.parse(searchWord); } catch (ParseException e) { e.printStackTrace(); } if(null != query >> null != indexSearcher){ try { Hits hits = indexSearcher.search(query); for(int i = 0; i < hits.length(); i ++){ SearchResultBean resultBean = new SearchResultBean(); resultBean.setHtmlPath(hits.doc(i).get("path")); resultBean.setHtmlTitle(hits.doc(i).get("title")); searchResult.add(resultBean); } } catch (IOException e) { e.printStackTrace(); } } return searchResult; } } |
?a >清单7中,注意到在q个c里面有三个U有属性。第一个是 searchWord
Q代表了来自客户端的搜烦词。第二个?indexManager
Q代表了在烦引子pȝ中定义的c?IndexManager
的一个实例。第三个?analyzer
Q代表了用来解析搜烦词的解析器。现在我们把注意力放在方?search
上面。这个方法首先检查烦引文件是否已l存在,如果已经存在Q那么就在已l存在的索引上进行检索,如果不存在,那么首先调用c?IndexManager
提供的方法来创徏索引Q然后在新创建的索引上进行检索。搜索结果返回后Q这个方法从搜烦l果中提取出需要的属性ƈ为每个搜索结果生成类 SearchResultBean
的一个实例。最后这?SearchResultBean
的实例被攑ֈ一个列表里面ƈq回l请求管理器?/p>
在类 SearchResultBean
中,含有两个属性,分别?htmlPath
?htmlTitle
Q以及这个两个属性的 get ?set Ҏ(gu)。这也意味着我们的搜索结果包含两个属性:htmlPath
?htmlTitle
Q其?htmlPath
代表?HTML 文g的\径,htmlTitle
代表?HTML 文g的标题?
IndexManager
用来实现q个子系l?a >清单8 l出了这个类的源代码?
package sample.dw.paper.lucene.index; import java.io.File; import java.io.IOException; import java.io.Reader; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import sample.dw.paper.lucene.util.HTMLDocParser; /** * This class is used to create an index for HTML files * */ public class IndexManager { //the directory that stores HTML files private final String dataDir = "c:\\dataDir"; //the directory that is used to store a Lucene index private final String indexDir = "c:\\indexDir"; /** * create index */ public boolean createIndex() throws IOException{ if(true == ifIndexExist()){ return true; } File dir = new File(dataDir); if(!dir.exists()){ return false; } File[] htmls = dir.listFiles(); Directory fsDirectory = FSDirectory.getDirectory(indexDir, true); Analyzer analyzer = new StandardAnalyzer(); IndexWriter indexWriter = new IndexWriter(fsDirectory, analyzer, true); for(int i = 0; i < htmls.length; i++){ String htmlPath = htmls[i].getAbsolutePath(); if(htmlPath.endsWith(".html") || htmlPath.endsWith(".htm")){ addDocument(htmlPath, indexWriter); } } indexWriter.optimize(); indexWriter.close(); return true; } /** * Add one document to the Lucene index */ public void addDocument(String htmlPath, IndexWriter indexWriter){ HTMLDocParser htmlParser = new HTMLDocParser(htmlPath); String path = htmlParser.getPath(); String title = htmlParser.getTitle(); Reader content = htmlParser.getContent(); Document document = new Document(); document.add(new Field("path",path,Field.Store.YES,Field.Index.NO)); document.add(new Field("title",title,Field.Store.YES,Field.Index.TOKENIZED)); document.add(new Field("content",content)); try { indexWriter.addDocument(document); } catch (IOException e) { e.printStackTrace(); } } /** * judge if the index exists already */ public boolean ifIndexExist(){ File directory = new File(indexDir); if(0 < directory.listFiles().length){ return true; }else{ return false; } } public String getDataDir(){ return this.dataDir; } public String getIndexDir(){ return this.indexDir; } } |
q个cd含两个私有属性,分别?dataDir
?indexDir
?code>dataDir 代表存放{待q行索引?HTML 面的\径,indexDir
代表了存?Lucene 索引文g的\径。类 IndexManager
提供了三个方法,分别?createIndex
, addDocument
?ifIndexExist
。如果烦引不存在的话Q你可以使用Ҏ(gu) createIndex
dZ个新的烦引,用方?addDocument
d一个烦引上d文。在我们的场景中Q一个文就是一?HTML 面。方?addDocument
会调用由c?HTMLDocParser
提供的方法对 HTML 文q行解析。你可以使用最后一个方?ifIndexExist
来判?Lucene 的烦引是否已l存在?
现在我们来看一下放在包 sample.dw.paper.lucene.util
里面的类 HTMLDocParser
。这个类用来?HTML 文g中提取出文本信息。这个类包含三个Ҏ(gu)Q分别是 getContent
Q?code>getTitle ?getPath
。第一个方法返回去除了 HTML 标记的文本内容,W二个方法返?HTML 文g的标题,最后一个方法返?HTML 文g的\径?a >清单9 l出了这个类的源代码?
package sample.dw.paper.lucene.util; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import org.apache.lucene.demo.html.HTMLParser; public class HTMLDocParser { private String htmlPath; private HTMLParser htmlParser; public HTMLDocParser(String htmlPath){ this.htmlPath = htmlPath; initHtmlParser(); } private void initHtmlParser(){ InputStream inputStream = null; try { inputStream = new FileInputStream(htmlPath); } catch (FileNotFoundException e) { e.printStackTrace(); } if(null != inputStream){ try { htmlParser = new HTMLParser(new InputStreamReader(inputStream, "utf-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } public String getTitle(){ if(null != htmlParser){ try { return htmlParser.getTitle(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } return ""; } public Reader getContent(){ if(null != htmlParser){ try { return htmlParser.getReader(); } catch (IOException e) { e.printStackTrace(); } } return null; } public String getPath(){ return this.htmlPath; } } |
现在我们可以?Tomcat 5.0 上运行开发好的应用程序?
现在我们已经成功的完成了CZ目的开发,q成功的用Lucene实现了搜索和索引功能。你可以下蝲q个目的源代码Q?a >下蝲Q?
![]() ![]() |
![]()
|
Lucene 提供了灵zȝ接口使我们更加方便的设计我们?Web 搜烦应用E序。如果你惛_你的应用E序中加入搜索功能,那么 Lucene 是一个很好的选择。在设计你的下一个带有搜索功能的应用E序的时候可以考虑使用 Lucene 来提供搜索功能?
U别Q中U?/p>
摘要Q本文介l了字符与编码的发展q程Q相x늚正确理解。D例说明了一些实际应用中Q编码的实现Ҏ(gu)。然后,本文讲述了通常对字W与~码的几U误解,׃q些误解而导致ؕ码生的原因Q以及消除ؕ码的办法。本文的内容늛了“中文问题”,“ؕ码问题”?/p>
掌握~码问题的关键是正确地理解相x念,~码所涉及的技术其实是很简单的。因此,阅读本文旉要慢d惻I多思考?/p>
“字W与~码”是一个被l常讨论的话题。即使这P时常出现的ؕ码仍然困扰着大家。虽然我们有很多的办法可以用来消除ؕ码,但我们ƈ不一定理解这些办法的内在原理。而有的ؕ码生的原因Q实际上׃底层代码本n有问题所D的。因此,不仅是初学者会对字W编码感到模p,有的底层开发h员同样对字符~码~Z准确的理解?/p>
从计机对多国语a的支持角度看Q大致可以分Z个阶D:
pȝ内码 | 说明 | pȝ | |
阶段一 | ASCII | 计算机刚开始只支持pQ其它语a不能够在计算Z存储和显C?/td> | 英文 DOS |
阶段?/td> | ANSI~码 Q本地化Q?/td> | Z计算机支持更多语aQ通常使用 0x80~0xFF 范围?2 个字节来表示 1 个字W。比如:汉字 '? 在中文操作系l中Q?[0xD6,0xD0] q两个字节存储?br /> 不同的国家和地区制定了不同的标准Q由此生了 GB2312, BIG5, JIS {各自的~码标准。这些?2 个字节来代表一个字W的各种汉字延~码方式Q称?b> ANSI ~码。在体中文系l下QANSI ~码代表 GB2312 ~码Q在日文操作pȝ下,ANSI ~码代表 JIS ~码?br /> 不同 ANSI ~码之间互不兼容Q当信息在国际间交流Ӟ无法属于两U语a的文字,存储在同一D?b> ANSI ~码的文本中?/td> | 中文 DOSQ中?Windows 95/98Q日?Windows 95/98 |
阶段?/td> | UNICODE Q国际化Q?/td> | Z使国际间信息交流更加方便Q国际组l制定了 UNICODE 字符?/b>Qؓ各种语言中的每一个字W设定了l一q且唯一的数字编P以满语言、跨q_q行文本转换、处理的要求?/td> | Windows NT/2000/XPQLinuxQJava |
字符串在内存中的存放Ҏ(gu)Q?/p>
?ASCII 阶段Q?b>单字节字W串使用一个字节存放一个字W(SBCSQ。比如,"Bob123" 在内存中为:
42 | 6F | 62 | 31 | 32 | 33 | 00 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
B | o | b | 1 | 2 | 3 | \0 |
在?ANSI ~码支持多种语言阶段Q每个字W用一个字节或多个字节来表C(MBCSQ,因此Q这U方式存攄字符也被UC多字节字W?/b>。比如,"中文123" 在中?Windows 95 内存中ؓ7个字节,每个汉字?个字节,每个英文和数字字W占1个字节:
D6 | D0 | CE | C4 | 31 | 32 | 33 | 00 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||
?/td> | ?/td> | 1 | 2 | 3 | \0 |
?UNICODE 被采用之后,计算机存攑֭W串Ӟ改ؓ存放每个字符?UNICODE 字符集中的序受目前计机一般?2 个字节(16 位)来存放一个序PDBCSQ,因此Q这U方式存攄字符也被UC宽字节字W?/b>。比如,字符?"中文123" ?Windows 2000 下,内存中实际存攄?5 个序P
2D | 4E | 87 | 65 | 31 | 00 | 32 | 00 | 33 | 00 | 00 | 00 | ??x86 CPU 中,低字节在?/font> |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||||||
?/td> | ?/td> | 1 | 2 | 3 | \0 |
一共占 10 个字节?/p>
理解~码的关键,是要把字W的概念和字节的概念理解准确。这两个概念Ҏ(gu)hQ我们在此做一下区分:
概念描述 | 举例 | |
字符 | Z使用的记P抽象意义上的一个符受?/td> | '1', '?, 'a', '$', 'K?, …?/td> |
字节 | 计算Z存储数据的单元,一?位的二进制数Q是一个很具体的存储空间?/td> | 0x01, 0x45, 0xFA, …?/td> |
ANSI 字符?/td> | 在内存中Q如果“字W”是?ANSI ~码形式存在的,一个字W可能用一个字节或多个字节来表C,那么我们U这U字W串?ANSI 字符?/b>或?b>多字节字W串?/td> | "中文123" Q占7字节Q?/font> |
UNICODE 字符?/td> | 在内存中Q如果“字W”是以在 UNICODE 中的序号存在的,那么我们U这U字W串?UNICODE 字符?/b>或?b>宽字节字W串?/td> | L"中文123" Q占10字节Q?/font> |
׃不同 ANSI ~码所规定的标准是不相同的Q因此,对于一个给定的多字节字W串Q我们必ȝ道它采用的是哪一U编码规则,才能够知道它包含了哪些“字W”。而对?UNICODE 字符?/b>来说Q不在什么环境下Q它所代表的“字W”内Ҏ(gu)L不变的?/p>
1.3 字符集与~码
各个国家和地区所制定的不?ANSI ~码标准中,都只规定了各自语a所需的“字W”。比如:汉字标准QGB2312Q中没有规定韩国语字W怎样存储。这?ANSI ~码标准所规定的内容包含两层含义:
各个国家和地区在制定~码标准的时候,“字W的集合”和“编码”一般都是同时制定的。因此,q_我们所说的“字W集”,比如QGB2312, GBK, JIS {,除了有“字W的集合”这层含义外Q同时也包含了“编码”的含义?/p>
?b>UNICODE 字符?/b>”包含了各种语言中用到的所有“字W”。用来给 UNICODE 字符集编码的标准有很多种Q比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig {?/p>
![]() |
|||
|
单介l一下常用的~码规则Qؓ后边的章节做一个准备。在q里Q我们根据编码规则的特点Q把所有的~码分成三类Q?/p>
分类 | ~码标准 | 说明 |
单字节字W编?/td> | ISO-8859-1 | 最单的~码规则Q每一个字节直接作Z?UNICODE 字符。比如,[0xD6, 0xD0] q两个字节,通过 iso-8859-1 转化为字W串Ӟ直接得?[0x00D6, 0x00D0] 两个 UNICODE 字符Q即 "ÖÐ"?br /> 反之Q将 UNICODE 字符串通过 iso-8859-1 转化为字节串Ӟ只能正常转化 0~255 范围的字W?/td> |
ANSI ~码 | GB2312, BIG5, Shift_JIS, ISO-8859-2 …?/td> | ?UNICODE 字符串通过 ANSI ~码转化为“字节串”时Q根据各自编码的规定Q一?UNICODE 字符可能转化成一个字节或多个字节?br /> 反之Q将字节串{化成字符串时Q也可能多个字节转化成一个字W。比如,[0xD6, 0xD0] q两个字节,通过 GB2312 转化为字W串Ӟ得?[0x4E2D] 一个字W,?'? 字?br /> “ANSI ~码”的特点Q?br />1. q些“ANSI ~码标准”都只能处理各自语言范围之内?UNICODE 字符?br />2. “UNICODE 字符”与“{换出来的字节”之间的关系是h定的?/td> |
UNICODE ~码 | UTF-8, UTF-16, UnicodeBig …?/td> | 与“ANSI ~码”类似的Q把字符串通过 UNICODE ~码转化成“字节串”时Q一?UNICODE 字符可能转化成一个字节或多个字节?br /> 与“ANSI ~码”不同的是: 1. q些“UNICODE ~码”能够处理所有的 UNICODE 字符?br />2. “UNICODE 字符”与“{换出来的字节”之间是可以通过计算得到的?/td> |
我们实际上没有必要去q每一U编码具体把某一个字W编码成了哪几个字节Q我们只需要知道“编码”的概念是把“字W”{化成“字节”就可以了。对于“UNICODE ~码”,׃它们是可以通过计算得到的,因此Q在Ҏ(gu)的场合,我们可以M解某一U“UNICODE ~码”是怎样的规则?/p>
![]() |
|||
|
?C++ ?Java 中,用来代表“字W”和“字节”的数据cdQ以及进行编码的Ҏ(gu)Q?/p>
cd或操?/b> | C++ | Java |
字符 | wchar_t | char |
字节 | char | byte |
ANSI 字符?/td> | char[] | byte[] |
UNICODE 字符?/td> | wchar_t[] | String |
字节东y字符?/td> | mbstowcs(), MultiByteToWideChar() | string = new String(bytes, "encoding") |
字符东y字节?/td> | wcstombs(), WideCharToMultiByte() | bytes = string.getBytes("encoding") |
以上需要注意几点:
![]() |
|||
|
声明一D字W串帔RQ?/p>
// ANSI 字符Ԍ内容长度 7 字节
char sz[20] = "中文123"; // UNICODE 字符Ԍ内容长度 5 ?wchar_tQ?0 字节Q?/span> wchar_t wsz[20] = L"\x4E2D\x6587\x0031\x0032\x0033"; |
UNICODE 字符串的 I/O 操作Q字W与字节的{换操作:
// q行时设定当?ANSI ~码QVC 格式 setlocale(LC_ALL, ".936"); // GCC 中格?/span> setlocale(LC_ALL, "zh_CN.GBK"); // Visual C++ 中用小?%sQ按?setlocale 指定~码输出到文?br />// GCC 中用大?%S fwprintf(fp, L"%s\n", wsz); // ?UNICODE 字符串按?setlocale 指定的编码{换成字节 wcstombs(sz, wsz, 20); // 把字节串按照 setlocale 指定的编码{换成 UNICODE 字符?br />mbstowcs(wsz, sz, 20); |
?Visual C++ 中,UNICODE 字符串常量有更简单的表示Ҏ(gu)。如果源E序的编码与当前默认 ANSI ~码不符Q则需要?#pragma setlocaleQ告诉编译器源程序用的~码Q?/p>
// 如果源程序的~码与当前默?ANSI ~码不一_ // 则需要此行,~译时用来指明当前源E序使用的编?/font> #pragma setlocale (".936") // UNICODE 字符串常量,内容长度 10 字节 wchar_t wsz[20] = L"中文123"; |
以上需要注?#pragma setlocale ?setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在编译时起作用,setlocale() 在运行时起作用?/p>
![]() |
|||
|
字符串类 String 中的内容?UNICODE 字符Ԍ
// Java 代码Q直接写中文
String string = "中文123"; // 得到长度?5Q因为是 5 个字W?/span> System.out.println(string.length()); |
字符?I/O 操作Q字W与字节转换操作。在 Java ?java.io.* 中,以“Stream”结cM般是用来操作“字节串”的c,以“Reader”,“Writer”结cM般是用来操作“字W串”的cR?/p>
// 字符串与字节串间怺转化 // 按照 GB2312 得到字节Q得到多字节字符Ԍ byte [] bytes = string.getBytes("GB2312"); // 从字节按?GB2312 得到 UNICODE 字符?/span> string = newString(bytes, "GB2312"); // 要将 String 按照某种~码写入文本文gQ有两种Ҏ(gu)Q?br /> // W一U办法:?Stream cd入已l按照指定编码{化好的字节串 OutputStream os = new FileOutputStream("1.txt"); os.write(bytes); os.close(); // W二U办法:构造指定编码的 Writer 来写入字W串 Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"), "GB2312"); ow.write(string); ow.close(); /* 最后得到的 1.txt ?2.txt 都是 7 个字?*/ |
如果 java 的源E序~码与当前默?ANSI ~码不符Q则在编译的时候,需要指明一下源E序的编码。比如:
E:\>javac -encoding BIG5 Hello.java |
以上需要注意区分源E序的编码与 I/O 操作的编码,前者是在编译时起作用,后者是在运行时起作用?/p>
![]() |
|||
|
对编码的误解 | |
误解一 | 在将“字节串”{化成“UNICODE 字符东y时Q比如在d文本文gӞ或者通过|络传输文本ӞҎ(gu)“字节串”简单地作ؓ单字节字W串Q采用每“一个字节”就是“一个字W”的Ҏ(gu)q行转化?br /> 而实际上Q在非英文的环境中,应该“字节串”作?ANSI 字符Ԍ采用适当的编码来得到 UNICODE 字符Ԍ有可能“多个字节”才能得到“一个字W”?br /> 通常Q一直在英文环境下做开发的E序员们Q容易有q种误解?/td> |
误解?/td> | ?DOSQWindows 98 {非 UNICODE 环境下,字符串都是以 ANSI ~码的字节Ş式存在的。这U以字节形式存在的字W串Q必ȝ道是哪种~码才能被正地使用。这使我们Ş成了一个惯性思维Q“字W串的编码”?br /> ?UNICODE 被支持后QJava 中的 String 是以字符的“序号”来存储的,不是以“某U编码的字节”来存储的,因此已经不存在“字W串的编码”这个概念了。只有在“字W串”与“字节串”{化时Q或者,一个“字节串”当成一?ANSI 字符串时Q才有编码的概念?br /> 不少的h都有q个误解?/td> |
W一U误解,往往是导致ؕ码生的原因。第二种误解Q往往D本来Ҏ(gu)U正的ؕ码问题变得更复杂?/p>
在这里,我们可以看到Q其中所讲的“误解一”,即采用每“一个字节”就是“一个字W”的转化Ҏ(gu)Q实际上也就{同于采?iso-8859-1 q行转化。因此,我们常常使用 bytes = string.getBytes("iso-8859-1") 来进行逆向操作Q得到原始的“字节串”。然后再使用正确?ANSI ~码Q比?string = new String(bytes, "GB2312")Q来得到正确的“UNICODE 字符东y?/p>
![]() |
|||
|
?UNICODE E序中的字符Ԍ都是以某U?ANSI ~码形式存在的。如果程序运行时的语a环境与开发时的语a环境不同Q将会导?ANSI 字符串的昄p|?/p>
比如Q在日文环境下开发的?UNICODE 的日文程序界面,拿到中文环境下运行时Q界面上显CZؕ码。如果这个日文程序界面改为采?UNICODE 来记录字W串Q那么当在中文环境下q行Ӟ界面上将可以昄正常的日文?/p>
׃客观原因Q有时候我们必d中文操作pȝ下运行非 UNICODE 的日文YӞq时我们可以采用一些工P比如Q南极星QAppLocale {,暂时的模拟不同的语言环境?/p>
![]() |
|||
|
当页面中的表单提交字W串Ӟ首先把字W串按照当前面的编码,转化成字节串。然后再每个字节{化成 "%XX" 的格式提交到 Web 服务器。比如,一个编码ؓ GB2312 的页面,提交 "? q个字符串时Q提交给服务器的内容?"%D6%D0"?/p>
在服务器端,Web 服务器把收到?"%D6%D0" 转化?[0xD6, 0xD0] 两个字节Q然后再Ҏ(gu) GB2312 ~码规则得到 "? 字?/p>
?Tomcat 服务器中Qrequest.getParameter() 得到qӞ常常是因为前面提到的“误解一”造成的。默认情况下Q当提交 "%D6%D0" l?Tomcat 服务器时Qrequest.getParameter() 返?[0x00D6, 0x00D0] 两个 UNICODE 字符Q而不是返回一?"? 字符。因此,我们需要?bytes = string.getBytes("iso-8859-1") 得到原始的字节串Q再?string = new String(bytes, "GB2312") 重新得到正确的字W串 "??/p>
![]() |
|||
|
通过数据库客LQ比?ODBC ?JDBCQ从数据库服务器中读取字W串Ӟ客户端需要从服务器获知所使用?ANSI ~码。当数据库服务器发送字节流l客LӞ客户端负责将字节按照正的~码转化?UNICODE 字符丌Ӏ?/p>
如果从数据库d字符串时得到qQ而数据库中存攄数据又是正确的,那么往往q是因ؓ前面提到的“误解一”造成的。解决的办法q是通过 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字节串Q再重新使用正确的编码{化成字符丌Ӏ?/p>
![]() |
|||
|
当一D?Text 或?HTML 通过?sh)子邮g传送时Q发送的内容首先通过一U指定的字符~码转化成“字节串”,然后再把“字节串”通过一U指定的传输~码QContent-Transfer-EncodingQ进行{化得到另一东y字节串”。比如,打开一电(sh)子邮件源代码Q可以看到类似的内容Q?/p>
Content-Type: text/plain; charset="gb2312" Content-Transfer-Encoding: base64 sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg== |
最常用?Content-Transfer-Encoding ?Base64 ?Quoted-Printable 两种。在对二q制文g或者中文文本进行{化时QBase64 得到的“字节串”比 Quoted-Printable 更短。在对英文文本进行{化时QQuoted-Printable 得到的“字节串”比 Base64 更短?/p>
邮g的标题,用了一U更短的格式来标注“字W编码”和“传输编码”。比如,标题内容?"?Q则在邮件源代码中表CZؓQ?/p>
// 正确的标题格?/span>
Subject: =?GB2312?B?1tA=?= |
其中Q?/p>
如果“传输编码”改?Quoted-PrintableQ同P如果标题内容?"?Q?/p>
// 正确的标题格?/span>
Subject: =?GB2312?Q?=D6=D0?= |
如果阅读邮g时出Cؕ码,一般是因ؓ“字W编码”或“传输编码”指定有误,或者是没有指定。比如,有的发邮件组件在发送邮件时Q标?"?Q?/p>
// 错误的标题格?/span>
Subject: =?ISO-8859-1?Q?=D6=D0?= |
q样的表C,实际上是明确指明了标题ؓ [0x00D6, 0x00D0]Q即 "ÖÐ"Q而不?"??/p>
![]() |
|||
|
非也。iso-8859-1 只是单字节字W集中最单的一U,也就是“字节编号”与“UNICODE 字符~号”一致的那种~码规则。当我们要把一个“字节串”{化成“字W串”,而又不知道它是哪一U?ANSI ~码Ӟ先暂时地把“每一个字节”作为“一个字W”进行{化,不会造成信息丢失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢复到原始的字节丌Ӏ?/p>
Java 中,字符串类 java.lang.String 处理的是 UNICODE 字符Ԍ不是 ANSI 字符丌Ӏ我们只需要把字符串作为“抽象的W号的串”来看待。因此不存在字符串的内码的问题?/p>