发博文
个人资料
一凡
一凡
  • 博客等级:
  • 博客积分:126
  • 博客访问:24,582
  • 关注人气:3
汽车遥控器
加载中…
评论
加载中…
留言
加载中…
访客
加载中…
好友
加载中…
博文
置顶: (2006-10-18 14:13)
分类: 文章欣赏

栗乡晚秋

                                                           一凡

抹一缕天边的云霞,

深入栗乡的每一个角落,

列队的雁阵啼鸣着南飞,

满眼里,

晚秋的原野一片金黄。

 

一叶知秋,

凉风将一串串的落叶,

别上清凉世界的前胸,

匆匆间,

秋天就不见了踪影。

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
标签:

杂谈

在本书的前面所介绍的内容中,所处理的都是纯文本文件。但是事实上,人们用于保存信息的文件并不是纯文本格式。现在比较流行的文件存储格式有Adobe公司的PDF和Microsoft的Word、Excel等。在处理这些文件的时候,不能简单的从文件读取字符,需要根据他们特殊的格式提取内容。本章就将对比较流行的PDF、Word和Excel格式的处理工具逐一进行介绍。

7.1 使用PDFBox处理PDF文档

PDF全称Portable Document Format,是Adobe公司开发的电子文件格式。这种文件格式与操作系统平台无关,可以在Windows、Unix或Mac OS等操作系统上通用。

PDF文件格式将文字、字型、格式、颜色及独立于设备和分辨率的图形图像等封装在一个文件中。如果要抽取其中的文本信息,需要根据它的文件格式来进行解析。幸好目前已经有不少工具能帮助我们做这些事情。

7.1.1 PDFBox的下载

最常见的一种PDF文本抽取工具就是PDFBox了,访问网址http://sourceforge.net/projects/pdfbox/,进入如图7-1所示的下载界面。

图7-1 PDFBox的下载页面

读者可以在该网页下载其最新的版本。本书采用的是PDFBox-0.7.3版本。PDFBox是一个开源的Java PDF库,这个库允许你访问PDF文件的各项信息。在接下来的例子中,将演示如何使用PDFBox提供的API,从一个PDF文件中提取出文本信息。

7.1.2 在Eclipse中配置

以下是在Eclipse中创建工程,并建立解析PDF文件的工具类的过程。

(1)在Eclipse的workspace中创建一个普通的Java工程:ch7。

(2)把下载的PDFBox-0.7.3.zip解压,解压后的目录结构如图7-2所示。

图7-2 解压后的PDFBox包

(3)进入external目录下,可以看到,这里包括了PDFBox所有用到的外部包。复制下面的Jar包到工程ch7的lib目录下(如还未建立lib目录,则先创建一个)。

l bcmail-jdk14-132.jar

l bcprov-jdk14-132.jar

l checkstyle-all-4.2.jar

l FontBox-0.1.0-dev.jar

l lucene-core-2.0.0.jar

然后再从PDFBox的lib目录下,复制PDFBox-0.7.3.jar到工程的lib目录下。

(4)在工程上单击右键,在弹出的快捷菜单中选择“Build Path->Config Build Path->Add Jars”命令,把工程lib目录下面的包都加入工程的Build Path。笔者机器上完整的工程目录如图7-3所示:

图7-3 工程截图

7.1.3 使用PDFBox解析PDF内容

在刚刚创建的Eclipse工程中,创建一个ch7.pdfbox包,并创建一个PdfboxTest类。该类包含一个getText方法,用于从一个PDF中获取文本信息,其代码如下。

代码7.1

public void geText(String file) throws Exception {

// 是否排序

   boolean sort = false;

// pdf文件名

   String pdfFile = file;

// 输入文本文件名称

   String textFile = null;

// 编码方式

   String encoding = "UTF-8";

// 开始提取页数

   int startPage = 1;

// 结束提取页数

   int endPage = Integer.MAX_VALUE;

// 文件输入流,生成文本文件

   Writer output = null;

// 内存中存储的PDF Document

   PDDocument document = null;

try {

      try {

         // 首先当作一个URL来装载文件,如果得到异常再从本地文件系统//去装载文件

         URL url = new URL(pdfFile);

         document = PDDocument.load(url);

         // 获取PDF的文件名

         String fileName = url.getFile();

        

// 以原来PDF的名称来命名新产生的txt文件

         if (fileName.length() > 4) {

            File outputFile = new File(fileName.substring(0, fileName.length() - 4) + ".txt");

            textFile = outputFile.getName();

         }

      } catch (MalformedURLException e) {

        

// 如果作为URL装载得到异常则从文件系统装载

         document = PDDocument.load(pdfFile);

         if (pdfFile.length() > 4) {

            textFile = pdfFile.substring(0, pdfFile.length() - 4) + ".txt";

         }

      }

      // 文件输入流,写入文件倒textFile

      output = new OutputStreamWriter(new FileOutputStream(textFile), encoding);

      // PDFTextStripper来提取文本

      PDFTextStripper stripper = null;

      stripper = new PDFTextStripper();

// 设置是否排序

      stripper.setSortByPosition(sort);

// 设置起始页

       stripper.setStartPage(startPage);

// 设置结束页

      stripper.setEndPage(endPage);

// 调用PDFTextStripper的writeText提取并输出文本

      stripper.writeText(document, output);

   } finally {

      if (output != null) {

         // 关闭输出流

         output.close();

      }

      if (document != null) {

         // 关闭PDF Document

         document.close();

      }

   }

}

在上面的代码中,getText方法接收一个String类型的参数,指定要提取的PDF文件路径。这个位置可以是一个URL或本地文件。然后函数调用PDFBox提供的PDFTextStripper类,设置提取过程中的一些属性(如起始页、是否排序等)。最后将文本提取并写入文件。

7.1.4 运行效果

下面看一下这个函数的运行效果,在PdfboxTest加入一个main函数,其代码如下。

public static void main(String[] args) {

   PdfboxTest test = new PdfboxTest();

   try {

      // 取得C盘下的index.pdf的内容

      test.geText("C:\\index.pdf");

   } catch (Exception e) {

      e.printStackTrace();

   }

}

这里要处理一个index.pdf文件,该PDF文件的内容如图7-4所示。

图7-4 要解析的PDF文档内容

通过PdfboxTest处理后的文本文件如图7-5所示。

图7-5 处理的结果

可以看到,PDF中的文本已经被提取出来,保存于文本文件中了。其中第4行的超链接部分“POI News WebBlog”,在文本文件中已经被替换成了普通的纯文本。读者可以根据PDFBox所提供的API文档进一步查询其他功能。

7.1.5 与Lucene的集成

PDFBox还提供和Lucene的集成,它提供了一套简单的方法把PDF Documents加入到Lucene的索引中去,请看以下代码:

Document lucenedocument = LucenePDFDocument.getDocument(…);

其中,LucenePDFDocument是PDFBox中提供的一个类,它的getDocument被重载为3个方法,分别接收一个File对象、InputStream对象或者URL对象作为参数,然后从该参数传递进来的PDF文件中,提取并生成Lucene的Document对象。

当通过PDFBox从一个PDF文档中得到一个Lucene Document后,可以直接使用IndexWriter把它加到Lucene的index中。LucenePDFDocument自动从PDF文件中提取各种元数据Field,并把它们加入到Document中。它提取的信息如表7-1所示。

表7-1 PDFBox生成的Lucene Document格式

Lucene Field名称

说明

path

文件系统路径(如果文档是从文件装载)

url

URL地址(如果文档是从网络装载)

contents

整个Document的内容,索引但不存储

summary

Document的内容前500个字符

modified

最后修改时间

uid

Document的惟一ID

CreationDate

从PDF的meta-data获取

Creator

从PDF的meta-data获取

Keywords

从PDF的meta-data获取

ModificationDate

从PDF的meta-data获取

Producer

从PDF的meta-data获取

Subject

从PDF的meta-data获取

Trapped

从PDF的meta-data获取

下面通过LucenePDFDocument,直接对PDF建立索引,在ch7.pdfbox包下面新建一个PdfLuceneTest类,该类的代码如下。

代码7.2

public class PdfLuceneTest {

    public static void main(String[] args) {

        try {

            // IndexWriter存放索引到d:\index下

            IndexWriter writer = new IndexWriter("d:\\index",

                    new StandardAnalyzer(), true);

            // LucenePDFDocument返回由PDF产生的Lucene Docuement

            Document d = LucenePDFDocument

                    .getDocument(new File("C:\\index.pdf"));

            // 写入索引

            writer.addDocument(d);

            // 关闭索引文件流

            writer.close();

            // 读取d:\index下的索引文件建立IndexSearcher

            IndexSearcher searcher = new IndexSearcher("d:\\index");

            // 对索引的contents Field进行查找关键词poi

            Term t = new Term("contents", "poi");

            // 根据Term生成Query

            Query q = new TermQuery(t);

            // 搜索返回结果集

            Hits hits = searcher.search(q);

            // 打印结果集

            for (int i = 0; i < hits.length(); i++) {

                System.out.println(hits.doc(i));

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

    }

}

函数利用LucenePDFDocument的getDocument函数,从一个PDF文件直接返回一个Lucene的Document,其中包含有path、url、modified、contents、summary等Field,把它们直接写入index,然后创建一个IndexSearcher,对contents字段经行检索,查找关键词“poi”(注意必须是小写),程序的执行结果如图7-6所示。

图7-6 搜索代码的运行结果

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
标签:

it

 

 

第六章 二进制、八进制、十六进制

6.1 为什么需要八进制和十六进制?

6.2 二、八、十六进制数转换到十进制数

  6.2.1 二进制数转换为十进制数

  6.2.2 八进制数转换为十进制数

  6.2.3 八进制数的表达方法

  6.2.4 八进制数在转义符中的使用

  6.2.5 十六进制数转换成十进制数

  6.2.6 十六进制数的表达方法

  6.2.7 十六进制数在转义符中的使用

6.3 十进制数转换到二、八、十六进制数

  6.3.1 10进制数转换为2进制数

  6.3.2 10进制数转换为8、16进制数

6.4 二、十六进制数互相转换

6.5 原码、反码、补码

6.6 通过调试查看变量的值

6.7 本章小结

这是一节“前不着村后不着店”的课。不同进制之间的转换纯粹是数学上的计算。不过,你不必担心会有么复杂,无非是乘或除的计算。

生活中其实很多地方的计数方法都多少有点不同进制的影子。

比如我们最常用的10进制,其实起源于人有10个指头。如果我们的祖先始终没有摆脱手脚不分的境况,我想我们现在一定是在使用20进制。

至于二进制……没有袜子称为0只袜子,有一只袜子称为1只袜子,但若有两袜子,则我们常说的是:1双袜子。

生活中还有:七进制,比如星期。十六进制,比如小时或“一打”,六十进制,比如分钟或角度……

 

(该版课程的内容更新及订正均已停止)

 旧版课程打包下载

----------------------------------

[想看涵盖“面向对象”、“图形编程”、“泛型编程”……

的“最新2008年版 白话C++”课程,请点击!] (另有: 博客版)

 

 

6.1 为什么需要八进制和十六进制?

 

编程中,我们常用的还是10进制……必竟C/C++是高级语言。

比如:

int a = 100,b = 99;

不过,由于数据在计算机中的表示,最终以二进制的形式存在,所以有时候使用二进制,可以更直观地解决问题。

但,二进制数太长了。比如int 类型占用4个字节,32位。比如100,用int类型的二进制数表达将是:

0000 0000 0000 0000 0110 0100

面对这么长的数进行思考或操作,没有人会喜欢。因此,C,C++ 没有提供在代码直接写二进制数的方法。

 

用16进制或8进制可以解决这个问题。因为,进制越大,数的表达长度也就越短。不过,为什么偏偏是16或8进制,而不其它的,诸如9或20进制呢?

2、8、16,分别是2的1次方,3次方,4次方。这一点使得三种进制之间可以非常直接地互相转换。8进制或16进制缩短了二进制数,但保持了二进制数的表达特点。在下面的关于进制转换的课程中,你可以发现这一点。

 

6.2 二、八、十六进制数转换到十进制数

6.2.1 二进制数转换为十进制数

二进制数第0位的权值是2的0次方,第1位的权值是2的1次方……

所以,设有一个二进制数:0110 0100,转换为10进制为:

下面是竖式:

 

0110 0100 换算成 十进制

 

第0位 0 * 20  =  0

第1位 0 * 21  =  0

第2位 1 * 22  =  4

第3位 0 * 23  =  0

第4位 0 * 24  =  0

第5位 1 * 25  = 32

第6位 1 * 26  = 64

第7位 0 * 27  =  0     +

---------------------------

              100  

 

用横式计算为:

0 * 20 + 0 * 21 + 1 * 22 + 1 * 23 + 0 * 24 + 1 * 25 + 1 * 26 + 0 * 27 = 100

 

0乘以多少都是0,所以我们也可以直接跳过值为0的位:

1 * 22 + 1 * 23 +  1 * 25 + 1 * 26 = 100

 

6.2.2 八进制数转换为十进制数

八进制就是逢8进1。

八进制数采用 0~7这八数来表达一个数。

八进制数第0位的权值为8的0次方,第1位权值为8的1次方,第2位权值为8的2次方……

所以,设有一个八进制数:1507,转换为十进制为:

用竖式表示:

 

1507换算成十进制。

 

第0位 7 * 80 = 7

第1位 0 * 81 = 0

第2位 5 * 82 = 320

第3位 1 * 83 = 512   +

--------------------------

              839

同样,我们也可以用横式直接计算:

7 * 80 + 0 * 81 + 5 * 82 + 1 * 83 = 839

 

结果是,八进制数 1507 转换成十进制数为 839

 

6.2.3 八进制数的表达方法

C,C++语言中,如何表达一个八进制数呢?如果这个数是 876,我们可以断定它不是八进制数,因为八进制数中不可能出7以上的阿拉伯数字。但如果这个数是123、是567,或12345670,那么它是八进制数还是10进制数,都有可能。

所以,C,C++规定,一个数如果要指明它采用八进制,必须在它前面加上一个0,如:123是十进制,但0123则表示采用八进制。这就是八进制数在C、C++中的表达方法。

由于C和C++都没有提供二进制数的表达方法,所以,这里所学的八进制是我们学习的,CtC++语言的数值表达的第二种进制法。

现在,对于同样一个数,比如是100,我们在代码中可以用平常的10进制表达,例如在变量初始化时:

 

int a = 100;

我们也可以这样写:

int a = 0144; //0144是八进制的100;一个10进制数如何转成8进制,我们后面会学到。

 

千万记住,用八进制表达时,你不能少了最前的那个0。否则计算机会通通当成10进制。不过,有一个地方使用八进制数时,却不能使用加0,那就是我们前面学的用于表达字符的“转义符”表达法。

 

6.2.4 八进制数在转义符中的使用

我们学过用一个转义符'\'加上一个特殊字母来表示某个字符的方法,如:'\n'表示换行(line),而'\t'表示Tab字符,'\''则表示单引号。今天我们又学习了一种使用转义符的方法:转义符'\'后面接一个八进制数,用于表示ASCII码等于该值的字符。

比如,查一下第5章中的ASCII码表,我们找到问号字符(?)的ASCII值是63,那么我们可以把它转换为八进值:77,然后用 '\77'来表示'?'。由于是八进制,所以本应写成 '\077',但因为C,C++规定不允许使用斜杠加10进制数来表示字符,所以这里的0可以不写。

事实上我们很少在实际编程中非要用转义符加八进制数来表示一个字符,所以,6.2.4小节的内容,大家仅仅了解就行。

 

6.2.5 十六进制数转换成十进制数

2进制,用两个阿拉伯数字:0、1;

8进制,用八个阿拉伯数字:0、1、2、3、4、5、6、7;

10进制,用十个阿拉伯数字:0到9;

16进制,用十六个阿拉伯数字……等等,阿拉伯人或说是印度人,只发明了10个数字啊?

 

16进制就是逢16进1,但我们只有0~9这十个数字,所以我们用A,B,C,D,E,F这五个字母来分别表示10,11,12,13,14,15。字母不区分大小写。

十六进制数的第0位的权值为16的0次方,第1位的权值为16的1次方,第2位的权值为16的2次方……

所以,在第N(N从0开始)位上,如果是是数 X (X 大于等于0,并且X小于等于 15,即:F)表示的大小为 X * 16的N次方。

假设有一个十六进数 2AF5, 那么如何换算成10进制呢?

 

用竖式计算:

 

2AF5换算成10进制:

 

第0位:  5 * 160 = 5

第1位:  F * 161 = 240

第2位:  A * 162 = 2560

第3位:  2 * 163 = 8192  +

-------------------------------------

                 10997 

直接计算就是:

5 * 160  + F * 161 + A * 162 + 2 * 163 = 10997

(别忘了,在上面的计算中,A表示10,而F表示15)

 

现在可以看出,所有进制换算成10进制,关键在于各自的权值不同。

假设有人问你,十进数 1234 为什么是 一千二百三十四?你尽可以给他这么一个算式:

1234 = 1 * 103 + 2 * 102 + 3 * 101 + 4 * 100

 

6.2.6  十六进制数的表达方法

如果不使用特殊的书写形式,16进制数也会和10进制相混。随便一个数:9876,就看不出它是16进制或10进制。

C,C++规定,16进制数必须以 0x开头。比如 0x1表示一个16进制数。而1则表示一个十进制。另外如:0xff,0xFF,0X102A,等等。其中的x也也不区分大小写。(注意:0x中的0是数字0,而不是字母O)

以下是一些用法示例:

 

int a = 0x100F;

int b = 0x70 + a;

 

至此,我们学完了所有进制:10进制,8进制,16进制数的表达方式。最后一点很重要,C/C++中,10进制数有正负之分,比如12表示正12,而-12表示负12,;但8进制和16进制只能用达无符号的正整数,如果你在代码中里:-078,或者写:-0xF2,C,C++并不把它当成一个负数。

 

6.2.7 十六进制数在转义符中的使用

 

转义符也可以接一个16进制数来表示一个字符。如在6.2.4小节中说的 '?' 字符,可以有以下表达方式:

 

'?'     //直接输入字符

'\77'   //用八进制,此时可以省略开头的0

'\0x3F' //用十六进制

 

同样,这一小节只用于了解。除了空字符用八进制数 '\0' 表示以外,我们很少用后两种方法表示一个字符。

 

6.3 十进制数转换到二、八、十六进制数

6.3.1 10进制数转换为2进制数

 

给你一个十进制,比如:6,如果将它转换成二进制数呢?

 

10进制数转换成二进制数,这是一个连续除2的过程:

把要转换的数,除以2,得到商和余数,

将商继续除以2,直到商为0。最后将所有余数倒序排列,得到数就是转换结果。

 

听起来有些糊涂?我们结合例子来说明。比如要转换6为二进制数。

 

“把要转换的数,除以2,得到商和余数”。

 那么:

 要转换的数是6, 6 ÷ 2,得到商是3,余数是0。 (不要告诉我你不会计算6÷3!)

 

“将商继续除以2,直到商为0……”

现在商是3,还不是0,所以继续除以2。

那就: 3 ÷ 2, 得到商是1,余数是1

 

“将商继续除以2,直到商为0……”

现在商是1,还不是0,所以继续除以2。

那就: 1 ÷ 2, 得到商是0,余数是1 (拿笔纸算一下,1÷2是不是商0余1!)

 

“将商继续除以2,直到商为0……最后将所有余数倒序排列”

好极!现在商已经是0。

我们三次计算依次得到余数分别是:0、1、1,将所有余数倒序排列,那就是:110了!

 

6转换成二进制,结果是110。

 

把上面的一段改成用表格来表示,则为:

被除数 计算过程 余数
6 6/2 3 0
3 3/2 1 1
1 1/2 0 1

(在计算机中,÷用 / 来表示)

 

如果是在考试时,我们要画这样表还是有点费时间,所更常见的换算过程是使用下图的连除:

(图:1)

请大家对照图,表,及文字说明,并且自已拿笔计算一遍如何将6转换为二进制数。

说了半天,我们的转换结果对吗?二进制数110是6吗?你已经学会如何将二进制数转换成10进制数了,所以请现在就计算一下110换成10进制是否就是6。

 

6.3.2 10进制数转换为8、16进制数

 

非常开心,10进制数转换成8进制的方法,和转换为2进制的方法类似,惟一变化:除数由2变成8。

 

来看一个例子,如何将十进制数120转换成八进制数。

 

用表格表示:

被除数 计算过程 余数
120 120/8 15 0
15 15/8 1 7
1 1/8 0 1

 

120转换为8进制,结果为:170。

 

非常非常开心,10进制数转换成16进制的方法,和转换为2进制的方法类似,惟一变化:除数由2变成16。

 

同样是120,转换成16进制则为:

被除数 计算过程 余数
120 120/16 7 8
7 7/16 0 7

 

120转换为16进制,结果为:78。

 

请拿笔纸,采用(图:1)的形式,演算上面两个表的过程。

 

6.4 二、十六进制数互相转换

 

二进制和十六进制的互相转换比较重要。不过这二者的转换却不用计算,每个C,C++程序员都能做到看见二进制数,直接就能转换为十六进制数,反之亦然。

我们也一样,只要学完这一小节,就能做到。

首先我们来看一个二进制数:1111,它是多少呢?

你可能还要这样计算:1 * 20 + 1 * 21 + 1 * 22 + 1 * 23 = 1 * 1 + 1 * 2 + 1 * 4 + 1 * 8 = 15。

然而,由于1111才4位,所以我们必须直接记住它每一位的权值,并且是从高位往低位记,:8、4、2、1。即,最高位的权值为23 = 8,然后依次是 22 = 4,21=2, 20 = 1。

 

记住8421,对于任意一个4位的二进制数,我们都可以很快算出它对应的10进制值。

 

下面列出四位二进制数 xxxx 所有可能的值(中间略过部分)

 

仅4位的2进制数  快速计算方法   十进制值     十六进值

1111        = 8 + 4 + 2 + 1  = 15          F

1110        = 8 + 4 + 2 + 0  = 14          E

1101        = 8 + 4 + 0 + 1  = 13          D          

1100        = 8 + 4 + 0 + 0  = 12          C          

1011        = 8 + 4 + 0 + 1  = 11          B          

1010        = 8 + 0 + 2 + 0  = 10          A

1001        = 8 + 0 + 0 + 1  = 10          9

....

0001        = 0 + 0 + 0 + 1  = 1           1

0000        = 0 + 0 + 0 + 0  = 0           0

 

二进制数要转换为十六进制,就是以4位一段,分别转换为十六进制。

如(上行为二制数,下面为对应的十六进制):

 

1111 1101 , 1010 0101 , 1001 1011

 F    D   ,  A    5   ,  9    B  

 

反过来,当我们看到 FD时,如何迅速将它转换为二进制数呢?

先转换F:

看到F,我们需知道它是15(可能你还不熟悉A~F这五个数),然后15如何用8421凑呢?应该是8 + 4 + 2 + 1,所以四位全为1 :1111。

接着转换 D:

看到D,知道它是13,13如何用8421凑呢?应该是:8 + 2 + 1,即:1011。

所以,FD转换为二进制数,为: 1111 1011

 

由于十六进制转换成二进制相当直接,所以,我们需要将一个十进制数转换成2进制数时,也可以先转换成16进制,然后再转换成2进制。

比如,十进制数 1234转换成二制数,如果要一直除以2,直接得到2进制数,需要计算较多次数。所以我们可以先除以16,得到16进制数:

被除数 计算过程 余数
1234 1234/16 77 2
77 77/16 4 13 (D)
4 4/16 0 4

 

结果16进制为: 0x4D2

 

然后我们可直接写出0x4D2的二进制形式: 0100 1011 0010。

其中对映关系为:

0100 -- 4

1011 -- D

0010 -- 2

 

同样,如果一个二进制数很长,我们需要将它转换成10进制数时,除了前面学过的方法是,我们还可以先将这个二进制转换成16进制,然后再转换为10进制。

下面举例一个int类型的二进制数:

01101101 11100101 10101111 00011011

我们按四位一组转换为16进制: 6D E5 AF 1B   

 

6.5 原码、反码、补码

 

结束了各种进制的转换,我们来谈谈另一个话题:原码、反码、补码。

 

我们已经知道计算机中,所有数据最终都是使用二进制数表达。

我们也已经学会如何将一个10进制数如何转换为二进制数。

不过,我们仍然没有学习一个负数如何用二进制表达。

 

比如,假设有一 int 类型的数,值为5,那么,我们知道它在计算机中表示为:

00000000 00000000 00000000 00000101

5转换成二制是101,不过int类型的数占用4字节(32位),所以前面填了一堆0。

现在想知道,-5在计算机中如何表示?

 

在计算机中,负数以其正值的补码形式表达

什么叫补码呢?这得从原码,反码说起。

 

原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。

比如 00000000 00000000 00000000 00000101 是 5的 原码。

 

反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。

取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)

比如:将00000000 00000000 00000000 00000101每一位取反,得11111111 11111111 11111111 11111010。

称:11111111 11111111 11111111 11111010 是 00000000 00000000 00000000 00000101 的反码。

反码是相互的,所以也可称:

11111111 11111111 11111111 11111010 和 00000000 00000000 00000000 00000101 互为反码。

 

补码:反码加1称为补码。

也就是说,要得到一个数的补码,先得到反码,然后将反码加上1,所得数称为补码。

比如:00000000 00000000 00000000 00000101 的反码是:11111111 11111111 11111111 11111010。

那么,补码为:

11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011

 

所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011。转换为十六进制:0xFFFFFFFB。

 

再举一例,我们来看整数-1在计算机中如何表示。

假设这也是一个int类型,那么:

 

1、先取1的原码:00000000 00000000 00000000 00000001

2、得反码:     11111111 11111111 11111111 11111110

3、得补码:     11111111 11111111 11111111 11111111

 

可见,-1在计算机里用二进制表达就是全1。16进制为:0xFFFFFF。

 

一切都是纸上说的……说-1在计算机里表达为0xFFFFFF,我能不能亲眼看一看呢?当然可以。利用C++ Builder的调试功能,我们可以看到每个变量的16进制值。

 

6.6 通过调试查看变量的值

下面我们来动手完成一个小小的实验,通过调试,观察变量的值。

我们在代码中声明两个int 变量,并分别初始化为5和-5。然后我们通过CB提供的调试手段,可以查看到程序运行时,这两个变量的十进制值和十六进制值。

首先新建一个控制台工程。加入以下黑体部分(就一行):

 

//---------------------------------------------------------------------------

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused

int main(int argc, char* argv[])

{

int aaaa = 5, bbbbb = -5;

return 0;

}

//---------------------------------------------------------------------------

没有我们熟悉的的那一行:

getchar();

所以,如果全速运行这个程序,将只是DOS窗口一闪而过。不过今天我们将通过设置断点,来使用程序在我们需要的地儿停下来。

设置断点:最常用的调试方法之一,使用程序在运行时,暂停在某一代码位置,

 

在CB里,设置断点的方法是在某一行代码上按F5或在行首栏内单击鼠标。

如下图:

在上图中,我们在return 0;这一行上设置断点。断点所在行将被CB以红色显示。

 

接着,运行程序(F9),程序将在断点处停下来。

(请注意两张图的不同,前面的图是运行之前,后面这张是运行中,左边的箭头表示运行运行到哪一行)

 

当程序停在断点的时,我们可以观察当前代码片段内,可见的变量。观察变量的方法很多种,这里我们学习使用Debug Inspector (调试期检视),来全面观察一个变量。

以下是调出观察某一变量的 Debug Inspector 窗口的方法:

 

先确保代码窗口是活动窗口。(用鼠标点一下代码窗口)

按下Ctrl键,然后将鼠标挪到变量 aaaa 上面,你会发现代码中的aaaa变蓝,并且出现下划线,效果如网页中的超链接,而鼠标也变成了小手状:

点击鼠标,将出现变量aaaa的检视窗口:

(笔者使用的操作系统为WindowsXP,窗口的外观与Win9X有所不同)

从该窗口,我可以看到:

aaaa :变量名

int  :变量的数据类型

0012FF88:变量的内存地址,请参看5.2 变量与内存地址;地址总是使用十六进制表达

5 : 这是变量的值,即aaaa = 5;

0x00000005 :同样是变量的值,但采用16进制表示。因为是int类型,所以占用4字节。

 

首先先关闭前面的用于观察变量aaaa的Debug Inspector窗口。

现在,我们用同样的方法来观察变量bbbb,它的值为-5,负数在计算机中使用补码表示。

正如我们所想,-5的补码为:0xFFFFFFFB。

 

再按一次F9,程序将从断点继续运行,然后结束。

6.7 本章小结

很难学的一章?

来看看我们主要学了什么:

 

1)我们学会了如何将二、八、十六进制数转换为十进制数。

三种转换方法是一样的,都是使用乘法。

 

2)我们学会了如何将十进制数转换为二、八、十六进制数。

方法也都一样,采用除法。

 

3)我们学会了如何快速的地互换二进制数和十六进制数。

要诀就在于对二进制数按四位一组地转换成十六进制数。

在学习十六进制数后,我们会在很多地方采用十六进制数来替代二进制数。

 

4)我们学习了原码、反码、补码。

把原码的0变1,1变0,就得到反码。要得到补码,则先得反码,然后加1。

以前我们只知道正整数在计算机里是如何表达,现在我们还知道负数在计算机里使用其绝对值的补码表达。

比如,-5在计算机中如何表达?回答是:5的补码。

 

5)最后我们在上机实验中,这会了如何设置断点,如何调出Debug Inspector窗口观察变量。

以后我们会学到更多的调试方法。

 

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
标签:

jdk

tomcat

jar

xml

apache

分类: Struts
javax.xml.transform.TransformerFactoryConfigurationError的解决办法
 

 

今天在做一个struts2的小程序时,遇到一个问题就是该网站不能被反问,分两步把这个问题人解决啦!!!

第一步:

去看tomcat的webapps,里边也部署上了。接着用昨天刚学的一招必杀,呵呵!去看C:\Program Files\Apache Software Foundation\Tomcat 5.0\logs下的日志,显示javax.xml.transform.TransformerFactoryConfigurationError Provider org.apache.xalan.processor.TransformerFactoryImpl not found

后来上网搜了一下:找到了一篇文章,说该问题是由于tomcat 里C:\Program Files\Apache Software Foundation\Tomcat 5.0\common\endorsed下的两个有关xml的jar文件和JDK里的xml解析器冲突造成的:原文如下:

http://localhost:8080/testdwr/dwr 访问

页面出现错误

javax.xml.transform.TransformerFactoryConfigurationError: Provider org.apache.xalan.processor.TransformerFactoryImpl not found
javax.xml.transform.TransformerFactory.newInstance(Unknown Source)
uk.ltd.getahead.dwr.convert.DOMConverter.<init>(DOMConverter.java:157)

这是一个 XML 解析器的问题,我们能直接能理解的就是 TransformerFactoryImpl 找不到,那么这个类在哪个包里呢? xalan,就去 apache 网上下载到 xalan.jar 包扔到应用的 WEB-INF/lib 目录中,重启 Tomcat 就能OK 了。

上面是第一种解决办法。问题是解决了,但是根由何在?难道就不能用别的 XML 解析器呢,难道 JDK 和 Tomcat 就没有为我们预备好对于 dwr 可用的 XML 解析器吗?先说个背景

·JDK1.3 没有 XML 解析器,所有要自己配上 xercesImpl.jar 和 xml-apis.jar
·JDK 1.4.0 和 JDK 1.4.1 虽然具有了 XML 解析器,但是有些 Bug,所有还是得把 xercesImpl.jar 放到 Tomcat/common/endorsed 目录中覆盖掉默认的解析器
·JDK 1.4.2 及后来版本的 XML 解析器可以工作的很好
·最后,那个出现错误本质原因还得由下面慢慢道来……

这样就是说 JDK 1.4.2 及更新版本根本用不着 xercesImpl.jar 和 xml-apis.jar 帮忙了。只要我们细心些就能发现在 Tomcat5.0.x/common/endorsed 目录中有两个包 xercesImpl.jar 和 xml-apis.jar。因为 Tomcat 加载 endorsed 中的包是通过参数 -Djava.endorsed.dirs="X:\Tomcat5.0.x\common\endorsed" 加载的,所以放在 endorsed 目录中的包要优于 JDK 的 rt.jar,所以要用 JDK 的 XML 解析器必须把 xercesImpl.jar 和 xml-apis.jar 从 endorsed 目录中移去,这是第二种解决办法。

再静心想一想,问题在 XML 解析器,为何偏偏是 org.apache.xalan.processor.TransformerFactoryImpl 这样的类名呢?这让我不免思考起 JDK 1.4 及 JDK 1.5 以上版本的差异来,再次回想起为何 Tomcat 5.5.x 版本需要 JDK 1.5 以上的版本来。

不妨做个实验,dwr 1.1.4 + JDK 1.4.2 + Tomcat 5.0.x,xercesImpl.jar 和 xml-apis.jar 仍旧让它们在 endorsed 目录中,运行最基本的 dwr 程序没一点问题。那为什么换个JDK就又行了呢?原因就在 xml-apis.jar 中指定了 TransformerFactory 的实现类全名为 org.apache.xalan.processor.TransformerFactoryImpl,它存在于 JDK 1.4.2 的 rt.jar 包中,而在 JDK 1.5 的 rt.jar 包中根本就没有 apache 的包了,所以它不行,再进一步,其实第二种解决办法如果用的是 JDK 1.5 以上版本仅仅需要把 xml-apis.jar 包从 endorsed 目录挪出就行了。

我按上面介绍的第二种方法,删除了endorsed下面的两个jar文件,再重启tomcat,查看日志,果然没报上面的那个错误,但一运行,还是不能显示该项目。

 

第二步:

查看MyEclipse下console控制台,发现有

严重: Error filterstart错误

上网查了一下:原文如下:

最近使用上了Tomcat 5.5,只是这东西在你最需要的时候,往往令你很失望。
  缘由代码要提交到CVS,删除了一些测试使用的类以及页面文件,只是当时忽略了去注释掉所删除的类在Struts.xml里Action映射。紧接着重新启动Tomcat,只是该站点无法访问,仔细查看Tomcat 5.5的日志,就发现:

严重: Error filterStart

       信息,当时就检查初始化Struts2的Filter以及初始化对应的Servlet-api.jar,没有发现什么问题。最后实在没有办法,直接使用Tomcat 6,其启动时候抛出的异常告诉我无法加载在struts.xml配置的Java 类。这时候,我才明白,原来问题所在。
一旦struts.xml文件所配置的类在初始化的时候一旦不存在,则造成整个站点无法访问。

        最后教训,一旦在struts.xml配置的类,若丢失的话,会影响整个站点的运行。

        建议大家可以直接把站点直接放在Tocmat 6下面,用于取代tomat 5.5。Tomcat 5.5的错误日志太让人寒心了~

”其启动时候抛出的异常告诉我无法加载在struts.xml配置的Java 类“

这名话是关键,说的很明确说是找不着java类,在我的程序里就是LoginAction这个类,怎么会找不到呢,上struts.xml文件里一看,天啊,应该是class="com.test.action.LoginAction",而我写的是"com.test.LoginAction",改过以后,运行,好使!!!

 

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
标签:

it

分类: AJAX
document对象


含有当前文档信息的对象.

属性

title 当前文档标题,如果未定义,则包含"Untitled".

location 文档的全URL.

lastModified 含有文档最后修改日期.

referrer 调用者URL,即用户是从哪个URL链接到当前页面的.

bgColor 背景色(#xxxxxx)

fgColor 前景文本颜色.

linkColor 超链接颜色.

vlinkColor 访问过的超链颜色.

alinkColor 激活链颜色(鼠标按住未放时).

forms[] 文档中form对象的数组,按定义次序存储.

forms.length 文档中的form对象数目.

links[] 与文档中所有HREF链对应的数组对象,按次序定义存储.

links.length 文档中HREF链的数目.

anchors[] 锚(...)数组,按次序定义存储.

anchors.length 文档中锚的数目.


方法

write("string") 将字符串突出给当前窗口.(字符串可以含有HTML标记)

writeln("string") 与write()类似,在结尾追加回车符,只在预定格式文本中(

...

...
)生效.

clear() 清当前窗口.

close() 关闭当前窗口.
Form对象
属性

name

中的NAME属性的字符串值.

method 中METHOD属性的类值,"0"="GET" ,"1"="POST" .

action 中ACTION属性的字符串值.

target 表格数据提交的目标,与标记中相应属性一致.

elements[index] elements属性包含form中的各个元素.

length 表格中的元素个数.


方法

submit() 提交表格.

事件处理器onSubmit() 用户单击一个定义好的按钮提交form时运行的代码.


text和textarea对象

属性

name NAME属性的字符串值.

value 域内容的字符串值.

defaultValue 域内容的初始字符串值.


方法

focus() 设置对象输入焦点.

blur() 从对象上移走输入焦点.

select() 选定对象的输入区域.


事件处理器

onFocus 当输入焦点进入时执行.

onBlur 当域失去焦点时执行.

onSelect 当域中有部分文本被选定时执行.

onChange 当域失去焦点且域值相对于onFocus执行有所改变时执行.
复选框对象

属性

name NAME属性的字符串值.

value 复选框内容的字符串值.如果设置了,则为"on",否则为"off".

checked 复选框内容的布尔值.如果设置了,则为true,否则为false .

defaultChecked 反映(CHECKED)属性的布尔值(缺省状态).


方法

click() 选定复选框,并使之状态为"on".


事件处理器

onClick 当用户单击Checkbox时执行.


单选按钮(radio)对象


属性

name NAME属性的字符串值.

length radio对象中单选按钮的个数.

value VALUE属性的字符串值.

checked 布尔值,按下为true,否则为false .

defaultChecked 反映CHECKED属性值的布尔值.


方法

click() 选定单选按钮.


事件处理器

onClick 当单选按钮被选定时执行.
select对象

属性

length select对象中对象的个数.

name 由NAME=属性定义的select对象的内部名.

selectedIndex select对象中当前被选option的下标.

options 该属性对应于在HTML中定义select对象时标记中的内容,它有如下属性:

text 标记后的文本串.

value VALUE属性的值,当Submit按钮被按下时,该值被提交.

defaultSelected 反映标记的SELECTED属性的布尔值.

selected 反映option的当前选择状态的布尔值.


事件处理器

onFocus 当输入焦点进入域时执行.

onBlur 当域失去输入焦点时执行.

onChange 当域失去焦点且如果域的值相对于onFocus执行时有所改变,则执行onChange.


Button对象


表格中有三种类型按钮,由标记中的TYPE属性定义:

.submit (type="SUBMIT")

.reset (type="RESET")

.custom (type="BUTTON")

所有按钮对象都有如下成分:

属性

value VALUE属性的字符串值.

name NAME属性的字符串值.


方法

click() 选定按钮


事件处理器

onClick 当按钮被单击时执行
阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
标签:

it

package cn;

import java.security.Key;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;

public class Arithmetic {

 static Key key;

 
 public static void getKey(String strKey) {
  try {
   KeyGenerator _generator = KeyGenerator.getInstance("DES");
   _generator.init(new SecureRandom(strKey.getBytes()));
   key = _generator.generateKey();
   _generator = null;
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 
 public static String getEncString(String strMing) {
  byte[] byteMi = null;
  byte[] byteMing = null;
  String strMi = "";
  try {
   return byte2hex(getEncCode(strMing.getBytes()));

   // byteMing = strMing.getBytes("UTF8");
   // byteMi = this.getEncCode(byteMing);
   // strMi = new String( byteMi,"UTF8");
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   byteMing = null;
   byteMi = null;
  }
  return strMi;
 }

 
 public static String getDesString(String strMi) {
  byte[] byteMing = null;
  byte[] byteMi = null;
  String strMing = "";
  try {
   return new String(getDesCode(hex2byte(strMi.getBytes())));

   // byteMing = this.getDesCode(byteMi);
   // strMing = new String(byteMing,"UTF8");
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   byteMing = null;
   byteMi = null;
  }
  return strMing;
 }

 
 private static byte[] getEncCode(byte[] byteS) {
  byte[] byteFina = null;
  Cipher cipher;
  try {
   cipher = Cipher.getInstance("DES");
   cipher.init(Cipher.ENCRYPT_MODE, key);
   byteFina = cipher.doFinal(byteS);
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   cipher = null;
  }
  return byteFina;
 }

 
 private static byte[] getDesCode(byte[] byteD) {
  Cipher cipher;
  byte[] byteFina = null;
  try {
   cipher = Cipher.getInstance("DES");
   cipher.init(Cipher.DECRYPT_MODE, key);
   byteFina = cipher.doFinal(byteD);
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   cipher = null;
  }
  return byteFina;
 }

 
 public static String byte2hex(byte[] b) { // 一个字节的数,
  // 转成16进制字符串
  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;
  }
  return hs.toUpperCase(); // 转成大写
 }

 public static byte[] hex2byte(byte[] b) {
  if ((b.length % 2) != 0)
   throw new IllegalArgumentException("长度不是偶数");
  byte[] b2 = new byte[b.length / 2];
  for (int n = 0; n < b.length; n += 2) {
   String item = new String(b, n, 2);
   // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个进制字节
   b2[n / 2] = (byte) Integer.parseInt(item, 16);
  }

  return b2;
 }

 public static void main(String[] args) {
  Arithmetic des = new Arithmetic();// 实例化一个对像
  des.getKey("aadd");// 生成密匙

  String strEnc = des.getEncString("樊振华");// 加密字符串,返回String的密文
  System.out.println(strEnc);

  String strDes = des.getDesString(strEnc);// 把String 类型的密文解密
  System.out.println(strDes);
 }

}


文章出处:http://www.diybl.com/course/3_program/java/javajs/2008924/145171.html

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 

Hibernate的乐观锁与悲观锁

 

 

文章转自网上好像是玉米田的,忘记了


锁( locking )
业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算处理中,我们希望针对某个 cut-off 时间点的数据进行处理,而不希望在结算进行过程中(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓的 “锁” ,即给我们选定的目标数据上锁,使其无法被其他程序修改。Hibernate 支持两种锁机制:即通常所说的 “悲观锁( Pessimistic Locking )”和 “乐观锁( Optimistic Locking )” 。

悲观锁( Pessimistic Locking )
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
一个典型的倚赖数据库的悲观锁调用:
select * from account where name=”Erica” for update
这条 sql 语句锁定了 account 表中所有符合检索条件(name=”Erica”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。Hibernate 的悲观锁,也是基于数据库的锁机制实现。
下面的代码实现了对查询记录的加锁:
String hqlStr ="from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE); // 加锁
List userList = query.list();// 执行查询,获取数据

query.setLockMode 对查询语句中,特定别名所对应的记录进行加锁(我们为TUser 类指定了一个别名 “user” ),这里也就是对返回的所有 user 记录进行加锁。
观察运行期 Hibernate 生成的 SQL 语句:
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id
as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex
from t_user tuser0_ where (tuser0_.name='Erica' ) for update

这里 Hibernate 通过使用数据库的 for update 子句实现了悲观锁机制。
Hibernate 的加锁模式有:
LockMode.NONE : 无锁机制。
LockMode.WRITE : Hibernate 在 Insert 和 Update 记录的时候会自动获取。
LockMode.READ : Hibernate 在读取记录的时候会自动获取。
以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。
LockMode.UPGRADE :利用数据库的 for update 子句加锁。
LockMode. UPGRADE_NOWAIT : Oracle 的特定实现,利用 Oracle 的 for update nowait 子句实现加锁。
上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:
Criteria.setLockMode
Query.setLockMode
Session.lock

注意,只有在查询开始之前(也就是 Hiberate 生成 SQL 之前)设定加锁,才会真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含 for update 子句的 Select SQL 加载进来,所谓数据库加锁也就无从谈起。

乐观锁( Optimistic Locking )
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个version 字段,当前值为 1 ;而当前帐户余额字段(balance)为 $100 。
1 操作员 A 此时将其读出(version=1),并从其帐户余额中扣除 $50($100-$50)。
2 在操作员 A 操作的过程中,操作员 B 也读入此用户信息(version=1),并从其帐户余额中扣除 $20 ($100-$20)。
3 操作员 A 完成了修改工作,将数据版本号加一(version=2),连同帐户扣除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
4 操作员 B 完成了操作,也将版本号加一(version=2)试图向数据库提交数据(balance=$80),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足“ 提交版本必须大于记录当前版本才能执行更新“ 的乐观锁策略,因此,操作员 B 的提交被驳回。这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员 A 的操作结果的可能。
从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A 和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数据库的更新操作,利用 Hibernate 提供的透明化乐观锁实现,将大大提升我们的生产力。
Hibernate 中可以通过 class 描述符的 optimistic-lock 属性结合 version描述符指定。

现在,我们为之前示例中的 TUser 加上乐观锁机制。
1 . 首先为 TUser 的 class 描述符添加 optimistic-lock 属性:
<hibernate-mapping>
<class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true"
dynamic-insert="true" optimistic-lock="version">
……
</class>
</hibernate-mapping>

optimistic-lock 属性有如下可选取值: 
none:无乐观锁 
version:通过版本机制实现乐观锁
dirty:通过检查发生变动过的属性实现乐观锁
all:通过检查所有属性实现乐观锁
其中通过 version 实现的乐观锁机制是 Hibernate 官方推荐的乐观锁实现,同时也是 Hibernate 中,目前唯一在数据对象脱离 Session 发生修改的情况下依然有效的锁机制。因此,一般情况下,我们都选择 version 方式作为 Hibernate 乐观锁实现机制。
2 . 添加一个 Version 属性描述符
<hibernate-mapping>
<class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true"
optimistic-lock="version">
<id name="id" column="id" type="java.lang.Integer">
<generator class="native">
</generator>
</id>
<version column="version" name="version" type="java.lang.Integer"/>
……
</class>
</hibernate-mapping>

注意 version 节点必须出现在 ID 节点之后。这里我们声明了一个 version 属性,用于存放用户的版本信息,保存在 TUser 表的version 字段中。
此时如果我们尝试编写一段代码,更新 TUser 表中记录数据,如:
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);
Transaction tx = session.beginTransaction();
user.setUserType(1); // 更新 UserType 字段
tx.commit();

每次对 TUser 进行更新的时候,我们可以发现,数据库中的 version 都在递增。而如果我们尝试在 tx.commit 之前,启动另外一个 Session ,对名为 Erica 的用户进行操作,以模拟并发更新时的情形:
Session session= getSession();
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
Session session2 = getSession();
Criteria criteria2 = session2.createCriteria(TUser.class);
criteria2.add(Expression.eq("name","Erica"));
List userList = criteria.list();
List userList2 = criteria2.list();TUser user =(TUser)userList.get(0);
TUser user2 =(TUser)userList2.get(0);
Transaction tx = session.beginTransaction();
Transaction tx2 = session2.beginTransaction();
user2.setUserType(99);
tx2.commit();
user.setUserType(1);
tx.commit();
执行以上代码,代码将在 tx.commit() 处抛出 StaleObjectStateException 异常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我们就可以在乐观锁校验失败时进行相应处理

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
(2008-07-17 10:17)
标签:

跟着火炬看中国

it

分类: 电脑维修
今天鼠标右键菜单出现问题,
 

     不管把鼠标放在哪里--桌面上、图标上都是这种情况:
     单击右键,鼠标保持不移动,出现透明的框没有文字,直到把鼠标在透明框上划过,框里的文字才一一显示,而且每行字之间还透明着,整个右键菜单残缺不全,等多久都没有变化。

 

     解决:我的电脑—右键—属性—高级—性能—设置—视觉效果—淡入淡出或滑动菜单到视图,将其前面的复选框的钩去掉。

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
标签:

it

分类: Spring Framework 开发参
 

 

<bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
  <property name="sessionFactory">
    <ref local="sessionFactory" />
  </property>
</bean>

配置一个事务代理模板,其他需要事务管理的bean可以继承它

<bean id="baseTransactionProxy" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
  <property name="transactionManager">
    <ref bean="transactionManager"/>
  </property> 
  <property name="transactionAttributes"> 
    <props> 
      <prop key="save*">PROPAGATION_REQUIRED</prop> 
      <prop key="*">PROPAGATION_REQUIRED, readOnly</prop> 
    </props> 
  </property> 
</bean>

所有save开头的方法都纳入事务管理,其他方法都作为只读事务

如果有个userManager需要事务管理的话,可以这样配置

<bean id="userManager" parent="baseTransactionProxy"> 
  <property name="target"> 
    <bean class="some.package.UserManagerImpl"> 
      <property name="userDAO">
        <ref bean="userDAO"/>
      </property> 
    </bean> 
  </property> 
</bean>

target属性中用了内部匿名bean的语法

先看HibernateTransactionManager。按照api里的解释:HibernateTransactionManager为每个SessionFactory产生的Session绑定到一个线程上。SessionFactoryUtils和HibernateTemplate直接使用当前线程里的Session(ThreadLocal模式?),自动参与到事务中。
这里有疑问,session如何绑定到线程,具体何时打开何时关闭
HibernateTemplate.execute()操作Session的3个步骤: 1.get Session

如果当前线程在事务中,获取为当前线程绑定的Session。否则创建新Session,并绑定到当前线程。(放入SessionHolder,并把他以当前线程的SessionFactory实例为key放入TransactionSynchronizationManager的ThreadLocal变量中)

2.使用Session 3.release Session

如果在事务中,则不关闭Session
阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
标签:

IT/科技

分类: Hibernate
 

ibatis基础代码包括:
1.    ibatis实例配置
一个典型的配置文件如下(具体配置项目的含义见后):
<? xml version="1.0" encoding="UTF-8"    ?>  
<! DOCTYPE sqlMapConfig
      PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0/
      "
http://www.ibatis.com/dtd/sql-map-config-2.dt

<sqlMapConfig >  

     < settings
       cacheModelsEnabled ="true"      
      enhancementEnabled ="true"      
      lazyLoadingEnabled ="true"      
      errorTracingEnabled ="true"      
      maxRequests ="32"           
      maxSessions ="10"           
      maxTransactions ="5"          
      useStatementNamespaces ="false"    
       />  

     < transactionManager    type ="JDBC" >  

     < dataSource    type ="SIMPLE" >  
         < property    name ="JDBC.Driver"    value ="com.p6spy.engine.spy.P6SpyDriver" />  
         < property    name ="JDBC.ConnectionURL"    value ="jdbc:mysql://localhost/sample" />  
         < property    name ="JDBC.Username"    value ="user" />  
         < property    name ="JDBC.Password"    value ="mypass" />  
         < property    name ="Pool.MaximumActiveConnections"    value ="10" />  
         < property    name ="Pool.MaximumIdleConnections"    value ="5" />  
         < property    name ="Pool.MaximumCheckoutTime"    value ="120000" />  
         < property    name ="Pool.TimeToWait"    value ="500" />  
         < property    name ="Pool.PingQuery"    value ="select 1 from ACCOUNT" />  
         < property    name ="Pool.PingEnabled"    value ="false" />  
         < property    name ="Pool.PingConnectionsOlderThan"    value ="1" />  
         < property    name ="Pool.PingConnectionsNotUsedFor"    value ="1" />  
       </ dataSource >  
     </ transactionManager >  

     < sqlMap    resource ="com/ibatis/sample/User.xml" />  

</ sqlMapConfig >   
  

⑴ Settings 节点

cacheModelsEnabled  
是否启用SqlMapClient上的缓存机制。 建议设为"true"

enhancementEnabled  
是否针对POJO启用字节码增强机getter/setter的调用效能,避免Reflect所带来的性能开销。同时,这也为Lazy Loading带来提升。 建议设为"true"

errorTracingEnabled
是否启用错误日志,在开发期间建议设为"true" 以方便调试

lazyLoadingEnabled
是否启用延迟加载机制,建议设为"true"

maxRequests
最大并发请求数(Statement并发数)

maxTransactions  
最大并发事务数

maxSessions    最大Session数。即当前最大允许的并发SqlMapClient数。

useStatementNamespaces  
是否使用Statement命名空间。
这里的命名空间指的是映射文件中,sqlMap节的namespace属性,如在上例中针对t_use
表的映射文件sqlMap节点: <sqlMap namespace="User"> 这里,指定了此sqlMap节点下定义的操作均属于"User"命名空间。 在useStatementNamespaces="true"的情况下,Statement调用需追加命名空间,如:sqlMap.update("User.updateUser",user);
否则直接通过Statement名称调用即可,如: sqlMap.update("updateUser",user); 但请注意此时需要保证所有映射文件中,Statement定义无重名。


transactionManager节点
transactionManager节点定义了ibatis的事务管理器,目前提供了以下几种选择:
JDBC
通过传统JDBC Connection.commit/rollback实现事务支持。  
JTA
使用容器提供的JTA服务实现全局事务管理。
EXTERNAL
外部事务管理,如在EJB中使用ibatis,通过EJB的部署配置即可实现自
动的事务管理机制。此时ibatis将把所有事务委托给外部容器进行管理。

dataSource节点
    dataSource从属于transactionManager节点,用于设定ibatis运行期使用的DataSource属性。
type属性:
dataSource节点的type属性指定了dataSource的实现类型。 可选项目:
SIMPLE:
    SIMPLE是ibatis内置的dataSource实现,其中实现了一个简单的
数据库连接池机制,对应 ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory。

DBCP:
    基于Apache DBCP连接池组件实现的DataSource封装,当无容器提
供DataSource服务时,建议使用该选项,对应ibatis实现类为
com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory。

JNDI:
使用J2EE容器提供的DataSource实现,DataSource将通过指定
的JNDI Name从容器中获取。对应 ibatis实现类为
com.ibatis.sqlmap.engine.datasource.JndiDataSourceFactory。

dataSource的子节点说明(SIMPLE&DBCP):
JDBC.Driver    JDBC 驱动。
如:org.gjt.mm.mysql.Driver

JDBC.ConnectionURL  
数据库URL。
如:jdbc:mysql://localhost/sample
如果用的是SQLServer JDBC Driver,需要
在url后追加SelectMethod=Cursor以获得
JDBC事务的多Statement支持。
JDBC.Username
    数据库用户名
JDBC.Password  
数据库用户密码
Pool.MaximumActiveConnections
数据库连接池可维持的最大容量。

Pool.MaximumIdleConnections
数据库连接池中允许的挂起(idle)连接数。

JNDI由于大部分配置是在应用服务器中进行,因此ibatis中的配置相对简
分别使用JDBC和JTA事务管理的JDNI配置:
使用JDBC事务管理的JNDI DataSource配置


< transactionManager    type ="JDBC"     >  
< dataSource    type ="JNDI" >  
< property    name ="DataSource"     value ="java:comp/env/jdbc/myDataSource" />  
</ dataSource >  
</ transactionManager >  
< transactionManager    type ="JTA"     >  
< property    name ="UserTransaction"     value ="java:/ctx/con/UserTransaction" />  
< dataSource    type ="JNDI" >  
< property    name ="DataSource"     value ="java:comp/env/jdbc/myDataSource" />  
</ dataSource >   
sqlMap节点
sqlMap节点指定了映射文件的位置,配置中可出现多个sqlMap节点,以指定
项目内所包含的所有映射文件。

ibatis基础语义

XmlSqlMapClientBuilder
XmlSqlMapClientBuilder是ibatis 2.0之后版本新引入的组件,用以替代1.x
版本中的XmlSqlMapBuilder。其作用是根据配置文件创建SqlMapClient实例。

SqlMapClient
SqlMapClient是ibatis的核心组件,提供数据操作的基础平台。SqlMapClient
可通过XmlSqlMapClientBuilder创建:
  

String resource    = " com/ibatis/sample/SqlMapConfig.xml " ;
Reader reader;

reader    =    Resources.getResourceAsReader(resource);

XmlSqlMapClientBuilder xmlBuilder    =   
new    XmlSqlMapClientBuilder();

SqlMapClient sqlMap    =    xmlBuilder.buildSqlMap(reader);  
"com/ibatis/sample/SqlMapConfig.xml"指明了配置文件在CLASSPATH
中的相对路径。XmlSqlMapClientBuilder通过接受一个Reader类型的配置文
件句柄,根据配置参数,创建SqlMapClient实例。

SqlMapClient提供了众多数据操作方法,下面是一些常用方法的示例,具体说明
文档请参见ibatis java doc,或者ibatis官方开发手册。

SqlMapClient基本操作示例

以下示例摘自ibatis官方开发手册,笔者对其进行了重新排版以获得更好的阅读效果。
例1: 数据写入操作(insert, update, delete):


sqlMap.startTransaction();
Product product    =     new    Product();
product.setId ( 1 );
product.setDescription (“Shih Tzu”);
int    rows    =    sqlMap.insert (“insertProduct”, product);
sqlMap.commitTransaction();  
例2: 数据查询 (select)


sqlMap.startTransaction();
Integer key    =     new    Integer ( 1 );
Product product    =    (Product)sqlMap.queryForObject (“getProduct”, key);
sqlMap.commitTransaction();  
例3: 在指定对象中存放查询结果(select)  


sqlMap.startTransaction();
Customer customer    =     new    Customer();
sqlMap.queryForObject(“getCust”, parameterObject, customer);
sqlMap.queryForObject(“getAddr”, parameterObject, customer);
sqlMap.commitTransaction();  
例4: 执行批量查询 (select)


sqlMap.startTransaction();
List list    =    sqlMap.queryForList (“getProductList”,    null )
sqlMap.commitTransaction();  
例5: 关于AutoCommit


// 没有预先执行startTransaction时,默认为auto_commit模式  
int    rows    =    sqlMap.insert (“insertProduct”, product);  
例6:查询指定范围内的数据


sqlMap.startTransaction();
List list    =    sqlMap.queryForList (“getProductList”,    null ,    0 ,    40 );
sqlMap.commitTransaction();  
例7: 结合RowHandler进行查询(select)


    public     class    MyRowHandler    implements    RowHandler     {
      public     void    handleRow (Object object, List list)    throws  
      SQLException     {
        Product product    =    (Product) object;
        product.setQuantity ( 10000 );
        sqlMap.update (“updateProduct”, product);
      }  
}  
sqlMap.startTransaction();
RowHandler rowHandler    =     new    MyRowHandler();
List list    =    sqlMap.queryForList (“getProductList”,    null ,
rowHandler);
sqlMap.commitTransaction();  
  

// 例8: 分页查询 (select)  
PaginatedList list    =  
sqlMap.queryForPaginatedList (“getProductList”,    null ,    10 );
list.nextPage();
list.previousPage();


// 例9: 基于Map的批量查询 (select)  
sqlMap.startTransaction();
Map map    =    sqlMap.queryForMap (“getProductList”,    null , “productCode”);
sqlMap.commitTransaction();
Product p    =    (Product) map.get(“EST - 93 ”); 

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
(2007-11-05 09:40)
标签:

IT/科技

分类: Hibernate
1.1. 下载iBatis驱动组件

  下载压缩文件包:iBATIS_DBL-2.1.7.597.zip需用的:ibatis-common-2.jar

  ibatis-dao-2.jar
  ibatis-sqlmap-2.jar

  1.2. SqlMapConfig.xml

  <xml version="1.0" encoding="UTF-8" >
  <sqlMapConfig>
  <settings cacheModelsEnabled="true“ enhancementEnabled="true“ lazyLoadingEnabled="true"
  errorTracingEnabled="true"
  maxRequests="32"
  maxSessions="10"
  maxTransactions="5"
  useStatementNamespaces="false"
  />
  <transactionManager type="JDBC">
  <dataSource type="SIMPLE">
  <property name="JDBC.Driver" value="org.gjt.mm.mysql.Driver"/>
  <property name="JDBC.ConnectionURL" value="jdbc:mysql://localhost/ibatissample"/>
  <property name="JDBC.Username" value="root"/>
  <property name="JDBC.Password" value="chenyan"/>
  <property name="Pool.MaximumActiveConnections" value="10"/>
  <property name="Pool.MaximumIdleConnections" value="5"/>
  <property name="Pool.MaximumCheckoutTime" value="120000"/>
  <property name="Pool.TimeToWait" value="500"/>
  <property name="Pool.PingQuery" value="select 1 from t_user"/>
  <property name="Pool.PingEnabled" value="false"/>
  <property name="Pool.PingConnectionsOlderThan" value="1"/>
  <property name="Pool.PingConnectionsNotUsedFor" value="1"/>
  </dataSource>
  </transactionManager>
  <sqlMap resource="hs/com/ibatis/User.xml"/>
  </sqlMapConfig>

  注意点:1. maxRequests >= maxSessions > maxTransactions

  2. useStatementNamespaces节点:对应于映射文件中的namespace属性,当此节点为true的时候,Statement调用需要追加命名空间。当此节点为false的时候,则需保证所有的映射文件中,Statement定义无重名。

  1.3. POJO文件

  package hs.com.ibatis.POJO;
  import java.io.Serializable;
  public class User implements Serializable {
  private Integer id;
  private String name;
  private Integer sex;
  /** default constructor */
  public User() {}
  public Integer getId() {
   return this.id;}
  public void setId(Integer id) {
   this.id = id;}
  public String getName() {
   return this.name;}
  public void setName(String name) {
   this.name = name;}
  public Integer getSex() {
   return this.sex;}
  public void setSex(Integer sex) {
   this.sex = sex;}
  }

  1.4. 映射文件

  <xml version="1.0" encoding="UTF-8">
  <sqlMap namespace="User">
  <typeAlias alias="user" type="hs.com.ibatis.POJO.User"/>
  <select id="getUser“ parameterClass="java.lang.String“ resultClass="user">
  <![CDATA[ Select name, sex from t_user where name = #name# ]]>
  </select>
  <update id="updateUser“ parameterClass="user">
  <![CDATA[ UPDATE t_user SET name=#name#, sex=#sex# WHERE id = #id# ]]>
  </update>
  <insert id="insertUser“ parameterClass="user">
  INSERT INTO t_user ( name, sex) VALUES ( #name#, #sex#)
  </insert>
  <delete id="deleteUser“ parameterClass="java.lang.String">
  delete from t_user where id = #value#
  </delete>
  </sqlMap>

  二.iBatis基础组件与操作

  1. XmlSqlMapClientBuilder

  2.0版本后引入,以替代XmlSqlMapBuilder。作用是根据配置文件创建SqlMapClient实例。2.7版本中好像找不到了!

  2. SqlMapClient

  提供数据操作的基础平台,线程安全

  String resource ="hs/com/ibatis/SqlMapConfig.xml";
  Reader reader = Resources.getResourceAsReader(resource);
  //XmlSqlMapClientBuilder xmlBuilder = new XmlSqlMapClientBuilder();
  SqlMapClient sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader);

  SqlMapClient基本操作:

  数据写入(Insert,Update,Delete)
  数据查询
  在指定对象中存放查询结果
  执行批量查询
  查询指定范围内的数据
  结合RowHandler查询
  分页查询
  基于Map的批量查询

  数据写入(Insert):
  sqlMap.startTransaction();
  Product product = new Product();
  product.setId (1);
  product.setDescription (“Arom_chen”);
  int rows = sqlMap.insert (“insertProduct”, product);
  sqlMap.commitTransaction();

  数据查询:
  sqlMap.startTransaction();
  Integer key = new Integer (1);
  Product product = (Product)sqlMap.queryForObject
  (“getProduct”, key);
  sqlMap.commitTransaction();

  其他的操作也是基本上根据上面两种操作变化而来!

  三.iBatis高级特性

  3.1. 数据关联
  3.2. 延迟加载
  3.3. 动态映射

  <select id="getUsers"
  parameterClass="user"
  resultMap="get-user-result">
  Select id, name, sex from t_user
  <dynamic prepend="WHERE">
  <isNotEmpty prepend="AND" property="name">
  (name like #name#)
  </isNotEmpty>
  <isNotEmpty prepend="AND" property="address">
  (address like #address#)
  </isNotEmpty>
  </dynamic>
  </select>

  注意:在第一个条件成立的时候不会加AND前缀。

  3.4. 事务管理

  附(mysql表):

  CREATE TABLE t_address (id int(10) not null,
  country varchar(100) not null,
  city varchar(100),
  town varchar(100),
  number int(10),
  PRIMARY KEY(id))
  TYPE=myisam;
  
  CREATE TABLE t_user (id int(10) not null,
  name varchar(100) not null,
  sex int(10),
  PRIMARY KEY(id))
  TYPE=myisam;

阅读  ┆ 评论  ┆ 转载 ┆ 收藏 
  

新浪BLOG意见反馈留言板 不良信息反馈 电话:4006900000 提示音后按1键(按当地市话标准计费) 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

新浪公司 版权所有