caitong

caitong

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  12 随笔 :: 0 文章 :: 0 评论 :: 0 Trackbacks

2009年6月5日 #

先比较一下Hadoop。

Hadoop 架构:


Cache Pool 架构:


Cache Server和Hadoop的Data Node是相似的,Cache Manager和Name Node对应,不过也有很多差异:http://www.bt285.cn http://www.5a520.cn  
  • Cache Pool要承受大并发访问,且每条数据都非常小,因此不可能再做一个Name Node来保存元数据,而是使用Consistent Hashing完成数据定位。
  • Cache Pool数据量相对较小,一个集群几百GB左右,单台Cache Server只有4-16GB,迁移性能非常高,所以任何一个节点调整都会有1/N数据被迁移,容量约等于单台Server的容量。新增节点时迁移相对比较慢,有大量数据被从多个节点迁移到这个新节点上,迁移完成会并发删除旧服务器上的数据;删除节点则会引起多个节点间的并发数据迁移,迁移效率较高,同时可能会造成每台服务器上有约1/N数据被LRU淘汰。
  • Cache Manager只是个管理器,它只完成节点监控、Cache Server划分、数据迁移控制、同步配置等功能,所有数据访问都与它无关。它是由多台服务器组成的高可用性小集群,使用简单决策过程产生Master,其它服务器只作备用。为避免网络、电源等问题的冲击,Cache Manager设置为只对同时一个Cache节点宕机有权利自动化迁移,多台同时宕机时,会发出报警,需要管理员人工控制迁移。
posted @ 2009-06-17 19:41 caitong| 编辑 收藏

Java语言本身具有跨平台性,如果通过Java调用DLL的技术方便易用,使用Java开发前台界面可以更快速,也能带来跨平台性。

       Java调用C/C   写好的DLL库时,由于基本数据类型不同、使用字节序列可能有差异,所以在参数传递过程中容易出现问题。

   使用Java调用DLL动态链接库的方案通常有三种:JNI, Jawin, Jacob. 其中JNI(Java Native Interface)是Java语言本身提供的调用本地已编译的函数库的方法,本身具有跨平台性,可以在不同的机器上调用不同的本地库。Jawin和 Jacob都是sourceforge.net的开源项目,都是基于JNI技术的依赖Windows的实现,使得在Windows平台下使用COM和 DLL的更加方便。

   三、JNI

   sun相关文档:http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/jniTOC.html

  http://www.bt285.cn  或是 http://www.5a520.cn

   JNI的完整例子 :http://www.pconline.com.cn/pcedu/empolder/gj/java/0506/642328.html

   JNI的应用方案是基于Java类和本地函数相映射的。其使用DLL的步骤还是相对比较麻烦,不但涉及到Java编程,还涉及到C/C   编程。

   JNI的使用步骤是:

   1.编写Java类,用该类将DLL对外提供的函数服务进行声明,其中的Java方法均声明为native,其方法签名可以自定义,不用实现函数体。

   2.用Javah工具将该Java类生成对应的.h头文件。

   3.最重要的比较麻烦的一步:编写C/C   代码实现.h头文件中声明的函数,该C/C   代码中包含jni.h头文件,并且编写代码时使用其中定义好的数据类型作为函数的输入和返回数据类型进行编程。用这种方法实现数据类型转换。例如数据类型:boolean(java) à jboolean(jni.h: typedef unsigned char jboolean),在自己编写的C/C   代码中使用数据类型jboolean映射Java中的boolean类型。在该步骤中,可以在C/C   代码中调用已经存在的DLL库。

   4.另外编写的Java代码时就可以使用该Java类了。

   在第3步中,编写C/C   函数时,可以使用一个叫interface pointer的env指针来调用JNI提供的一系列(很多)函数,用这些函数来访问JVM的对象和数据。

   使用JNI的缺点:使用比较麻烦,需要对已有的DLL进行封装,需要对C/C   比较了解。

   使用JNI的优点:可以跨平台调用本地库。

   四、Jawin

   官方网站:http://jawinproject.sourceforge.net/

   官方文档(Jawin介绍): http://jawinproject.sourceforge.net/jawin.html

   官方文档(Jawin使用DLL):http://jawinproject.sourceforge.net/jawinuserguide_dll.html

   官方文档(Jawin数据指令): http://jawinproject.sourceforge.net/instruction_docs.html http://www.feng123.com

   Jawin的应用方案是基于函数调用时采用原始字节流传递数据的。就是在Java中指明一个DLL中的某个函数后,通过原始字节流(需要考虑参数数据类型所占的存储字节数及系统使用的字节序列)传递给该DLL函数需要的参数,其返回值也是通过原始字节流解析的方式获得正确的值。

   Jawin的使用步骤:

   1.环境配置:下载Jawin;Jawin.dll放入工程目录下;Jawin.jar相关jar文件加入到运行库中(LibPath或者Eclipse下配置工程的BuildPath-AddLibrary)。

   2.获得函数指针:new FuncPtr("DllFileName.DLL", "dllFunctionName");

   3.用LittleEndianOutputStream将函数需要的参数写入到一个原始字节流NakedByteStream。

   4.最重要的一步:调用FuncPtr.invoke()。传入参数比较复杂。

   5.解析上一步的返回值(字节数组)。

   第4步中传入的参数包括:

   1.指令字符串。一个"XXX:Y:ZZZ"格式的字符串。其含义分别是传入参数中的每个字节的数据类型意义、返回值的类型、需要从传入指针中读取的数据(inout类型参数)。比如:

   函数签名int func(int, int, struct s*, char*); //其中struct s*调用完函数后需要读出,struct s所占字节数为16。

   其指令字符串为:IIP16G:I关于Java调用dll的方法 - 电博 - 电博的博客4L4n16L4。该字符串在解析返回值(字节数组)时,首先应该是返回类型I对应的4个字节,然后是inout类型的参数中n16对应的16个字节。

   其中字符串的意义可以在Jawin提供的文件instructions.h中找到,或者在官方文档(Jawin数据指令)中找到常用的一些指令字符串的意义。

   2.传入参数的总字节大小。

   3.前面写好的传入参数的原始字节流。

   4.一个object数组。

   5.ReturnFlags,用以根据C/C   返回值将C/C   的错误转换为Java的异常并抛出。其中CHECK_NONE表示不检查;CHECK_FALSE和CHECK_WIN32分别表示返回0是FALSE和 SUCCESS,根据是否出错决定是否抛出异常;CHECK_HRESULT表示使用COM模型中的HRESULT作为返回值,其错误码可以配置。

   使用Jawin的缺点:不方便调试,几乎所有的错误都抛出同样的异常COMException;需要对数据类型的转换比较了解;不能跨平台,对Windows的依赖性比较强。

   使用Jawin的优点:方便使用,不用进行C/C   开发,不用对原始DLL进行封装就可以方便使用。

   五 Jacob

   官方文档:http://danadler.com/jacob/

   Jacob是Java-Com Bridge的缩写,也可以用来调用DLL。其底层也是使用JNI实现,也具有Windows 的平台依赖性,但是网上有人反映其易用性不如jawin。

posted @ 2009-06-15 21:08 caitong| 编辑 收藏

虽然颈椎病有五种不同的类型,根据解放军306医院的统计,目前30至 40岁的上班族颈椎病患者中,76%以上都是由于劳损导致的颈肌型颈椎病。此类颈椎病主要是由于工作中姿势性劳损、过渡劳累,从而使得颈部软组织损伤、气血郁滞。
  上班族的颈椎病绝非一日之寒导致的,属于慢性疾病,在治疗方面也不可能一蹴而就, 下面是骨科专家,对上班族预防颈椎病关键的几点知识进行介绍。
■ TOP1:为什么经常低头伏案的人容易换颈椎病
正常脊柱各段因人体生理需要,均有一定的弯曲弧度,称为生理曲度。颈椎生理曲度的存在,能增加颈椎的弹性,减轻和缓冲重力的震荡,防止对脊髓和大脑的损伤。
  经常低头伏案,会使得颈椎正常的生理屈度变直,引起颈椎很多其他变化(如松动、增生、肌肉紧张等),从而刺激周围神经或血管,导致颈椎病痛。
■ TOP2:上班族的正确坐姿

■ TOP3 :如何在睡眠中康复颈椎?
  每个人生命中有 1/3的时间在睡眠中渡过;一个紧密适合颈椎生理曲度的枕头,可使工作、学习、生活一天后的你,在睡眠之中解除颈椎肌肉、韧带的疲劳。
  根据人体工学,枕头的形状,以中间低、两端高的元宝形最适合颈椎生理曲度。这种枕头可利用前方凸出部位来维持颈椎的生理曲度。枕芯填充物方面,慢回弹温感聚氨酯具有独特的黏弹性和温感性,它可根据温度和压力而下陷,但又不会反弹。这两种特性使得它成为良好的释压材料。

■ TOP4:笔记本电脑在如何伤害你的颈椎?

笔记本电脑在人体工程学方面存在严重缺陷,屏幕与键盘之间距离太近,僵着脖子低头看屏幕,可能造成颈肌肉损伤;将机器抬到眼睛适合的位置,又可能造成肩膀和手臂肌肉劳损。目前,各种相关病变已在笔记本早期用户中逐渐显现出来了。
  正确的防范方法是垫高笔记本电脑,同时使用外接的传统键盘。
■ TOP5:颈椎病重在预防!
  目前医学界还没有有效治疗颈椎病的方法,总的来说颈椎病重在预防。虽然颈椎病多发生在中老年,但病变过程往往开始于青少年时期。特别在目前上班族中,颈椎病的发病年龄已经大大提前。
  针对颈椎病的致病因素——劳损、落枕、睡姿不良、枕头不当、风寒、头颈外伤等,如我们能尽早采取有效预防措施,则可以降低颈椎病的发病率或推迟其发病时间。

桌面摆放不当导致颈椎病
在美国,因为'工作中长期同一姿势而导致某些身体异常'的人数比率已经达到流行病的传播比率,而此类身体异常中,颈椎病、腕管综合征、腰椎和尾椎问题占比最高。美国职业安全与健康协会经过长期统计发现, RSI(Repetitive Strain Injury,重复性力损伤,见 http://www.bt285.cnhttp://www.5a520.cn )之所以能很快变成一种流行病,是因为大多数电脑使用者未采用正确的坐姿,以及不能科学的摆放桌面设备。
  根据国内专业的健康机构的调查,中国的电脑健康伤害问题正日趋严重,全社会的重视程度还远远不够。同时电脑职业病在中国还不是法定职业病,没有配套的鉴定、赔偿机制。专家建议,要避免电脑对上班族的健康伤害,需要每个企业、每个上班族充分认识到其重要性并采取措施加以预防。
  在此,专家、学者建议大家可以采取如下的措施预防工作中对健康的伤害:
正确的坐姿:

  -前倾的姿势使得头部对颈椎的负担最大,因此上班族应尽量采用微微向后倾,靠在座椅靠背上的姿势进行工作,其间可穿插采用坐直的姿势,但不可采用向前倾的姿势进行工作;
  -杜绝低头伏案工作,电脑屏幕摆放在平行或微微低于视线的位置,特别是针对笔记本电脑的用户,尽量垫高笔记本,以抬高显示器,减少颈椎病的发病概率;同时外接台式机键盘,减小笔记本抬高后对手腕的劳损,这样也可获得眼睛与屏幕间更大的间距,保护视力;
-手臂自然下垂,放置在座椅扶手上,键盘过高或过低都容易导致肩颈肌肉疲劳;
桌面设备及摆放:

  鼻子、键盘中线、显示器中线位于一条直线上(见图中中轴线),减少身体的扭曲;
  键盘和鼠标尽量放置在如图中的'轻松操作区域'内;鼠标与键盘在同一水平面,同时尽量靠近中轴线的位置,切勿摆放太远。
  在录入文档时,尽量采用文件夹,将文档竖立固定在与显示器同一水平面;
  接电话时,用肩膀和头部夹着电话的姿势,对颈肩肌肉的伤害都是很大的,如果工作中接听电话的时间很多,可采用耳麦式电话;
工作中重复性力损伤看似可怕,但专家也告诉记者,对于各种电脑健康伤害,其实只要被人们意识到了,并进一步跨越'知'与'行'的鸿沟,养成科学的工作习惯,这些伤害就很容易避免。同时,在8小时工作以外的时间,我们也应该采取各种其它养护措施,全方位为健康保驾护航。

工作中的颈椎操
   8小时+加班,紧张工作中的你,可能会由于时间紧而忽略了颈椎保健。有时想做做颈椎操,也可能因为一些颈椎操过于'异形',而不好意思在公司里做。没关系,下面这套颈椎保健动作,随时随地都可以'隐性'的进行。
保健动作一(如图):

  动作:双手交叉抱于头后,头用力向后仰,双手用力向上托住头;
  优点:锻炼颈部肌肉,放松颈椎骨骼间隙,而且同事也不觉得你的动作奇怪,还以为你在思考问题呢 :)
  时间:每小时进行一次,动作坚持三十秒,一次六组。
保健动作二:
  动作:旋转头部
  优点:随时随地
  时间:随时

回家后的颈椎瑜珈
工作了一天的上班族,颈椎也处于疲劳状态,回到家中,瑜伽中模仿动物的姿态可以令我们的颈椎更舒服更健康。
  练习时间:每日晚间,每个动作 6秒钟,一次 4组。
金刚鱼式

作用:伸展脊椎、颈部与后背的肌肉。
动作:跪坐于地板上,双手放于两大腿上,吸气。呼气身体慢慢向后,使头顶逐渐触地,双手在胸前合十。
牛面式

作用:矫正颈椎、脊柱,扩张胸部,放松肩关节,令背阔肌得到伸展。
动作:坐于地板,两腿互相交叉,双膝上下一条直线,双脚分别放于异侧的臀部旁边。双手在背后相扣,保持背部的挺拔。如果感觉困难,可双手抓住一条毛巾,效果相同。
猫伸展式

作用:脊柱及周围肌肉群更富有弹性,放松颈部和肩部使背部肌肉协调工作。
动作:跪于地板,双手支撑身体。吸气,脊柱向下伸展,抬头提臀
posted @ 2009-06-12 21:21 caitong| 编辑 收藏

RMI是Java平台实现远程调用的规范,下面是一个小例子,本机测试通过

一共有三个java类,远程接口,服务端程序,客户端程序

远程接口:

import java.rmi.*;

public interface HelloIn extends java.rmi.Remote{
 String sayHello() throws RemoteException;
}

服务端程序:

/**
* author by http://www.bt285.cn  http://www.5a520.cn
*/
import java.rmi.*;
import java.net.*;
import java.rmi.registry.*;
import java.rmi.server.*;

public class Hello extends java.rmi.server.UnicastRemoteObject implements HelloIn{
 public Hello() throws RemoteException{
  super();
 }
 public String sayHello() throws RemoteException{
  return "Hello,World!";
 }
 public static void main(String[] args){
  //System.setSecurityManager(new java.rmi.RMISecurityManager());
  try{
 
      Hello h=new Hello();
      java.rmi.Naming.rebind("hello",h);
      System.out.print("Ready......");
   }
   catch(Exception e){
    e.printStackTrace();
   }
 
 }
}

执行服务端程序前在命令行方式下启动rmi的注册程序:  start rmiregistry

客户端程序:

/**
* author by http://www.bt285.cn  http://www.5a520.cn
*/

import java.rmi.*;
import java.rmi.registry.*;

public class Helloworld{
 public static void main(String[] args){
  //System.setProperty( "java.security.policy", "client.policy" );
  //System.setSecurityManager(new java.rmi.RMISecurityManager());
  try{
   HelloIn hi=(HelloIn)Naming.lookup("//fengl/hello");
   for(int i=0;i<10;i++){
    System.out.println(hi.sayHello());
   }
  }
  catch(Exception e){
   e.printStackTrace();
  }
  }
 }

执行客户端程序前先用  rmic Hello  生成Stub 和 Skeleton 的class,它们
实际上是远程调用的底层的实现。
 
最后执行java Helloworld 控制台打印出 Hello,World,成功调用.

 

 

posted @ 2009-06-11 20:44 caitong| 编辑 收藏

         J2EE/XML开发者通常都是使用文档对象模型(DOM)API或简单的API for XML(SAX) API来分析XML文档。然而,这些API都有其缺点。其中,DOM API的缺点之一是消耗大量的内存,因为在该XML文档可以被导航之前,必须创建一个完整的XML文档的内存结构。而SAX API的缺点在于,它实例了一种推分析模型API,其中分析事件是由分析器生成的。比较之下,StAX则是基于一种拉分析模型。在本文中,你将首先创建你自己的XML文档,然后学习使用各种不同方法来对之进行分析;最后,我们使用事件生成的StAX拉方法。

  一、 推分析之于拉分析

  比较于推分析,拉分析具有如下一些优点:

  1. 在拉分析中,事件是由分析应用程序生成的,因此把分析规则提供到客户端而不是分析器。

  2. 拉分析的代码更简单并且它比推分析有更少的库。

  3. 拉分析客户端能同时读多个XML文档。

  4. 拉分析允许你过滤XML文档并且跳过分析事件。

  二、 了解StAX

  针对于XML的流式API(StAX),是在2004年3月的JSR 173规范中引入,这是一种针对XML的流式拉分析API。StAX是JDK 6.0提供的一种新特征,你可以从此处下载它的测试版本试用。

  一个推模型分析器不断地生成事件,直到XML文档被完全分析结束。但是,拉分析由应用程序进行调整;因此,分析事件是由应用程序生成的。这意味着,使用StaX,你可以推迟分析-在分析时跳过元素并且分析多个文档。在使用DOM API的时候,你必须把整个的XML文档分析成一棵DOM结构,这样也就降低了分析效率。而借助于StAX,在分析XML文档时生成分析事件。有关于StAX分析器与其它分析器的比较在此不多介绍。

  StAX API的实现是使用了Java Web服务开发(JWSDP)1.6,并结合了Sun Java流式XML分析器(SJSXP)-它位于javax.xml.stream包中。XMLStreamReader接口用于分析一个XML文档,而XMLStreamWriter接口用于生成一个XML文档。XMLEventReader负责使用一个对象事件迭代子分析XML事件-这与XMLStreamReader所使用的光标机制形成对照。本教程将基于JDK 6.0中的StAX实现来完成对一个XML文档的分析。

  其实,StaX仅仅是JDK 6.0所提供的XML新特征之一。新的JDK 6.0还提供了对针对于XML-Web服务的Java架构(JAX-WS)2.0,针对于XML绑定的Java API(JAXB) 2.0,XML数字签名API的支持,甚至还支持SQL:2003 'XML'数据类型。

  三、 初步安装

  如果你正在使用JDK 6.0,那么默认情况下,StAX API位于Classpath中。如果你在使用JWSDP 1.6,请把JWSDP 1.6 StAX API添加到classpath中。这需要把\sjsxp\lib\ jsr173_api.jar和\sjsxp\lib\sjsxp.jar http://www.bt285.cn 添加到CLASSPATH变量中。在目录下安装JWSDP 1.6。Jsr173_api.jar相应于JSR-173 API JAR,Sjsxp.jar相应于SJXSP实现JAR。

  四、 使用XMLStreamWriter进行写操作

  首先,你要创建将待分析的XML文档。由StAX的XMLStreamWriter生成XML。然而,XMLStreamWriter的一个限制是,它不一定会生成良构的文档-而且生成的文档也不一定是有效的。你需要确保生成的XML文档是良构的。列表1是一个由XMLStreamWriter生成的原始XML文档的示例。

  在此,你试图使用XMLStreamWriter API生成列表1中的catalog.xml。在本节中的代码片断节选自XMLWriter.java应用程序,显示于列表2中。首先,你将导入StAX包类,请参考下列编码:

/**
*author by http://www.5a520.cnhttp://www.feng123.com 

*
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.stream.XMLOutputFactory;


  你要从一个XMLOutputFactory中得到你的XMLStreamWriter。因此,首先你必须创建一个新的XMLOutputFactory:


XMLOutputFactory outputFactory=XMLOutputFactory.newInstance();


  接下来,创建一个FileWriter以输出XML文档-它将被生成到一个XML文件中:


FileWriter output=new FileWriter(new File("C:/STAX/catalog.xml"));


  接下来,创建一个XMLStreamWriter:


XMLStreamWriter XMLStreamWriterr=outputFactory.createXMLStreamWriter(output);


  现在,使用writeStartDocument()方法创建一个文档开头。添加要在XML声明中指定的编码和版本(记住,指定的编码并不是生成的XML文档的编码)。如果你需要指定XML文档的编码,该怎么办呢?当从一个XMLOutputFactory对象创建一个XMLStreamWriter对象时,你会这样做:


XMLStreamWriter.writeStartDocument("UTF-8","1.0");


  使用writeComment()方法以输出一个注释:


XMLStreamWriter.writeComment("A OReilly Journal Catalog");


  使用writeProcessingInstruction()方法以输出一条处理指令:


XMLStreamWriter.writeProcessingInstruction("catalog","journal='OReilly'");


  使用writeStartElement()方法以输出'catalog'元素的开始(元素前缀和命名空间URI也可以在这个方法中指定的):


XMLStreamWriter.writeStartElement("journal","catalog"," http://www.feng123.com  ");


  使用writeNamespace()方法以添加'journal'命名空间声明(命名空间前缀和命名空间URI也是在这个方法中指定的):


XMLStreamWriter.writeNamespace("journal"," http://www.bt285.cn ");


  再次使用writeNamespace()方法添加xsi命名空间:


XMLStreamWriter.writeNamespace("xsi","http://www.w3.org/2001/XMLSchema-instance");


  使用writeAttribute()方法添加xsi:namespaceSchemaLocation属性:


XMLStreamWriter.writeAttribute("xsi:noNamespaceSchemaLocation","file://c:/Schemas/catalog.xsd");


  使用writeAttribute()方法添加'publisher'属性:


XMLStreamWriter.writeAttribute("publisher","OReilly");


  输出'journal'元素的开始。当增加一个新元素时,前一个元素的'>'括号也被添加上:


XMLStreamWriter.writeStartElement("journal","journal","http:
//OnJava.com/Journal");


  使用writeAttribute()方法以添加'date'和'title'属性。然后,使用writeElement()方法以添加'article'和'title'元素。然后,使用writeCharacters()方法输出'title'元素的文本:


XMLStreamWriter.writeCharacters("Data Binding with XMLBeans");


  任何包含文本或子元素的元素都要有一个结束标签。使用writeEndElement()元素来添加'title'元素的结束标签:


XMLStreamWriter.writeEndElement();


  添加'author'元素和'journal'元素的结束标签。在writeEndElement()方法中,不必要指定元素前缀和命名空间URI。以类似方式添加另一个'journal'元素。然后,添加'catalog'元素的结束标签。最后,输出缓冲的数据:


XMLStreamWriter.flush();


  最后一步,关闭XMLStreamWriter。


XMLStreamWriter.close();


  这就是生成catalog.xml的过程。

  源码中的列表2展示了完整的Java应用程序-XMLWriter.java。这个应用程序可以作为一个命令行应用程序运行或在一种例如Eclipse这样的IDE中运行。

 

  五、 使用XMLStreamReader进行分析

  通过使用XMLStreamReader API分析列表1中的文档,我们来详细分析一下其工作原理。XMLStreamReader使用一种光标分析XML文档。它的接口包含一个next()方法-由它分析下一个分析事件。getEventType()方法返回事件类型。后面的代码片断来自于XMLParser.java应用程序,详见列表3。

  在这个XMLParser.java应用程序中,首先,你要导入StAX类:


import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.stream.XMLInputFactory;


  然后,创建一个XMLInputFactory,由此你会得到一个XMLStreamReader:


XMLInputFactory inputFactory=XMLInputFactory.newInstance();


  现在,你需要创建一个InputStream,作为一个输入流,它描述了将被分析的文件。另外,还要从前面创建的XMLInputFactory对象中创建一个XMLStreamReader。


InputStream input=new FileInputStream(new File("C:/STAX/catalog.xml"));
XMLStreamReader xmlStreamReader =inputFactory.createXMLStreamReader(input);


  如果更多分析事件可用,hasNext()方法返回true。然后,使用next()方法获得下一个分析事件:


int event=xmlStreamReader.next();


  比较于SAX分析,StAX分析的优点是,一个分析事件可以被跳过-通过调用next()方法,详见下面的代码。例如,如果分析事件类型为ENTITY_DECLARATION,那么开发者可以决定是要从当前事件中获得事件信息,还是检索下一个事件:


If(event.getEventType()==XMLStreamConstants.ENTITY_DECLARATION){
int event=xmlStreamReader.next();
}


  通过不调用next()方法,分析也可以被推迟。next()方法返回int,它代表了一个分析事件-通过使用一个XMLStreamConstants常量指定。

  XMLStreamReader所返回的不同的事件类型列举于表格1中。


事件类型  描述
START_DOCUMENT  一个文档的开始
START_ELEMENT 一个元素的开始
ATTRIBUTE 一个元素属性
NAMESPACE 一个命名空间声明
CHARACTERS 字符可以是文本,或是一个空格
COMMENT 一个注释
SPACE 可忽略的空格
PROCESSING_INSTRUCTION 处理指令
DTD 一个DTD
ENTITY_REFERENCE 一个实体参考
CDATA Cdata节
END_ELEMENT 结束元素
END_DOCUMENT 结束文档
ENTITY_DECLARATION 一个实体声明
NOTATION_DECLARATION 一个标志声明

表格1.XMLStreamReader事件

  这些不同的分析事件能够使你获得XML文档中的数据和元数据。如果分析事件类型是START_DOCUMENT,那么你将使用getEncoding()方法获得XML文档中的指定编码,而你将使用getVersion()方法返回XML文档的XML版本。

  同样,如果你在使用一个START_ELEMENT事件类型工作,那么你将使用getPrefix()方法来返回元素前缀并且使用getNamespaceURI来返回元素前缀命名空间或默认命名空间。为了获得元素的本地命名,你将使用getLocalName()方法并且使用getAttributesCount()方法获得属性数目。你将使用getAttributePrefix(i)方法得到一个指定的属性索引i的属性前缀,而使用getAttributeNamespace(i)方法取得属性命名空间。使用getAttributeLocalName(i)方法获得属性本地命名,使用getAttributeValue(i)方法获得属性值。如果事件类型是CHARACTERS或COMMENT,则使用getText()方法获得相应的文本。

  列表4显示了示例XML文档,catalog.xml,的分析输出结果。

  列表3显示了用于分析XML文档的Java应用程序。你可以从命令行上或在一种例如Eclipse这样的IDE中来运行该应用程序。记住:如果你没有首先运行XMLWriter.java应用程序而运行XMLParser.java(见源码中的列表2),那么你需要把catalog.xml(见源码中的列表1)复制到C:/StAX目录下。

 

  六、 使用XMLEventReader进行分析

  本节将向你展示如何使用XMLEventReader来分析catalog.xml。XMLEventReader接口使用一个事件对象迭代算子分析一个XML文档;通过这种方式,一个XML事件生成一个XMLEvent对象。XMLEventReader类似于XMLStreamReader-分析事件是由StAX分析器生成的。然而,XMLEventReader比XMLStreamReader有一个优点:通过使用XMLEventReader,一个应用程序可以使用peek()方法来"偷看"下一个事件,而不必从流中读取事件。这样,一个应用程序客户端可以决定是否有必要分析下一个事件。本节中的代码片断节选自XMLEventParser.java应用程序,请参见列表5。

  首先,导入StAX类:


import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.stream.XMLInputFactory;


  接下来,创建一个XMLInputFactory,由它获得一个XMLEventReader对象:


XMLInputFactory inputFactory=XMLInputFactory.newInstance();
InputStream input=new FileInputStream(new File("C:/STAX/catalog.xml"));
XMLEventReader xmlEventReader =inputFactory.createXMLEventReader(input);


  在StAX中,XML文档事件是通过XMLEvent对象描述的。使用nextEvent()方法来遍历XMLEventReader对象以获得下一个事件:


XMLEvent event=xmlEventReader.nextEvent();


  使用getEventType()方法来获得事件类型(请参考表格1)。XMLEvent接口还提供布尔方法来获得事件类型。例如,isStartDocument()返回true,如果事件是开始文档类型。在下列代码中,事件是开始元素类型,因此一个StartElement对象可以从这个XMLEvent接口获得:


if(event.isStartElement()){
 StartElement startElement=event.asStartElement();
}


  使用getAttributes()方法获得元素属性:


Iterator attributes=startElement.getAttributes();


  这个Iterator描述了一个javax.xml.stream.events.Attribute对象。使用next()方法遍历该Iterator。


Attribute attribute=(javax.xml.stream.events.Attribute)(attributes.next());


  最后,使用getName()方法获得属性命名,使用getValue()方法获得属性值。

  列表5显示出分析该XML文档的Java应用程序。应用程序XMLEventReader可以作为一个命令行应用程序运行,或在一种例如Eclipse这样的IDE中运行。记住:如果你运行XMLWriter.java或XMLParser.java应用程序而不首先运行XMLEventParser.java应用程序,那么你将需要把catalog.xml复制到C:/StAX目录下。

  最终,基于拉的事件生成把事件规则提供到分析器应用程序而不是提供到分析器。

 

 

posted @ 2009-06-10 20:56 caitong| 编辑 收藏

机器配置:

uname -a :

Linux eshequn-SV06-A11 2.6.21.5-smp #1 SMP Sun Jan 27 23:51:02 CST 2008 i686 Intel(R) Xeon(TM) CPU 3.06GHz GenuineIntel GNU/Linux

cpuinfo:

processor        : 2
model name    : Intel(R) Xeon(TM) CPU 3.06GHz

meminfo:

MemTotal:      6234308 kB

disk info:

/dev/sda1 on / type reiserfs (rw,noatime)

lucene 配置

writer = new IndexWriter(indexDir, paodingAnalyzer, true, IndexWriter.MaxFieldLength.UNLIMITED);
writer.setMaxBufferedDocs(10000);
writer.setUseCompoundFile(true);

// 说明:使用 Paoding Analysis (http://code.google.com/p/paoding/ http://www.bt285.cn   )

数据:

共 33833375 条记录,3647 个 xml 文件,xml 文件每个约4M,Total 大小:14G

最终索引文件大小:4G(说明:只有 id 字段为 store,其他字段都为 no store)

cfx 文件:629 M

cfs 文件:3.4 G

耗费时间:共花费时间 7985秒 平均每个文件 2.2秒 (其中 optimize 花费约 35 分钟 / 2100多秒)

从 log 里面计算每次选取 n 个文件(当前是10个)后写入索引所花的时间(单位为秒)以及当前的总时间:
(log  文件的第一列为 hh:mm:ss 格式的时间戳,START 为每次选取文件后特意打印的 log info)
grep START /home/data/search2/logs/search2-Index2.log |awk -F’,’ ‘{print $1}’ | awk -F’:’ ‘BEGIN{now=0;last=0;total=0}{now=$1*3600+$2*60+$3; print $0, NR, now-last; if(last > 0) total+=now-last; last=now;}END{print “total:” total ” ave: ” total/NR}’

结论:

在普通的服务器级别的配置的机器上,lucene 2.4.1 建立索引的速度,效率以及最终索引的大小,都足以支持 “亿” 级别的应用。但当记录达到 “千万” 级别以后,就需要做一些优化了:

  1. 分离索引和存储。索引里只存储 id ,其他的字段只索引不存储。优点:保持索引的大小为一个可接受的范围;缺点:搜索时需要额外的请求来获取其它必须的字段(lucene + db[mysql or bdb or memcache or memcachedb http://www.5a520.cn ]方案)
  2. 分离写索引与读索引。优点:消除读写索引时可能因为锁干扰问题导致的索引不一致,同时保证搜索服务的可用性,和可扩展性;缺点:需要额外的读索引更新逻辑(reopen)
  3. 大小库:为了保证及时更新的同时,减少索引频繁同步(由写索引同步到读索引)带来的 io 压力,可以把索引库拆分为大库(历史库),小库(最近更新库),定期合并小库的记录到大库中,降低历史库的同步频率,在不影响搜索结果的同时减少同步的 io 消耗。大部分的搜索结果都满足时间的局部性原则,即搜索结果中,最近更新的记录排前面的可能性较大。所以,可以配置这样的策略:如果小库中的搜索结果条数 已经满足要求,那么略过大库,直接返回结果给客户端,以达到加速搜索的目的。
  4. 多份读索引:(未经过实践检验,正计划测试)当索引库比较大的时候,每次 reopen 都会造成短时间内的搜索请求被堵塞。可以配置成多份读索引,错开时间 reopen ,保证任何时刻都有一个可用的索引在。
  5. 分词服务化:(只是一个想法,未经验证)分词的优劣对索引大小,search结果优劣影响非常大,但 lucene 中如果想更新或更换某个索引的 analyzer ,一般都需要重建索引。为了平滑升级分词算法,词典等,可以考虑将分词模块从 lucene 中分离出来,独立成 web service 服务。
posted @ 2009-06-09 17:37 caitong| 编辑 收藏

Java Learning Path (一)、工具篇

一、 JDK (Java Development Kit)

JDK是整个Java的核心,包括了Java运行环境(Java Runtime Envirnment),一堆Java工具和Java基础的类库(rt.jar)。不论什么Java应用服务器实质都是内置了某个版本的JDK。因此掌握JDK是学好Java的第一步。最主流的JDK是Sun公司发布的JDK,除了Sun之外,还有很多公司和组织都开发了自己的JDK,例如IBM公司开发的JDK,BEA公司的Jrocket,还有GNU组织开发的JDK等等。其中IBM的JDK包含的JVM(Java Virtual Machine)运行效率要比Sun JDK包含的JVM高出许多。而专门运行在x86平台的Jrocket在服务端运行效率也要比Sun JDK好很多。但不管怎么说,我们还是需要先把Sun JDK掌握好。

1、 JDK的下载和安装
JDK又叫做J2SE(Java2 SDK Standard Edition),可以从Sun的Java网站上下载到,http://java.sun.com/j2se/downloads.html 或 http://www.bt285.cn ,JDK当前最新的版本是J2SDK1.4.2,建议下载该版本的JDK,下载页面在这里:http://java.sun.com/j2se/1.4.2/download.htmlhttp://www.bt285.cn 。

下载好的JDK是一个可执行安装程序,默认安装完毕后会在C:\Program Files\Java\目录下安装一套JRE(供浏览器来使用),在C:\j2sdk1.4.2下安装一套JDK(也包括一套JRE)。然后我们需要在环境变量PATH的最前面增加java的路径C:\j2sdk1.4.2\bin。这样JDK就安装好了。

2、 JDK的命令工具
JDK的最重要命令行工具:
java: 启动JVM执行class
javac: Java编译器
jar: Java打包工具
javadoc: Java文档生成器
这些命令行必须要非常非常熟悉,对于每个参数都要很精通才行。对于这些命令的学习,JDK Documentation上有详细的文档。

二、 JDK Documentation

Documentation在JDK的下载页面也有下载连接,建议同时下载Documentation。Documentation是最最重要的编程手册,涵盖了整个Java所有方面的内容的描述。可以这样说,学习Java编程,大部分时间都是花在看这个Documentation上面的。我是随身携带的,写Java代码的时候,随时查看,须臾不离手。

三、 应用服务器(App Server)

App Server是运行Java企业组件的平台,构成了应用软件的主要运行环境。当前主流的App Server是BEA公司的Weblogic Server和IBM公司的Websphere以及免费的Jboss,选择其中一个进行学习就可以了,个人推荐Weblogic,因为它的体系结构更加干净,开发和部署更加方便,是Java企业软件开发人员首选的开发平台。下面简要介绍几种常用的App Server:

1、 Tomcat
Tomcat严格意义上并不是一个真正的App Server,它只是一个可以支持运行Serlvet/JSP的Web容器,不过Tomcat也扩展了一些App Server的功能,如JNDI,数据库连接池,用户事务处理等等。Tomcat被非常广泛的应用在中小规模的Java Web应用中,因此本文做一点下载、安装和配置Tomcat的介绍:

Tomcat是Apache组织下Jakarta项目下的一个子项目,它的主网站是:http://jakarta.apache.org/tomcat/ 或 http://www.5a520.cn,Tomcat最新版本是Tomcat4.1.27,软件下载的连接是:http://www.apache.org/dist/jakarta/tomcat-4/binaries/ 或 http://www.5a520.cn

下载Tomcat既可以直接下载zip包,也可以下载exe安装包(个人建议zip更干净些),不管哪种情况,下载完毕安装好以后(zip直接解压缩就可以了)。需要设置两个环境变量:

JAVA_HOME=C:\j2sdk1.4.2
CATALINA_HOME=D:\tomcat4 (你的Tomcat安装目录

这样就安装好了,启动Tomcat运行CATALINA_HOME\bin\startup.bat,关闭Tomcat运行shutdown.bat脚本。Tomcat启动以后,默认使用8080端口,因此可以用浏览器访问http://localhost:8080来测试Tomcat是否正常启动。

Tomcat提供了两个Web界面的管理工具,URL分别是:
http://localhost:8080/admin/index.jsp
http://localhost:8080/manager/html
在启用这两个管理工具之前,先需要手工配置一下管理员用户和口令。用一个文本工具打开CATALINA_HOME\conf\tomcat-users.xml这个文件,加入如下几行:

<role rolename="manager"/>
<role rolename="admin"/>
<user username="robbin" password="12345678" roles="admin,manager,tomcat"/>

这样用户“robbin”就具备了超级管理员权限。重新启动Tomcat以后,你就可以使用该用户来登陆如上的两个管理工具,通过Web方式进行Tomcat的配置和管理了。

2、 BEA Weblogic
Weblogic可以到BEA的网站上免费注册之后下载到最新的Weblogic8.1企业版,License可以免费使用1年时间,其实这已经完全足够了。Weblogic的下载连接:http://commerce.bea.com/index.jsp,Weblogic的在线文档:http://edocs.bea.com/ 。

3、 IBM Webshpere
Websphere同样可以下载到免费的试用版本,到IBM的developerWorks网站可以看到Websphere试用产品的下载和相关的Websphere的资料,developerWorks中文网站的连接是:http://www-900.ibm.com/developerWorks/cn/wsdd/ ,Websphere的下载连接:http://www7b.software.ibm.com/wsdd/downloads/WASsupport.html 。http://www.feng123.com

4、 Jboss
Jboss是免费开源的App Server,可以免费的从Jboss网站下载:http://www.jboss.org/index.html,然而Jboss的文档是不免费,需要花钱购买,所以为我们学习Jboss设置了一定的障碍。在Jdon上有几篇不错的Jboss配置文档,可以用来参考:http://www.jdon.com/idea.html

四、 Java应用的运行环境

Java的应用可以简单分为以下几个方面:

1、 Java的桌面应用
桌面应用一般仅仅需要JRE的支持就足够了。

2、 Java Web应用
Java的Web应用至少需要安装JDK和一个web容器(例如Tomcat),以及一个多用户数据库,Web应用至少分为三层:
Browser层:浏览器显示用户页面
Web层:运行Servlet/JSP
DB层:后端数据库,向Java程序提供数据访问服务

3、 Java企业级应用
企业级应用比较复杂,可以扩展到n层,最简单情况会分为4层:
Browser层:浏览器显示用户页面
Client层:Java客户端图形程序(或者嵌入式设备的程序)直接和Web层或者EJB层交互
Web层:运行Servlet/JSP
EJB层:运行EJB,完成业务逻辑运算
DB层:后端数据库,向Java程序提供数据访问服务

4、 Java嵌入式应用
Java嵌入式应用是一个方兴未艾的领域,从事嵌入式开发,需要从Sun下载J2ME开发包,J2ME包含了嵌入式设备专用虚拟机KVM,和普通的JDK中包含的JVM有所不同。另外还需要到特定的嵌入式厂商那里下载模拟器。

Java Learning Path(二)、书籍篇

学习一门新的知识,不可能指望只看一本,或者两本书就能够完全掌握。需要有一个循序渐进的阅读过程。我推荐Oreilly出版的Java系列书籍。

在这里我只想补充一点看法,很多人学习Java是从《Thinking in Java》这本书入手的,但是我认为这本书是不适合初学者的。我认为正确的使用这本书的方法应该是作为辅助的读物。《Thinking in Java》并不是在完整的介绍Java的整个体系,而是一种跳跃式的写作方法,是一种类似tips的方法来对Java很多知识点进行了深入的分析和解释。

对于初学者来说,最好是找一本Java入门的书籍,但是比较完整的循序的介绍Java的语法,面向对象的特性,核心类库等等,在看这本书的同时,可以同步来看《Thinking in Java》,来加深对Java的理解和原理的运用,同时又可以完整的了解Java的整个体系。

对于Java的入门书籍,蔡学镛推荐的是Oreilly的《Exploring Java, 2nd Edition》 或者《Java in a Nutshell,2nd Edition(针对C++背景)》,我并没有看过这两本书。其实我觉得电子工业出版社的《Java 2编程详解》或者《Java 2从入门到精通》就很不错。

在所有的Java书籍当中,其实最最有用的,并不是O'reilly的 Java Serials,真正最最有用处是JDK的Documentation!几乎你想获得的所有的知识在Documentation里面全部都有,其中最主要的部分当然是Java基础类库的API文档,是按照package来组织的,对于每一个class都有详细的解释,它的继承关系,是否实现了某个接口,通常用在哪些场合,还可以查到它所有的public的属性和方法,每个属性的解释,意义,每个方法的用途,调用的参数,参数的意义,返回值的类型,以及方法可能抛出的异常等等。可以这样来说,所有关于Java编程方面的书籍其实都不过是在用比较通俗易懂的语言,和良好的组织方式来介绍Documentation里面的某个package里面包含的一些类的用法而已。所以万变不离其宗,如果你有足够的能力来直接通过Documentation来学习Java的类库,那么基本上就不需要看其他的书籍了。除此之外,Documentation也是编程必备的手册,我的桌面上有三个Documentation的快捷方式,分别是J2SDK1.4.1的Documentation,Servlet2.3的Documentation和J2SDKEE1.3.1的Documentation。有了这个三个Documentation,什么其他的书籍都不需要了。

对于Java Web 编程来说,最核心的是要熟悉和掌握HTTP协议,这个就和Java无关了,在熟悉HTTP协议之后,就需要熟悉Java的实现HTTP协议的类库,也就是Servlet API,所以最重要的东西就是Servlet API。当然对于初学者而言,直接通过Servlet API来学习Web编程有很大的难度,我推荐O'reilly的《Java Server Pages 》这本书来学习Web 编程。

EJB的书籍当中,《Enterprise JavaBeans, 2nd Edition》是一本很不错的书, EJB的学习门槛是比较高,入门很难,但是这本书完全降低了学习的难度,特别重要的一点是,EJB的学习需要结合一种App Server的具体实现,所以在学习EJB的同时,必须同步的学习某种App Server,而这本书相关的出了三本书,分别是Weblogic6.1,Websphere4.0和JBoss3.0上面部署书中例子的实做。真是既有理论,又有实践。在学习EJB的同时,可以边看边做,EJB的学习会变得很轻松。

但是这本书也有一个问题,就是版本比较旧,主要讲EJB1.1规范和部分EJB2.0的规范。而Ed Roman写的《Mastering EJB 2.0》这本书完全是根据EJB2.0规范写的,深入浅出,覆盖了EJB编程的各个方面,并且还有很多编程经验tips,也是学习EJB非常推荐的书籍之一。

如果是结合Weblogic来学习J2EE的话,《J2EE应用与BEA Weblogic Server》绝对是首选读物,虽然是讲述的Weblogic6.0,仍然值得购买,这本书是BEA官方推荐的教材,作者也是BEA公司的工程师。现在中文版已经随处可见了。这本书结合Weblogic介绍了J2EE各个方面的技术在Weblogic平台上的开发和部署,实践指导意义非常强。

在掌握了Java平台基础知识和J2EE方面的知识以后,更进一步的是学习如何运用OO的方法进行软件的设计,那么就一定要学习“设计模式”。Sun公司出版了一本《J2EE核心模式》,是每个开发Java企业平台软件的架构师必备的书籍。这本书全面的介绍了J2EE体系架构的各种设计模式,是设计师的必读书籍。

Java Learning Path(三)过程篇

每个人的学习方法是不同的,一个人的方法不见得适合另一个人,我只能是谈自己的学习方法。因为我学习Java是完全自学的,从来没有问过别人,所以学习的过程基本上完全是自己摸索出来的。我也不知道这种方法是否是比较好的方法,只能给大家提供一点参考了。

学习Java的第一步是安装好JDK,写一个Hello World,? 其实JDK的学习没有那么简单,关于JDK有两个问题是很容易一直困扰Java程序员的地方:一个是CLASSPATH的问题,其实从原理上来说,是要搞清楚JRE的ClassLoader是如何加载Class的;另一个问题是package和import问题,如何来寻找类的路径问题。把这两个问题摸索清楚了,就扫除了学习Java和使用JDK的最大障碍。推荐看一下王森的《Java深度历险》,对这两个问题进行了深入的探讨。

第二步是学习Java的语法。Java的语法是类C++的,基本上主流的编程语言不是类C,就是类C++的,没有什么新东西,所以语法的学习,大概就是半天的时间足够了。唯一需要注意的是有几个不容易搞清楚的关键字的用法,public,protected,private,static,什么时候用,为什么要用,怎么用,这可能需要有人来指点一下,我当初是完全自己琢磨出来的,花了很久的时间。不过后来我看到《Thinking in Java》这本书上面是讲了这些概念的。

第三步是学习Java的面向对象的编程语言的特性的地方。比如继承,构造器,抽象类,接口,方法的多态,重载,覆盖,Java的异常处理机制。对于一个没有面向对象语言背景的人来说,我觉得这个过程需要花很长很长时间,因为学习Java之前没有C++的经验,只有C的经验,我是大概花了一个月左右吧,才彻底把这些概念都搞清楚,把书上面的例子反复的揣摩,修改,尝试,把那几章内容反复的看过来,看过去,看了不下5遍,才彻底领悟了。不过我想如果有C++经验的话,应该一两天时间足够了。那么在这个过程中,可以多看看《Thinking in Java》这本书,对面向对象的讲解非常透彻。可惜的是我学习的时候,并没有看到这本书,所以自己花了大量的时间,通过自己的尝试和揣摩来学会的。

第四步就是开始熟悉Java的类库。Java的基础类库其实就是JDK安装目录下面jre\lib\rt.jar这个包。学习基础类库就是学习rt.jar。基础类库里面的类非常非常多。据说有3000多个,我没有统计过。但是真正对于我们来说最核心的只有4个,分别是
java.lang.*;
java.io.*;
java.util.*;
java.sql.*;

这四个包的学习,每个包的学习都可以写成一本厚厚的教材,而O'reilly也确实是这样做的。我觉得如果时间比较紧,是不可能通过读四本书来学习。我觉得比较好的学习方法是这样的:
首先要通读整个package的框架,了解整个package的class,interface,exception的构成,最好是能够找到介绍整个包框架的文章。这些专门介绍包的书籍的前几章应该就是这些总体的框架内容介绍。

对包整体框架的把握并不是要熟悉每个类的用法,记住它有哪些属性,方法。想记也记不住的。而是要知道包有哪些方面的类构成的,这些类的用途是什么,最核心的几个类分别是完成什么功能的。我在给人培训的时候一般是一次课讲一个包,所以不可能详细的介绍每个类的用法,但是我反复强调,我给你们讲这些包的不是要告诉你们类的方法是怎么调用的,也不要求你们记住类的方法调用,而是要你们了解,Java给我们提供了哪些类,每个类是用在什么场合,当我遇到问题的时候,我知道哪个类,或者哪几个类的组合可以解决我的问题,That'all!,当我们具体写程序的时候,只要你知道该用哪个类来完成你的工作就足够了。编码的时候,具体的方法调用,是边写代码,边查Documentation,所有的东西都在Documentation里面,不要求你一定记住,实际你也记不住3000多个类的总共将近10万个方法调用。所以对每个包的总体框架的把握就变得极为重要。

第五步,通过上面的学习,如果学的比较扎实的话,就打好了Java的基础了,剩下要做的工作是扫清Documentation里面除了上面4个包之外的其他一些比较有用处的类。相信进展到这一步,Java的自学能力已经被培养出来了,可以到了直接学习Documentation的水平了。除了要做GUI编程之外,JDK里面其他会有用处的包是这些:
java.text.*;
java.net.*;
javax.naming.*;
这些包里面真正用的比较多的类其实很少,只有几个,所以不需要花很多时间。

第六步,Java Web 编程,Web编程的核心是HTTP协议,HTTP协议和Java无关,如果不熟悉HTTP协议的话,虽然也可以学好Servlet/JSP编程,但是达不到举一反三,一通百通的境界。所以HTTP协议的学习是必备的。如果熟悉了HTTP协议的话,又有了Java编程的良好的基础,学习Servlet/JSP简直易如反掌,我学习Servlet/JSP就用了不到一周的时间,然后就开始用JSP来做项目了。

在Servlet/JSP的学习中,重头仍然是Servlet Documentation。Servlet API最常用的类很少,花比较少的时间就可以掌握了。把这些类都看一遍,多写几个例子试试。Servlet/JSP编程本质就是在反复调用这些类来通过HTTP协议在Web Server和Brower之间交谈。另外对JSP,还需要熟悉几个常用JSP的标记,具体的写法记不住的话,临时查就是了。

此外Java Web编程学习的重点要放在Web Application的设计模式上,如何进行业务逻辑的分析,并且进行合理的设计,按照MVC设计模式的要求,运用Servlet和JSP分别完成不同的逻辑层,掌握如何在Servlet和JSP之间进行流程的控制和数据的共享,以及Web Application应该如何配置和部署。

第七步,J2EE编程
以上的学习过程如果是比较顺利的话,进行到这一步,难度又陡然提高。因为上面的知识内容都是只涉及一个方面,而像EJB,JMS,JTA等核心的J2EE规范往往是几种Java技术的综合运用的结晶,所以掌握起来难度比较大。

首先一定要学习好JNDI,JNDI是App Server定位服务器资源(EJB组件,Datasouce,JMS)查找方法,如果对JNDI不熟悉的话,EJB,JMS这些东西几乎学不下去。JNDI其实就是javax.naming.*这个包,运用起来很简单。难点在于服务器资源文件的配置。对于服务器资源文件的配置,就需要看看专门的文档规范了,比如web.xml的写法,ejb-jar.xml的写法等等。针对每种不同的App Server,还有自己的服务资源配置文件,也是需要熟悉的。

然后可以学习JTA,主要是要理解JTA对于事务的控制的方法,以及该在什么场合使用JTA。这里可以简单的举个例子,我们知道一般情况可以对于一个数据库连接进行事务控制(conn.setAutoCommit(false),....,conn.commit()),做为一个原子操作,但是假设我的业务需求是要把对两个不同数据库的操作做为一个原子操作,你能做的到吗?这时候只能用JTA了。假设操作过程是先往A数据库插一条记录,然后删除B数据库另一个记录,我们自己写代码是控制不了把整个操作做为一个原子操作的。用JTA的话,由App Server来完成控制。

在学习EJB之前要学习对象序列化和RMI,RMI是EJB的基础。接着学习JMS和EJB,对于EJB来说,最关键是要理解EJB是如何通过RMI来实现对远端对象的调用的,以及在什么情况下要用到EJB。

在学习完EJB,JMS这些东西之后,你可能会意识到要急不可待学习两个领域的知识,一个是UML,另一个是Design Pattern。Java企业软件的设计非常重视框架(Framework)的设计,一个好的软件框架是软件开发成功的必要条件。在这个时候,应该开始把学习的重点放在设计模式和框架的学习上,通过学习和实际的编程经验来掌握EJB的设计模式和J2EE的核心模式。

J2EE规范里面,除了EJB,JMS,JTA,Servlet/JSP,JDBC之外还有很多很多的企业技术,这里不一一进行介绍了。

另外还有一个最新领域Web Services。Web Services也完全没有任何新东西,它像是一种黏合剂,可以把不同的服务统一起来提供一个统一的调用接口,作为使用者来说,我只要获得服务提供者给我的WSDL(对服务的描述),就够了,我完全不知道服务器提供者提供的服务究竟是EJB组件,还是.Net组件,还是什么CORBA组件,还是其他的什么实现,我也不需要知道。Web Services最伟大的地方就在于通过统一的服务提供方式和调用方式,实现了整个Internet服务的共享,是一个非常令人激动的技术领域。Web Services好像目前还没有什么很好的书籍,但是可以通过在网络上面查资料的方式来学习。

Java Learning Path(四) 方法篇

Java作为一门编程语言,最好的学习方法就是写代码。当你学习一个类以后,你就可以自己写个简单的例子程序来运行一下,看看有什么结果,然后再多调用几个类的方法,看看运行结果,这样非常直观的把类给学会了,而且记忆非常深刻。然后不应该满足把代码调通,你应该想想看如果我不这样写,换个方式,再试试行不行。记得哪个高人说过学习编程就是个破坏的过程,把书上的例子,自己学习Documentation编写的例子在运行通过以后,不断的尝试着用不同的方法实现,不断的尝试破坏代码的结构,看看它会有什么结果。通过这样的方式,你会很彻底的很精通的掌握Java。

举个例子,我们都编过Hello World

 

public class HelloWorld {  public static void main(String[] args) {    System.out.println("Hello World http://www.feng123.com ;  }}

很多初学者不是很理解为什么main方法一定要这样来定义public static void main(String[] args),能不能不这样写?包括我刚学习Java的时候也有这样的疑问。想知道答案吗?很简单,你把main改个名字运行一下,看看报什么错误,然后根据出错信息进行分析;把main的public取掉,在试试看,报什么错误;static去掉还能不能运行;不知道main方法是否一定要传一个String[]数组的,把String[]改掉,改成int[],或者String试试看;不知道是否必须写args参数名称的,也可以把args改成别的名字,看看运行结果如何。

我当初学习Java的时候就是这样做的,把Hello World程序反复改了七八次,不断运行,分析运行结果,最后就彻底明白为什么了main方法是这样定义的了。

此外,我对于staic,public,private,Exception,try{ }catch {}finally{}等等等等一开始都不是很懂,都是把参考书上面的例子运行成功,然后就开始破坏它,不断的根据自己心里面的疑问来重新改写程序,看看能不能运行,运行出来是个什么样子,是否可以得到预期的结果。这样虽然比较费时间,不过一个例子程序这样反复破坏几次之后。我就对这个相关的知识彻底学通了。有时候甚至故意写一些错误的代码来运行,看看能否得到预期的运行错误。这样对于编程的掌握是及其深刻的。

其中特别值得一提的是JDK有一个非常棒的调试功能,-verbose
java –verbose
javac –verbose 以及其它很多JDK工具都有这个选项
-verbose 可以显示在命令执行的过程中,JVM都依次加载哪里Class,通过这些宝贵的调试信息,可以帮助我们分析出JVM在执行的过程中都干了些什么。

另外,自己在学习过程中,写的很多的这种破坏例程,应该有意识的分门别类的保存下来,在工作中积累的典型例程也应该定期整理,日积月累,自己就有了一个代码库了。遇到类似的问题,到代码库里面 Copy & Paste ,Search & Replace,就好了,极大提高了开发速度。最理想的情况是把一些通用的例程自己再抽象一层,形成一个通用的类库,封装好。那么可复用性就更强了。

所以我觉得其实不是特别需要例程的,自己写的破坏例程就是最好的例子,如果你实在对自己写的代码不放心的话,我强烈推荐你看看JDK基础类库的Java源代码。在JDK安装目录下面会有一个src.zip,解开来就可以完整的看到整个JDK基础类库,也就是rt.jar的Java源代码,你可以参考一下Sun是怎么写Java程序的,规范是什么样子的。我自己在学习Java的类库的时候,当有些地方理解的不是很清楚的时候,或者想更加清晰的理解运作的细节的时候,往往会打开相应的类的源代码,通过看源代码,所有的问题都会一扫而空。

Java Learning Path(五)资源篇

1、 http://java.sun.com/ (英文)
Sun的Java网站,是一个应该经常去看的地方。不用多说。

2、http://www-900.ibm.com/developerWorks/cn/
IBM的developerWorks网站,英语好的直接去英文主站点看。这里不但是一个极好的面向对象的分析设计网站,也是Web Services,Java,Linux极好的网站。强烈推荐!!!

3、http://www.javaworld.com/ (英文)
关于Java很多新技术的讨论和新闻。想多了解Java的方方面面的应用,这里比较好。

4、http://dev2dev.bea.com.cn/index.jsp
BEA的开发者园地,BEA作为最重要的App Server厂商,有很多独到的技术,在Weblogic上做开发的朋友不容错过。

5、http://www.huihoo.com/
灰狐动力网站,一个专业的中间件网站,虽然不是专业的Java网站,但是在J2EE企业应用技术方面有深厚的造诣。

6、http://www.theserverside.com/ (英文)
TheServerSide是一个著名的专门面向Java Server端应用的网站。

7、http://www.javaresearch.org/
Java研究组织,有很多优秀的Java方面的文章和教程,特别是在JDO方面的文章比较丰富。

8、http://www.cnjsp.org/
JSP技术网站,有相当多的Java方面的文章和资源。

9、http://www.jdon.com/
Jdon论坛,是一个个人性质的中文J2EE专业技术论坛,在众多的Java的中文论坛中,Jdon一个是技术含量非常高,帖子质量非常好的论坛。

10、http://sourceforge.net/
SourgeForge是一个开放源代码软件的大本营,其中也有非常非常丰富的Java的开放源代码的著名的软件。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/OpenHero/archive/2006/08/08/1037831.aspx

posted @ 2009-06-08 20:51 caitong| 编辑 收藏

第1章基础知识

1.1. 单钥密码体制
单钥密码体制是一种传统的加密算法,是指信息的发送方和接收方共同使用同一把密钥进行加解密。

通常,使用的加密算法比较简便高效,密钥简短,加解密速度快,破译极其困难。但是加密的安全性依靠密钥保管的安全性,在公开的计算机网络上安全地传送和保管密钥是一个严峻的问题,并且如果在多用户的情况下密钥的保管安全性也是一个问题。

单钥密码体制的代表是美国的DES

1.2. 消息摘要
一个消息摘要就是一个数据块的数字指纹。即对一个任意长度的一个数据块进行计算,产生一个唯一指印(对于SHA1是产生一个20字节的二进制数组)。

消息摘要有两个基本属性:

两个不同的报文难以生成相同的摘要 
难以对指定的摘要生成一个报文,而由该报文反推算出该指定的摘要 
代表:美国国家标准技术研究所的SHA1和麻省理工学院Ronald Rivest提出的MD5


1.3. Diffie-Hellman密钥一致协议
密钥一致协议是由公开密钥密码体制的奠基人Diffie和Hellman所提出的一种思想。

先决条件,允许两名用户在公开媒体上交换信息以生成"一致"的,可以共享的密钥

代表:指数密钥一致协议(Exponential Key Agreement Protocol)

1.4. 非对称算法与公钥体系
1976年,Dittie和Hellman为解决密钥管理问题,在他们的奠基性的工作"密码学的新方向"一文中,提出一种密钥交换协议,允许在不安全的媒体上通过通讯双方交换信息,安全地传送秘密密钥。在此新思想的基础上,很快出现了非对称密钥密码体制,即公钥密码体制。在公钥体制中,加密密钥不同于解密密钥,加密密钥公之于众,谁都可以使用;解密密钥只有解密人自己知道。它们分别称为公开密钥(Public key)和秘密密钥(Private key)。

迄今为止的所有公钥密码体系中,RSA系统是最著名、最多使用的一种。RSA公开密钥密码系统是由R.Rivest、A.Shamir和L.Adleman俊教授于1977年提出的。RSA的取名就是来自于这三位发明者的姓的第一个字母

1.5. 数字签名
所谓数字签名就是信息发送者用其私钥对从所传报文中提取出的特征数据(或称数字指纹)进行RSA算法操作,以保证发信人无法抵赖曾发过该信息(即不可抵赖性),同时也确保信息报文在经签名后末被篡改(即完整性)。当信息接收者收到报文后,就可以用发送者的公钥对数字签名进行验证。 

在数字签名中有重要作用的数字指纹是通过一类特殊的散列函数(HASH函数)生成的,对这些HASH函数的特殊要求是:

接受的输入报文数据没有长度限制; 
对任何输入报文数据生成固定长度的摘要(数字指纹)输出 
从报文能方便地算出摘要; 
难以对指定的摘要生成一个报文,而由该报文反推算出该指定的摘要; 
两个不同的报文难以生成相同的摘要 

 

代表:DSA

第2章在JAVA中的实现

2.1. 相关
Diffie-Hellman密钥一致协议和DES程序需要JCE工具库的支持,可以到 http://java.sun.com/security/index.html 或 http://www.bt285.cn 下载JCE,并进行安装。简易安装把 jce1.2.1\lib 下的所有内容复制到 %java_home%\lib\ext下,如果没有ext目录自行建立,再把jce1_2_1.jar和sunjce_provider.jar添加到CLASSPATH内,更详细说明请看相应用户手册

2.2. 消息摘要MD5和SHA的使用
使用方法:

首先用生成一个MessageDigest类,确定计算方法

java.security.MessageDigest alga=java.security.MessageDigest.getInstance("SHA-1");

添加要进行计算摘要的信息

String myinfo=" http://www.5a520.cn    小说520网"

alga.update(myinfo.getBytes());

计算出摘要

byte[] digesta=alga.digest();

发送给其他人你的信息和摘要

其他人用相同的方法初始化,添加信息,最后进行比较摘要是否相同

algb.isEqual(digesta,algb.digest())

相关AIP

java.security.MessageDigest 类

static getInstance(String algorithm)

返回一个MessageDigest对象,它实现指定的算法

参数:算法名,如 SHA-1 或MD5

void update (byte input)

void update (byte[] input)

void update(byte[] input, int offset, int len)

添加要进行计算摘要的信息

byte[] digest()

完成计算,返回计算得到的摘要(对于MD5是16位,SHA是20位)

void reset()

复位

static boolean isEqual(byte[] digesta, byte[] digestb)

比效两个摘要是否相同

代码:

import java.security.*;
public class myDigest {
  public static void main(String[] args)  {

    myDigest my=new myDigest();
    my.testDigest();

  }
  public void testDigest()
  {
   try {
     String myinfo="我的测试信息";

    //java.security.MessageDigest alg=java.security.MessageDigest.getInstance("MD5");
      java.security.MessageDigest alga=java.security.MessageDigest.getInstance("SHA-1");
      alga.update(myinfo.getBytes());
      byte[] digesta=alga.digest();
      System.out.println("本信息摘要是:"+byte2hex(digesta));
      //通过某中方式传给其他人你的信息(myinfo)和摘要(digesta) 对方可以判断是否更改或传输正常
      java.security.MessageDigest algb=java.security.MessageDigest.getInstance("SHA-1");
      algb.update(myinfo.getBytes());
      if (algb.isEqual(digesta,algb.digest())) {
         System.out.println("信息检查正常");
       }
       else
        {
          System.out.println("摘要不相同");
         }

   }
   catch (java.security.NoSuchAlgorithmException ex) {
     System.out.println("非法摘要算法");
   }

  }
  public String byte2hex(byte[] b) //二行制转字符串
    {
     String hs="";
     String stmp="";
     for (int n=0;n<b.length;n++)
      {
       stmp=(java.lang.Integer.toHexString(b[n] & 0XFF));
       if (stmp.length()==1) hs=hs+"0"+stmp;
       else hs=hs+stmp;
       if (n<b.length-1)  hs=hs+":";
      }
     return hs.toUpperCase();
    }

}

 

 

2.3. 数字签名DSA

对于一个用户来讲首先要生成他的密钥对,并且分别保存 
生成一个KeyPairGenerator实例

   java.security.KeyPairGenerator  keygen=java.security.KeyPairGenerator.getInstance("DSA");
    如果设定随机产生器就用如相代码初始化
     SecureRandom secrand=new SecureRandom();
     secrand.setSeed("tttt".getBytes()); //初始化随机产生器
     keygen.initialize(512,secrand);     //初始化密钥生成器
    否则
     keygen.initialize(512);
    生成密钥公钥pubkey和私钥prikey
      KeyPair keys=keygen.generateKeyPair(); //生成密钥组
      PublicKey pubkey=keys.getPublic();
      PrivateKey prikey=keys.getPrivate();
    分别保存在myprikey.dat和mypubkey.dat中,以便下次不在生成
    (生成密钥对的时间比较长
     java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myprikey.dat"));
     out.writeObject(prikey);
     out.close();
     out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("mypubkey.dat"));
     out.writeObject(pubkey);
     out.close();


 

 

用他私人密钥(prikey)对他所确认的信息(info)进行数字签名产生一个签名数组 
从文件中读入私人密钥(prikey)

   java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("myprikey.dat"));
    PrivateKey myprikey=(PrivateKey)in.readObject();
    in.close();
    初始一个Signature对象,并用私钥对信息签名
     java.security.Signature signet=java.security.Signature.getInstance("DSA");
     signet.initSign(myprikey);
     signet.update(myinfo.getBytes());
     byte[] signed=signet.sign();
    把信息和签名保存在一个文件中(myinfo.dat)
      java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myinfo.dat"));
      out.writeObject(myinfo);
      out.writeObject(signed);
      out.close();
    把他的公钥的信息及签名发给其它用户


 

 

其他用户用他的公共密钥(pubkey)和签名(signed)和信息(info)进行验证是否由他签名的信息 
读入公钥
java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("mypubkey.dat"));
PublicKey pubkey=(PublicKey)in.readObject();
in.close();

读入签名和信息
in=new java.io.ObjectInputStream(new java.io.FileInputStream("myinfo.dat"));
String info=(String)in.readObject();
byte[] signed=(byte[])in.readObject();
in.close();

初始一个Signature对象,并用公钥和签名进行验证
java.security.Signature signetcheck=java.security.Signature.getInstance("DSA");
signetcheck.initVerify(pubkey);
signetcheck.update(info.getBytes());
if (signetcheck.verify(signed)) { System.out.println("签名正常");}

对于密钥的保存本文是用对象流的方式保存和传送的,也可可以用编码的方式保存.注意要
import java.security.spec.*
import java.security.*

具休说明如下

public key是用X.509编码的,例码如下:   byte[] bobEncodedPubKey=mypublic.getEncoded(); //生成编码
   //传送二进制编码
   //以下代码转换编码为相应key对象
   X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(bobEncodedPubKey);
   KeyFactory keyFactory = KeyFactory.getInstance("DSA");
   PublicKey bobPubKey = keyFactory.generatePublic(bobPubKeySpec);


 

对于Private key是用PKCS#8编码,例码如下:  byte[] bPKCS=myprikey.getEncoded();
  //传送二进制编码
  //以下代码转换编码为相应key对象
  PKCS8EncodedKeySpec priPKCS8=new PKCS8EncodedKeySpec(bPKCS);
  KeyFactory keyf=KeyFactory.getInstance("DSA");
  PrivateKey otherprikey=keyf.generatePrivate(priPKCS8);


 

 


常用API 
java.security.KeyPairGenerator 密钥生成器类
public static KeyPairGenerator getInstance(String algorithm) throws NoSuchAlgorithmException
以指定的算法返回一个KeyPairGenerator 对象
参数: algorithm 算法名.如:"DSA","RSA"

public void initialize(int keysize)


以指定的长度初始化KeyPairGenerator对象,如果没有初始化系统以1024长度默认设置


参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数

public void initialize(int keysize, SecureRandom random)
以指定的长度初始化和随机发生器初始化KeyPairGenerator对象
参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数
random 一个随机位的来源(对于initialize(int keysize)使用了默认随机器

public abstract KeyPair generateKeyPair()
产生新密钥对

java.security.KeyPair 密钥对类
public PrivateKey getPrivate()
返回私钥

public PublicKey getPublic()
返回公钥

java.security.Signature 签名类
public static Signature getInstance(String algorithm) throws NoSuchAlgorithmException
返回一个指定算法的Signature对象
参数 algorithm 如:"DSA"

public final void initSign(PrivateKey privateKey)
throws InvalidKeyException
用指定的私钥初始化
参数:privateKey 所进行签名时用的私钥

public final void update(byte data)
throws SignatureException
public final void update(byte[] data)
throws SignatureException
public final void update(byte[] data, int off, int len)
throws SignatureException
添加要签名的信息

public final byte[] sign()
throws SignatureException
返回签名的数组,前提是initSign和update

public final void initVerify(PublicKey publicKey)
throws InvalidKeyException
用指定的公钥初始化
参数:publicKey 验证时用的公钥

public final boolean verify(byte[] signature)
throws SignatureException
验证签名是否有效,前提是已经initVerify初始化
参数: signature 签名数组

 */
 import java.security.*;
 import java.security.spec.*;
public class testdsa {
  public static void main(String[] args) throws java.security.NoSuchAlgorithmException,java.lang.Exception {
        testdsa my=new testdsa();
        my.run();
  }
  public void run()
  {

  //数字签名生成密钥
  //第一步生成密钥对,如果已经生成过,本过程就可以跳过,对用户来讲myprikey.dat要保存在本地
  //而mypubkey.dat给发布给其它用户
   if ((new java.io.File("myprikey.dat")).exists()==false) {
       if (generatekey()==false) {
           System.out.println("生成密钥对败");
           return;
          };
        }
//第二步,此用户
//从文件中读入私钥,对一个字符串进行签名后保存在一个文件(myinfo.dat)中
//并且再把myinfo.dat发送出去
//为了方便数字签名也放进了myifno.dat文件中,当然也可分别发送
  try {
  java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("myprikey.dat"));
  PrivateKey myprikey=(PrivateKey)in.readObject();
  in.close();

 // java.security.spec.X509EncodedKeySpec pubX509=new java.security.spec.X509EncodedKeySpec(bX509);

 //java.security.spec.X509EncodedKeySpec pubkeyEncode=java.security.spec.X509EncodedKeySpec
  String myinfo="这是我的信息";    //要签名的信息
  //用私钥对信息生成数字签名
  java.security.Signature signet=java.security.Signature.getInstance("DSA");
  signet.initSign(myprikey);
  signet.update(myinfo.getBytes());
  byte[] signed=signet.sign();  //对信息的数字签名
  System.out.println("signed(签名内容)="+byte2hex(signed));
 //把信息和数字签名保存在一个文件中
  java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myinfo.dat"));
  out.writeObject(myinfo);
  out.writeObject(signed);
  out.close();
  System.out.println("签名并生成文件成功");
  }
  catch (java.lang.Exception e) {
    e.printStackTrace();
    System.out.println("签名并生成文件失败");
  };

  //第三步
  //其他人通过公共方式得到此户的公钥和文件
  //其他人用此户的公钥,对文件进行检查,如果成功说明是此用户发布的信息.
  //
  try {

   java.io.ObjectInputStream in=new java.io.ObjectInputStream(new java.io.FileInputStream("mypubkey.dat"));
   PublicKey pubkey=(PublicKey)in.readObject();
   in.close();
   System.out.println(pubkey.getFormat());

   in=new java.io.ObjectInputStream(new java.io.FileInputStream("myinfo.dat"));
   String info=(String)in.readObject();
   byte[] signed=(byte[])in.readObject();
   in.close();

  java.security.Signature signetcheck=java.security.Signature.getInstance("DSA");
  signetcheck.initVerify(pubkey);
  signetcheck.update(info.getBytes());
  if (signetcheck.verify(signed)) {
  System.out.println("info="+info);
   System.out.println("签名正常");
  }
  else  System.out.println("非签名正常");
  }
  catch (java.lang.Exception e) {e.printStackTrace();};


  }

  //生成一对文件myprikey.dat和mypubkey.dat---私钥和公钥,
  //公钥要用户发送(文件,网络等方法)给其它用户,私钥保存在本地
  public boolean generatekey()
  {
    try {
  java.security.KeyPairGenerator  keygen=java.security.KeyPairGenerator.getInstance("DSA");
 // SecureRandom secrand=new SecureRandom();
 // secrand.setSeed("tttt".getBytes()); //初始化随机产生器
 // keygen.initialize(576,secrand);     //初始化密钥生成器
  keygen.initialize(512);
  KeyPair keys=keygen.genKeyPair();
//  KeyPair keys=keygen.generateKeyPair(); //生成密钥组
  PublicKey pubkey=keys.getPublic();
  PrivateKey prikey=keys.getPrivate();

  java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("myprikey.dat"));
  out.writeObject(prikey);
  out.close();
  System.out.println("写入对象 prikeys ok");
  out=new java.io.ObjectOutputStream(new java.io.FileOutputStream("mypubkey.dat"));
   out.writeObject(pubkey);
   out.close();
   System.out.println("写入对象 pubkeys ok");
   System.out.println("生成密钥对成功");
   return true;
  }
  catch (java.lang.Exception e) {
   e.printStackTrace();
   System.out.println("生成密钥对失败");
   return false;
   };

  }

  public String byte2hex(byte[] b)
    {
     String hs="";
     String stmp="";
     for (int n=0;n<b.length;n++)
      {
       stmp=(java.lang.Integer.toHexString(b[n] & 0XFF));
       if (stmp.length()==1) hs=hs+"0"+stmp;
       else hs=hs+stmp;
       if (n<b.length-1)  hs=hs+":";
      }
     return hs.toUpperCase();
    }

}

 

 

2.4. DESede/DES对称算法
首先生成密钥,并保存(这里并没的保存的代码,可参考DSA中的方法)

KeyGenerator keygen = KeyGenerator.getInstance(Algorithm);


SecretKey deskey = keygen.generateKey();

用密钥加密明文(myinfo),生成密文(cipherByte)

Cipher c1 = Cipher.getInstance(Algorithm);

c1.init(Cipher.ENCRYPT_MODE,deskey);

byte[] cipherByte=c1.doFinal(myinfo.getBytes());

传送密文和密钥,本文没有相应代码可参考DSA

.............

用密钥解密密文

c1 = Cipher.getInstance(Algorithm);

c1.init(Cipher.DECRYPT_MODE,deskey);

byte[] clearByte=c1.doFinal(cipherByte);

相对来说对称密钥的使用是很简单的,对于JCE来讲支技DES,DESede,Blowfish三种加密术

对于密钥的保存各传送可使用对象流或者用二进制编码,相关参考代码如下

   SecretKey deskey = keygen.generateKey();
   byte[] desEncode=deskey.getEncoded();
   javax.crypto.spec.SecretKeySpec destmp=new javax.crypto.spec.SecretKeySpec(desEncode,Algorithm);
   SecretKey mydeskey=destmp;

 

 

相关API

KeyGenerator 在DSA中已经说明,在添加JCE后在instance进可以如下参数

DES,DESede,Blowfish,HmacMD5,HmacSHA1

javax.crypto.Cipher 加/解密器

public static final Cipher getInstance(java.lang.String transformation)
                                throws java.security.NoSuchAlgorithmException,
                                       NoSuchPaddingException

 

 

返回一个指定方法的Cipher对象

参数:transformation 方法名(可用 DES,DESede,Blowfish)

public final void init(int opmode, java.security.Key key)
throws java.security.InvalidKeyException


用指定的密钥和模式初始化Cipher对象

参数:opmode 方式(ENCRYPT_MODE, DECRYPT_MODE, WRAP_MODE,UNWRAP_MODE)

key 密钥

public final byte[] doFinal(byte[] input)
                     throws java.lang.IllegalStateException,
                            IllegalBlockSizeException,
                            BadPaddingException


 


对input内的串,进行编码处理,返回处理后二进制串,是返回解密文还是加解文由init时的opmode决定


注意:本方法的执行前如果有update,是对updat和本次input全部处理,否则是本inout的内容

/*
安全程序 DESede/DES测试
*/
import java.security.*;
import javax.crypto.*;
public class testdes {
public static void main(String[] args){
    testdes my=new testdes();
    my.run();
  }
public  void run() {
//添加新安全算法,如果用JCE就要把它添加进去
 Security.addProvider(new com.sun.crypto.provider.SunJCE());
String Algorithm="DES"; //定义 加密算法,可用 DES,DESede,Blowfish
String myinfo="要加密的信息";
   try {
   //生成密钥
   KeyGenerator keygen = KeyGenerator.getInstance(Algorithm);
   SecretKey deskey = keygen.generateKey();

   //加密
   System.out.println("加密前的二进串:"+byte2hex(myinfo.getBytes()));
   System.out.println("加密前的信息:"+myinfo);
   Cipher c1 = Cipher.getInstance(Algorithm);
   c1.init(Cipher.ENCRYPT_MODE,deskey);
   byte[] cipherByte=c1.doFinal(myinfo.getBytes());
    System.out.println("加密后的二进串:"+byte2hex(cipherByte));
   //解密
   c1 = Cipher.getInstance(Algorithm);
   c1.init(Cipher.DECRYPT_MODE,deskey);
   byte[] clearByte=c1.doFinal(cipherByte);
   System.out.println("解密后的二进串:"+byte2hex(clearByte));
   System.out.println("解密后的信息:"+(new String(clearByte)));

  }
   catch (java.security.NoSuchAlgorithmException e1) {e1.printStackTrace();}
   catch (javax.crypto.NoSuchPaddingException e2) {e2.printStackTrace();}
   catch (java.lang.Exception e3) {e3.printStackTrace();}
  }
 public String byte2hex(byte[] b) //二行制转字符串
    {
     String hs="";
     String stmp="";
     for (int n=0;n<b.length;n++)
      {
       stmp=(java.lang.Integer.toHexString(b[n] & 0XFF));
       if (stmp.length()==1) hs=hs+"0"+stmp;
       else hs=hs+stmp;
       if (n<b.length-1)  hs=hs+":";
      }
     return hs.toUpperCase();
    }

}

 

 

2.5. Diffie-Hellman密钥一致协议
公开密钥密码体制的奠基人Diffie和Hellman所提出的 "指数密钥一致协议"(Exponential Key Agreement Protocol),该协议不要求别的安全性先决条件,允许两名用户在公开媒体上交换信息以生成"一致"的,可以共享的密钥。在JCE的中实现用户alice生成DH类型的密钥对,如果长度用1024生成的时间请,推荐第一次生成后保存DHParameterSpec,以便下次使用直接初始化.使其速度加快

System.out.println("ALICE: 产生 DH 对 ...");
KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
 aliceKpairGen.initialize(512);
KeyPair aliceKpair = aliceKpairGen.generateKeyPair();

 

 

alice生成公钥发送组bob

byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();

 

 

bob从alice发送来的公钥中读出DH密钥对的初始参数生成bob的DH密钥对

注意这一步一定要做,要保证每个用户用相同的初始参数生成的

   DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();
    KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
    bobKpairGen.initialize(dhParamSpec);
    KeyPair bobKpair = bobKpairGen.generateKeyPair();

 

 

bob根据alice的公钥生成本地的DES密钥

   KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
    bobKeyAgree.init(bobKpair.getPrivate());
    bobKeyAgree.doPhase(alicePubKey, true);
    SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");

 

 

bob已经生成了他的DES密钥,他现把他的公钥发给alice,

      byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();

 

 

alice根据bob的公钥生成本地的DES密钥

       ,,,,,,解码
    KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
    aliceKeyAgree.init(aliceKpair.getPrivate());
    aliceKeyAgree.doPhase(bobPubKey, true);
    SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");

 

 

bob和alice能过这个过程就生成了相同的DES密钥,在这种基础就可进行安全能信

常用API

java.security.KeyPairGenerator 密钥生成器类
public static KeyPairGenerator getInstance(String algorithm)
throws NoSuchAlgorithmException
以指定的算法返回一个KeyPairGenerator 对象
参数: algorithm 算法名.如:原来是DSA,现在添加了 DiffieHellman(DH)

public void initialize(int keysize)
以指定的长度初始化KeyPairGenerator对象,如果没有初始化系统以1024长度默认设置
参数:keysize 算法位长.其范围必须在 512 到 1024 之间,且必须为 64 的倍数
注意:如果用1024生长的时间很长,最好生成一次后就保存,下次就不用生成了

public void initialize(AlgorithmParameterSpec params)
throws InvalidAlgorithmParameterException
以指定参数初始化

javax.crypto.interfaces.DHPublicKey
public DHParameterSpec getParams()
返回
java.security.KeyFactory

public static KeyFactory getInstance(String algorithm)
throws NoSuchAlgorithmException
以指定的算法返回一个KeyFactory
参数: algorithm 算法名:DSH,DH

public final PublicKey generatePublic(KeySpec keySpec)
throws InvalidKeySpecException
根据指定的key说明,返回一个PublicKey对象

java.security.spec.X509EncodedKeySpec
public X509EncodedKeySpec(byte[] encodedKey)
根据指定的二进制编码的字串生成一个key的说明
参数:encodedKey 二进制编码的字串(一般能过PublicKey.getEncoded()生成)
javax.crypto.KeyAgreement 密码一至类

public static final KeyAgreement getInstance(java.lang.String algorithm)
throws java.security.NoSuchAlgorithmException
返回一个指定算法的KeyAgreement对象
参数:algorithm 算法名,现在只能是DiffieHellman(DH)

public final void init(java.security.Key key)
throws java.security.InvalidKeyException
用指定的私钥初始化
参数:key 一个私钥

public final java.security.Key doPhase(java.security.Key key,
boolean lastPhase)
throws java.security.InvalidKeyException,
java.lang.IllegalStateException
用指定的公钥进行定位,lastPhase确定这是否是最后一个公钥,对于两个用户的
情况下就可以多次定次,最后确定
参数:key 公钥
lastPhase 是否最后公钥

public final SecretKey generateSecret(java.lang.String algorithm)
throws java.lang.IllegalStateException,
java.security.NoSuchAlgorithmException,
java.security.InvalidKeyException
根据指定的算法生成密钥
参数:algorithm 加密算法(可用 DES,DESede,Blowfish)

*/
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.security.interfaces.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.interfaces.*;
import com.sun.crypto.provider.SunJCE;

public class testDHKey {


    public static void main(String argv[]) {
    try {
        testDHKey my= new testDHKey();
        my.run();
    } catch (Exception e) {
        System.err.println(e);

    }
    }

    private void run() throws Exception {
        Security.addProvider(new com.sun.crypto.provider.SunJCE());

    System.out.println("ALICE: 产生 DH 对 ...");
    KeyPairGenerator aliceKpairGen = KeyPairGenerator.getInstance("DH");
        aliceKpairGen.initialize(512);
    KeyPair aliceKpair = aliceKpairGen.generateKeyPair(); //生成时间长

        // 张三(Alice)生成公共密钥 alicePubKeyEnc 并发送给李四(Bob) ,
        //比如用文件方式,socket.....
    byte[] alicePubKeyEnc = aliceKpair.getPublic().getEncoded();

       //bob接收到alice的编码后的公钥,将其解码
    KeyFactory bobKeyFac = KeyFactory.getInstance("DH");
    X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec  (alicePubKeyEnc);
    PublicKey alicePubKey = bobKeyFac.generatePublic(x509KeySpec);
        System.out.println("alice公钥bob解码成功");
     // bob必须用相同的参数初始化的他的DH KEY对,所以要从Alice发给他的公开密钥,
         //中读出参数,再用这个参数初始化他的 DH key对

         //从alicePubKye中取alice初始化时用的参数
    DHParameterSpec dhParamSpec = ((DHPublicKey)alicePubKey).getParams();
    KeyPairGenerator bobKpairGen = KeyPairGenerator.getInstance("DH");
    bobKpairGen.initialize(dhParamSpec);
    KeyPair bobKpair = bobKpairGen.generateKeyPair();
        System.out.println("BOB: 生成 DH key 对成功");
    KeyAgreement bobKeyAgree = KeyAgreement.getInstance("DH");
    bobKeyAgree.init(bobKpair.getPrivate());
        System.out.println("BOB: 初始化本地key成功");
        //李四(bob) 生成本地的密钥 bobDesKey
    bobKeyAgree.doPhase(alicePubKey, true);
    SecretKey bobDesKey = bobKeyAgree.generateSecret("DES");
    System.out.println("BOB: 用alice的公钥定位本地key,生成本地DES密钥成功");
        // Bob生成公共密钥 bobPubKeyEnc 并发送给Alice,
        //比如用文件方式,socket.....,使其生成本地密钥
    byte[] bobPubKeyEnc = bobKpair.getPublic().getEncoded();
        System.out.println("BOB向ALICE发送公钥");

         // alice接收到 bobPubKeyEnc后生成bobPubKey
         // 再进行定位,使aliceKeyAgree定位在bobPubKey
    KeyFactory aliceKeyFac = KeyFactory.getInstance("DH");
    x509KeySpec = new X509EncodedKeySpec(bobPubKeyEnc);
    PublicKey bobPubKey = aliceKeyFac.generatePublic(x509KeySpec);
       System.out.println("ALICE接收BOB公钥并解码成功");
;
    KeyAgreement aliceKeyAgree = KeyAgreement.getInstance("DH");
    aliceKeyAgree.init(aliceKpair.getPrivate());
        System.out.println("ALICE: 初始化本地key成功");

    aliceKeyAgree.doPhase(bobPubKey, true);
        // 张三(alice) 生成本地的密钥 aliceDesKey
    SecretKey aliceDesKey = aliceKeyAgree.generateSecret("DES");
        System.out.println("ALICE: 用bob的公钥定位本地key,并生成本地DES密钥");

        if (aliceDesKey.equals(bobDesKey)) System.out.println("张三和李四的密钥相同");
       //现在张三和李四的本地的deskey是相同的所以,完全可以进行发送加密,接收后解密,达到
       //安全通道的的目的

        /*
         * bob用bobDesKey密钥加密信息
         */
    Cipher bobCipher = Cipher.getInstance("DES");
    bobCipher.init(Cipher.ENCRYPT_MODE, bobDesKey);
        String bobinfo= "这是李四的机密信息";
        System.out.println("李四加密前原文:"+bobinfo);
    byte[] cleartext =bobinfo.getBytes();
    byte[] ciphertext = bobCipher.doFinal(cleartext);

        /*
         * alice用aliceDesKey密钥解密
         */
    Cipher aliceCipher = Cipher.getInstance("DES");
    aliceCipher.init(Cipher.DECRYPT_MODE, aliceDesKey);
    byte[] recovered = aliceCipher.doFinal(ciphertext);
        System.out.println("alice解密bob的信息:"+(new String(recovered)));
    if (!java.util.Arrays.equals(cleartext, recovered))
        throw new Exception("解密后与原文信息不同");
    System.out.println("解密后相同");

    }

}

第3章小结
在加密术中生成密钥对时,密钥对的当然是越长越好,但费时也越多,请从中从实际出发选取合适的长度,大部分例码中的密钥是每次运行就从新生成,在实际的情况中是生成后在一段时间保存在文件中,再次运行直接从文件中读入,从而加快速度。当然定时更新和加强密钥保管的安全性也是必须的。


 

posted @ 2009-06-07 21:28 caitong| 编辑 收藏

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。本文将对它们之间的区别进行一番剖析,试图给开发者提供一个在二者之间进行选择的依据。

理解抽象类

abstract class和interface在Java语言中都是用来进行抽象类(本文中的抽象类并非从abstract class翻译而来,它表示的是一个抽象体,而abstract class为Java语言中用于定义抽象类的一种方法,请读者注意区分)定义的,那么什么是抽象类,使用抽象类能为我们带来什么好处呢?

在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如:如果我们进行一个图形编辑软件的开发,就会发现问题领域存在着圆、三角形这样一些具体概念,它们是不同的,但是它们又都属于形状这样一个概念,形状这个概念在问题领域是不存在的,它就是一个抽象概念。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的。

在面向对象领域,抽象类主要用来进行类型隐藏。我们可以构造出一个固定的一组行为的抽象描述,但是这组行为却能够有任意个可能的具体实现方式。这个抽象描述就是抽象类,而这一组任意个可能的具体实现则表现为所有可能的派生类。模块可以操作一个抽象体。由于模块依赖于一个固定的抽象体,因此它可以是不允许修改的;同时,通过从这个抽象体派生,也可扩展此模块的行为功能。熟悉OCP的读者一定知道,为了能够实现面向对象设计的一个最核心的原则OCP(Open-Closed Principle),抽象类是其中的关键所在。


从语法定义层面看abstract class和interface

在语法层面,Java语言对于abstract class和interface给出了不同的定义方式,下面以定义一个名为Demo的抽象类为例来说明这种不同。

使用abstract class的方式定义Demo抽象类的方式如下:
for examples http://www.bt285.cn http://www.5a520.cn  

abstract class Demo {
abstract void method1();
abstract void method2();

使用interface的方式定义Demo抽象类的方式如下:

interface Demo {
void method1();
void method2();

}

在abstract class方式中,Demo可以有自己的数据成员,也可以有非abstarct的成员方法,而在interface方式的实现中,Demo只能够有静态的不能被修改的数据成员(也就是必须是static final的,不过在interface中一般不定义数据成员),所有的成员方法都是abstract的。从某种意义上说,interface是一种特殊形式的abstract class。

从编程的角度来看,abstract class和interface都可以用来实现"design by contract"的思想。但是在具体的使用上面还是有一些区别的。

首先,abstract class在Java语言中表示的是一种继承关系,一个类只能使用一次继承关系。但是,一个类却可以实现多个interface。也许,这是Java语言的设计者在考虑Java对于多重继承的支持方面的一种折中考虑吧。

其次,在abstract class的定义中,我们可以赋予方法的默认行为。但是在interface的定义中,方法却不能拥有默认行为,为了绕过这个限制,必须使用委托,但是这会 增加一些复杂性,有时会造成很大的麻烦。

在抽象类中不能定义默认行为还存在另一个比较严重的问题,那就是可能会造成维护上的麻烦。因为如果后来想修改类的界面(一般通过abstract class或者interface来表示)以适应新的情况(比如,添加新的方法或者给已用的方法中添加新的参数)时,就会非常的麻烦,可能要花费很多的时间(对于派生类很多的情况,尤为如此)。但是如果界面是通过abstract class来实现的,那么可能就只需要修改定义在abstract class中的默认行为就可以了。

同样,如果不能在抽象类中定义默认行为,就会导致同样的方法实现出现在该抽象类的每一个派生类中,违反了"one rule,one place"原则,造成代码重复,同样不利于以后的维护。因此,在abstract class和interface间进行选择时要非常的小心。


从设计理念层面看abstract class和interface

上面主要从语法定义和编程的角度论述了abstract class和interface的区别,这些层面的区别是比较低层次的、非本质的。本小节将从另一个层面:abstract class和interface所反映出的设计理念,来分析一下二者的区别。作者认为,从这个层面进行分析才能理解二者概念的本质所在。

前面已经提到过,abstarct class在Java语言中体现了一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is a"关系,即父类和派生类在概念本质上应该是相同的(参考文献〔3〕中有关于"is a"关系的大篇幅深入的论述,有兴趣的读者可以参考)。对于interface 来说则不然,并不要求interface的实现者和interface定义在概念本质上是一致的,仅仅是实现了interface定义的契约而已。为了使论述便于理解,下面将通过一个简单的实例进行说明。

考虑这样一个例子,假设在我们的问题领域中有一个关于Door的抽象概念,该Door具有执行两个动作open和close,此时我们可以通过abstract class或者interface来定义一个表示该抽象概念的类型,定义方式分别如下所示:

使用abstract class方式定义Door:

for examples http://www.feng123.com

abstract class Door {
abstract void open();
abstract void close();
}


使用interface方式定义Door:


interface Door {
void open();
void close();
}


其他具体的Door类型可以extends使用abstract class方式定义的Door或者implements使用interface方式定义的Door。看起来好像使用abstract class和interface没有大的区别。

如果现在要求Door还要具有报警的功能。我们该如何设计针对该例子的类结构呢(在本例中,主要是为了展示abstract class和interface反映在设计理念上的区别,其他方面无关的问题都做了简化或者忽略)?下面将罗列出可能的解决方案,并从设计理念层面对这些不同的方案进行分析。

解决方案一:

简单的在Door的定义中增加一个alarm方法,如下:

abstract class Door {
abstract void open();
abstract void close();
abstract void alarm();
}


或者

interface Door {
void open();
void close();
void alarm();
}


那么具有报警功能的AlarmDoor的定义方式如下:

class AlarmDoor extends Door {
void open() { … }
void close() { … }
void alarm() { … }
}


或者

class AlarmDoor implements Door {
void open() { … }
void close() { … }
void alarm() { … }

这种方法违反了面向对象设计中的一个核心原则ISP(Interface Segregation Priciple),在Door的定义中把Door概念本身固有的行为方法和另外一个概念"报警器"的行为方法混在了一起。这样引起的一个问题是那些仅仅依赖于Door这个概念的模块会因为"报警器"这个概念的改变(比如:修改alarm方法的参数)而改变,反之依然。

解决方案二:

既然open、close和alarm属于两个不同的概念,根据ISP原则应该把它们分别定义在代表这两个概念的抽象类中。定义方式有:这两个概念都使用abstract class方式定义;两个概念都使用interface方式定义;一个概念使用abstract class方式定义,另一个概念使用interface方式定义。

显然,由于Java语言不支持多重继承,所以两个概念都使用abstract class方式定义是不可行的。后面两种方式都是可行的,但是对于它们的选择却反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理。我们一一来分析、说明。

如果两个概念都使用interface方式来定义,那么就反映出两个问题:1、我们可能没有理解清楚问题领域,AlarmDoor在概念本质上到底是Door还是报警器?2、如果我们对于问题领域的理解没有问题,比如:我们通过对于问题领域的分析发现AlarmDoor在概念本质上和Door是一致的,那么我们在实现时就没有能够正确的揭示我们的设计意图,因为在这两个概念的定义上(均使用interface方式定义)反映不出上述含义。

如果我们对于问题领域的理解是:AlarmDoor在概念本质上是Door,同时它有具有报警的功能。我们该如何来设计、实现来明确的反映出我们的意思呢?前面已经说过,abstract class在Java语言中表示一种继承关系,而继承关系在本质上是"is a"关系。所以对于Door这个概念,我们应该使用abstarct class方式来定义。另外,AlarmDoor又具有报警功能,说明它又能够完成报警概念中定义的行为,所以报警概念可以通过interface方式定义。如下所示:

abstract class Door {
abstract void open();
abstract void close();
}
interface Alarm {
void alarm();
}
class AlarmDoor extends Door implements Alarm {
void open() { … }
void close() { … }
void alarm() { … }
}


这种实现方式基本上能够明确的反映出我们对于问题领域的理解,正确的揭示我们的设计意图。其实abstract class表示的是"is a"关系,interface表示的是"like a"关系,大家在选择时可以作为一个依据,当然这是建立在对问题领域的理解上的,比如:如果我们认为AlarmDoor在概念本质上是报警器,同时又具有Door的功能,那么上述的定义方式就要反过来了。

 

结论

abstract class和interface是Java语言中的两种定义抽象类的方式,它们之间有很大的相似性。但是对于它们的选择却又往往反映出对于问题领域中的概念本质的理解、对于设计意图的反映是否正确、合理,因为它们表现了概念间的不同的关系(虽然都能够实现需求的功能)。这其实也是语言的一种的惯用法。 

 

posted @ 2009-06-05 21:34 caitong| 编辑 收藏