csusky

常用链接

统计

最新评论

2008年2月24日 #

异步IO的关闭事件

JAVA SOCKET只定义了四种事件

public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;

是没有关闭事件的,我们怎么判断一个连接是否关闭呢?
如果你的selector注册了一个OP_READ事件,那么在连接关闭的时候将会产生一个OP_READ事件
也就是说本来阻塞的selector此时将会被唤醒,但是如果试图在此事件的通道中读取数据将会返回-1
如下:

Set<SelectionKey> readyKeys = selector.selectedKeys();

= readyKeys.iterator()

SelectionKey key 
= (SelectionKey)i.next();

if (operation == SelectionKey.OP_READ &&
                         key.isReadable())
                
{
                    ReadableByteChannel incomingChannel 
= (ReadableByteChannel)key.channel(); 
//此时将会得到-1,表明该链接已关闭
int n = incomingChannel.read(readBuffer);
}
此时我们需要取消该KEY 如下:
if (n == -1)
            
{
                key.cancel();
                  //关闭输入输出 
                  sc.socket().shutdownOutput();
                  sc.socket().shutdownInput();
                   //关闭SOCKET
                   sc.socket().close();
                  //关闭通道
                   incomingChannel.close();
            }

posted @ 2009-11-10 22:28 晓宇 阅读(393) | 评论 (1)编辑 收藏

ExecutorFilter

1 . 用Executors构造一个新的线程池
ExecutorService executor = Executors.newCachedThreadPool();

方法 newCachedThreadPool();
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的 ThreadFactory 创建新线程。
2. 用构造的线程池创建ExecutorFilter
ExecutorFilter es= new ExecutorFilter(executor));

在ExecutorFilter内部:
只需要将相应的事件分发到到线程池的相应线程即可,但是SessionCreated事件只能在主线程中,不能分发
触发方法
1 .
首先构造一个IoFilterEvent,这个IoFilterEvent包含1、事件的类型,2、下一个过滤器
然后触发该时间的处理方法。
 if (eventTypes.contains(IoEventType.SESSION_OPENED)) {
            fireEvent(
new IoFilterEvent(nextFilter, IoEventType.SESSION_OPENED,
                    session, 
null));
        }

2 .
从线程池中取出一个线程执行事件处理
protected void fireEvent(IoFilterEvent event) {
        getExecutor().execute(event);
    }


在构造ExecutorFilter 时如果没有传入IoEventType则默认只对如下几种几件感兴趣
EXCEPTION_CAUGHT
MESSAGE_RECEIVED
MESSAGE_SENT
SESSION_CLOSED
SESSION_IDLE
SESSION_OPENED
当然还需要覆盖相应的事件处理方法 如上所示

posted @ 2008-12-12 11:33 晓宇 阅读(1537) | 评论 (0)编辑 收藏

ORACLE的块大小

参数db_block_size;
这个参数只能设置成底层操作系统物理块大小的整数倍,最好是2的n次方倍。
如WINDOWS下4KB,8KB,16KB
且该参数需要在建库的时候指定,一旦指定不能更改。
虽然在ORACLE9I以上可以指定表空间的数据库大小,允许同时使用包括非默认大小在内的数据库块大小。不过需要设置指定大小数据块的buffer_cache.

小的块:
小的块降低块竞争,因为每个块中的行较少.
小的块对于小的行有益.
小的块对于随意的访问较好.如果一个块不太可能在读入内存后被修改,那么块的大小越小使用buffer cache越有效率。当内存资源很珍贵时尤为重要,因为数据库的buffer cache是被限制大小的。
劣势:
小块的管理消费相对大.
因为行的大小你可能只在块中存储很小数目的行,这可能导致额外的I/O。
小块可能导致更多的索引块被读取

大的块
好处:
更少的管理消费和更多存储数据的空间.
大块对于有顺序的读取较好.  譬如说全表扫描
大块对很大的行较好
大块改进了索引读取的性能.大的块可以在一个块中容纳更多的索引条目,降低了大的索引级的数量.越少的index level意味着在遍历索引分支的时候越少的I/O。
劣势:
大块不适合在OLTP中用作索引块,因为它们增加了在索引叶块上的块竞争。
如果你是随意的访问小的行并有大的块,buffer cache就被浪费了。例如,8 KB的block size 和50 byte row size,你浪费了7,950



 

posted @ 2008-11-25 15:45 晓宇 阅读(1748) | 评论 (0)编辑 收藏

TIPS

将进酒  杯莫停  -------> 亭名:  悲默亭

全球通史

《诗经·采薇》

昔我往矣,杨柳依依 今我来思,雨雪霏霏

posted @ 2008-11-10 16:31 晓宇 阅读(172) | 评论 (0)编辑 收藏

SPRING整合IBMMQ实现全局事物

     摘要: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance...  阅读全文

posted @ 2008-10-27 17:01 晓宇 阅读(2392) | 评论 (0)编辑 收藏

Lucene的切词 analysis包

在搜索引擎中,切词语是一个重要的部分,其中包括专有名词的提取、词的分割、词的格式化等等。
TokenStream 类几乎是所有这些类的基类
有两个需要被子类实现的方法Token next() 和 close()
首先来看analysis包,这个包主要是提供一些简单的词汇化处理
Tokenizer结尾的类是将要处理的字符串进行分割成Token流,而根据分割的依据的又产生了以下几个Tokenizer类
首先Tokenizer类是所有以Tokenizer结尾的类的基
然后是CharTokenizer,所有的以Tokenizer结尾的类都是从这个类继承的
这个类中有一个抽象方法
  protected abstract boolean isTokenChar(char c);
另外一个需要被子类覆写的方法
  protected char normalize(char c) {};
是对单个字符进行处理的方法譬如说将英文字母全部转化为小写

还有一个变量
protected Reader input;
这个读取器是这些类所处理的数据的   数据源
输入一个Reader ,产生一个Token流


这个方法是是否进行切分的依据,依次读取char流,然后用这个方法对每个char进行检测,如果返回false则将预先存储在
词汇缓冲区中的char数组作为一个Token返回
LetterTokenizer :
      protected boolean isTokenChar(char c) {
              return Character.isLetter(c);
      }
WhitespaceTokenizer:
      protected boolean isTokenChar(char c) {
              return !Character.isWhitespace(c);
      } 
LowerCaseTokenizer extends LetterTokenizer:
protected char normalize(char c) {
      return Character.toLowerCase(c);
   }

   在构造函数中调用super(in);进行和 LetterTokenizer同样的操作,但是在词汇化之前所有的词都转化为小写了
 
然后是以Filter结尾的类,这个类簇主要是对已经词汇化的Token流进行进一步的处理
 输入是Token流 , 输出仍然是Token流。
TokenFilter extends TokenStream  是所有这些类的父类
protected TokenStream input;
在TokenFilter 中有一个TokenStream 变量,是Filter类簇处理的数据源,而Filter类簇又是继承了TokenStream 类的
有一个public final Token next()方法,这个方法以TokenStream.next()产生的Token流 为处理源,产生的仍然是Token流
只不过中间有一些处理的过程
LowerCaseFilter:将所有的Token流的转化为小写
     t.termText = t.termText.toLowerCase();
StopFilter:过滤掉一些停止词,这些停止词由构造函数指定
     for (Token token = input.next(); token != null; token = input.next())
      if (!stopWords.contains(token.termText))
        return token;


比较一下Tokenizer类簇和Filter类簇,可以知道
Tokenizer类簇主要是对输入的Reader流,实际上是字符流按照一定的规则进行分割,产生出Token流
其输入是字符串的Reader流形式,输出是Token流

Filter类簇主要是对输入的Token流进行更进一步的处理,如去除停止词,转化为小写
主要为一些格式化操作。
由于Filter类簇的输入输出相同,所以可以嵌套几个不同的Filter类,以达到预期的处理目的。
前一个Filter类的输出作为后一个Filter类的输入
而Tokenizer类簇由于输入输出不同,所以不能嵌套







posted @ 2008-05-30 14:47 晓宇 阅读(1006) | 评论 (1)编辑 收藏

JDK1.5的自动装箱功能

在JAVA JDK1.5以后具有的自动装箱与拆箱的功能,所谓的自动装箱
与拆箱也就是把基本的数据类型自动的转为封装类型。

如:自动装箱,它可以直接把基本类型赋值给封装类型

Integer num = 10 ;

Double d = 2d ;

自动拆箱,它可以把封装类型赋值给基本类型

int num = new Integer(10);

double d = new Double(2d);

自动装箱与拆箱的功能事实上是编译器来帮您的忙,编译器在编译时期依您所编写的语法,决定是否进行装箱或拆箱动作。在自动装箱时对于值从-128到127之间的值,它们被装箱为Integer对象后,会存在内存中被重用,所以范例4.6中使用==进行比较时,i1 与 i2实际上参考至同一个对象。如果超过了从-128到127之间的值,被装箱后的Integer对象并不会被重用,即相当于每次装箱时都新建一个Integer对象,所以范例4.7使用==进行比较时,i1与i2参考的是不同的对象。所以不要过分依赖自动装箱与拆箱,您还是必须知道基本数据类型与对象的差异。

    public void testBoxingUnboxing() {

        int i = 10;

        Integer inta = i;

        inta++;

        inta += 1;

        int j = inta;

        assertTrue(j == inta);结果是:true//junit里面的方法

        assertTrue(j == new Integer(j)); 结果是:true

        assertTrue(10000 == new Integer(10000)); 结果是:true

    }

Integer i = 100.相当于编译器自动为您作以下的语法编译:

Integer i = new Integer(100).所以自动装箱与拆箱的功能是所谓的“编译器蜜糖”(Compiler Sugar),虽然使用这个功能很方便,但在程序运行阶段您得了解Java的语义。例如下面的程序是可以通过编译的:

Integer i = null.int j = i.这样的语法在编译时期是合法的,但是在运行时期会有错误,因为这种写法相当于:

Integer i = null.int j = i.intValue().null表示i没有参考至任何的对象实体,它可以合法地指定给对象参考名称。由于实际上i并没有参考至任何的对象,所以也就不可能操作intValue()方法,这样上面的写法在运行时会出现NullPointerException错误。

自动装箱、拆箱的功能提供了方便性,但隐藏了一些细节,所以必须小心。再来看范例4.6,您认为结果是什么呢?

Ü. 范例4.6 AutoBoxDemo2.java

public class AutoBoxDemo2 {

public static void main(String[] args) {
Integer i1 = 100;

Integer i2 = 100;

if (i1 == i2)

System.out.println("i1 == i2");

else

System.out.println("i1 != i2").

}

}

从自动装箱与拆箱的机制来看,可能会觉得结果是显示i1 == i2,您是对的。那么范例4.7的这个程序,您觉得结果是什么?

Ü. 范例4.7 AutoBoxDemo3.java

public class AutoBoxDemo3 {

public static void main(String[] args) {

Integer i1 = 200;

Integer i2 = 200;

if (i1 == i2)

System.out.println("i1 == i2");

else

System.out.println("i1 != i2");

}

}

结果是显示i1 != i2这有些令人惊讶,两个范例语法完全一样,只不过改个数值而已,结果却相反。

其实这与==运算符的比较有关,在第3章中介绍过==是用来比较两个基本数据类型的变量值是否相等,事实上==也用于判断两个对象引用名称是否参考至同一个对象。

在自动装箱时对于值从–128127之间的值,它们被装箱为Integer对象后,会存在内存中被重用,所以范例4.6中使用==进行比较时,i1 i2实际上参考至同一个对象。如果超过了从–128127之间的值,被装箱后的Integer对象并不会被重用,即相当于每次装箱时都新建一个Integer对象,所以范例4.7使用==进行比较时,i1i2参考的是不同的对象。

所以不要过分依赖自动装箱与拆箱,您还是必须知道基本数据类型与对象的差异。范例4.7最好还是依正规的方式来写,而不是依赖编译器蜜糖(Compiler Sugar)。例如范例4.7必须改写为范例4.8才是正确的。

Ü. 范例4.8 AutoBoxDemo4.java

public class AutoBoxDemo4 {
public static void main(String[] args) {

Integer i1 = 200;

Integer i2 = 200;

if (i1.equals(i2))

System.out.println("i1 == i2");

else

System.out.println("i1 != i2");

}

}

结果这次是显示i1 == i2使用这样的写法,相信也会比较放心一些,对于这些方便但隐藏细节的功能到底要不要用呢?基本上只有一个原则:如果您不确定就不要用。

posted @ 2008-05-16 11:33 晓宇 阅读(412) | 评论 (0)编辑 收藏

关于IndexWriter中的3个性能参数

IndexWriter中有3个重要的性能参数
mergeFactor           默认为10
minMergeDocs      默认为10
maxMergeDocs     默认为Integer.maxValue

maxMergeDocs     一个段中所能包含的最大的doc数,达到这个数目即不再将段进行合并 一般不改变这个值
minMergeDocs      是指在RAMDirectory中保存的Doc的个数,达到minMergeDocs 个即要合并到硬盘上去(在硬盘上新建一个段)
mergeFactor           合并因子,是控制硬盘上的段的合并的,每次在硬盘上新建一个段之后即执行
                                 targetMergeDocs*=mergeFactor(一开始targetMergeDocs=minMergeDocs) 如果硬盘上的doc数目大于等于                            targetMergeDocs则将硬盘上最后建立的mergeFactor个段进行合并成一个段

拿默认的参数举例:
如果硬盘上面已经有9个段  每个段分别存储了10个Document,共(90个DOC),这时候如果程序再向硬盘合并一个新的段(含10个DOC),合并完之后targetMergeDocs=10*10  程序检查已经合并的最后(按照创建的时间先后顺序)mergeFactor个段的document的总和100是否大于等于targetMergeDocs(这里是100,刚好满足要求)于是程序又将硬盘上面的后10个段合并为一个新的段。

另外一个例子:
doc数目            段数目
  1000---------------9个
  100-----------------9个
  10   ----------------9个
这时如果再象硬盘中新建一个新的包含了10个doc的段
    doc数目            段数目
  (1) 1000----------------9个

  (2)  100-----------------9个

  (3)   10  ----------------9个
                                     
  (4)    10 ----------------1个
这时候(3)(4)首先合并成一个新的段(3-4)包含100个doc
 然后(2)(3-4)和并成一个新段(2-3-4)包含1000个doc
然后(1)(2-3-4)合并成一个新的段  包含10000个doc
最后合并成一个段


private final void maybeMergeSegments() throws IOException {
    
long targetMergeDocs = minMergeDocs;
    
while (targetMergeDocs <= maxMergeDocs) {
      
// find segments smaller than current target size
      int minSegment = segmentInfos.size();
      
int mergeDocs = 0;
      
while (--minSegment >= 0{
        SegmentInfo si 
= segmentInfos.info(minSegment);
        
if (si.docCount >= targetMergeDocs)
          
break;
        mergeDocs 
+= si.docCount;
      }


      
if (mergeDocs >= targetMergeDocs)          // found a merge to do
        mergeSegments(minSegment+1);
      
else
        
break;

      targetMergeDocs 
*= mergeFactor;        // increase target size
      System.out.println("- -- - -targetMergeDocs:"+targetMergeDocs);
      
try {Thread.sleep(5000);} catch(Exception e) {};
    }

  }

posted @ 2008-05-15 19:27 晓宇 阅读(1409) | 评论 (0)编辑 收藏

HIBERNATE的一对多和多对一关联

HIBERNATE一多对关联中  要求在持久化类中定义集合类属性时,必须把属性声明为接口,因为HIBERNATE在调用持久化类的SET/GET方法时传递的是HIBERNATE自己定义的集合类。
在定义集合时,一般先初始化为集合实现类的一个实例 : private Set orders=new HashSet(),这样可以避免访问空集合出现NullPointerException.

posted @ 2008-05-14 11:01 晓宇 阅读(226) | 评论 (0)编辑 收藏

Lucene索引文件的格式

segments文件的格式: (段的信息)
int:  =-1    查看文件是否是Lucene合法的文件格式
long:        版本号,每更新一次该文件将会将版本号加1
int:         用来命名新段
int:         段的数目
String + int 段的信息 String是段的名称  int是段中所含的doc数目
String + int 同上


.fnm的文件格式:   (Field的信息)
int:               Field的个数,最少为1,最少有一个Field("",false),在初始化的时候写入(暂时不知道原因); 名称为空字符串,未索引,        未               向           量化。readVInt()读取
String: byte      String是 Field的名称  byte指示该Field 是否被索引,是否向量化 (值有:11,10,01)第一个1代表被索引,第二个代表被向量化
String: byte Field 同上
     

 

.fdx的文件格式:主要是提供对.fdt中存储的document的随即读取
long :       第一个document在.fdt文件中的位置
long:        第二个document在.fdt文件中的位置


.fdt的文件格式:  .fdt文件存储了一系列document的信息
VInt:        该document中的isStored属性为true的域的个数
(VInt:)      如果该field的isStored属性为true则得到该field的fieldNumber,暂时不知道这个fieldNumber是怎么产生的,有什么用,初步估计是按照field创建的顺序产生的,每次再上一个field的fieldNumber基础上加1。
byte:        如果该field的isTokenized属性为true写入1否则写入false。
String:      该field的stringValue()值。
一个document结束,下面的数据将会开始一个新的document,每个新的document的开始点的文件位置都会在.fdx中有记载,便于随即访问

 

posted @ 2008-04-21 17:52 晓宇 阅读(465) | 评论 (0)编辑 收藏

org.apache.lucene.index.SegmentInfos

final class SegmentInfos extends Vector
可以看出该类实际上是一个Vector   以及封装了对该Vevtor的一些操作
实际上封装的是对segments文件的一些读写操作
先来看下segments文件的格式

segments文件的格式:
int:  =-1         文件是否是Lucene合法的文件格式正常情况下为 -1
long:             版本号,每更新一次该文件将会将版本号加1
int:                用来命名新段
int:                段的数目
String + int  段的信息 String是段的名称  int是段中所含的doc数目
String + int  同上

所以用Lucene的API,我们可以简单的打印出其segments的所有信息

try {
   //DataInputStream fis = new DataInputStream(new FileInputStream("C:\\sf\\snow\\segments"));
   FSDirectory dir=FSDirectory.getDirectory("C:/sf/snow", false);
    InputStream input = dir.openFile("segments");
   System.out.println("Format:"+input.readInt());             //得到文件标志,是否为正常的segments文件
   System.out.println("version:"+input.readLong());        //得到版本号
   System.out.println("name:"+input.readInt());                //得到用来重命名新段的int,暂时不知道有什么用
   int n=input.readInt();                                                          //段的数目
   System.out.println("SegmentNum:"+n);                          
   for(int i=0;i<n;i++) {                                                           //用循环打印出所有段的信息 名称和长度
    System.out.println("segment "+i+" - name:"+input.readString()+" num:"+input.readInt());
   }
  } catch (Exception e) {

  }
当然,该类提供了更为复杂的访问和更新segments文件的方法
 final void read(Directory directory)    将所有的段信息保存在本vector中
final void write(Directory directory)    跟新该segment文件的内容,主要是为了添加段,
主要是更新 版本号 段的数目,跟新完这些后即可往segment文件后添加新段的信息。

posted @ 2008-04-18 17:02 晓宇 阅读(350) | 评论 (0)编辑 收藏

org.apache.lucene.index.SegmentInfo

segment(段)的信息
该类比较简单,贴出其全部代码

import org.apache.lucene.store.Directory;

final class SegmentInfo {
  public String name;        //在索引目录中唯一的名称 
  public int docCount;      // 该段中doc的数目
  public Directory dir;      // 该段所存在的Dirrectory

  public SegmentInfo(String name, int docCount, Directory dir) {
    this.name = name;
    this.docCount = docCount;
    this.dir = dir;
  }
}

posted @ 2008-04-18 16:45 晓宇 阅读(253) | 评论 (0)编辑 收藏

org.apache.lucene.store.RAMInputStream

该类是从RAMFile中读数据用的
最重要的一个方法:
该方法存在着从RAMFile的多个byte[1024]中读取数据的情况,所以应该在循环中进行处理

 public void readInternal(byte[] dest, int destOffset, int len) {
    int remainder = len;
    int start = pointer;
    while (remainder != 0) {
      int bufferNumber = start/BUFFER_SIZE; //  buffer的序号
      int bufferOffset = start%BUFFER_SIZE; //    buffer偏移量
      int bytesInBuffer = BUFFER_SIZE - bufferOffset;// 在当前buffer中剩下的字节数
      //如果缓冲区中剩余的字节大于len,则读出len长度的字节,如果不够则读出剩余的字节数
      // bytesToCopy表示实际读出的字节数
      int bytesToCopy = bytesInBuffer >= remainder ? remainder : bytesInBuffer;
      byte[] buffer = (byte[])file.buffers.elementAt(bufferNumber);
      System.arraycopy(buffer, bufferOffset, dest, destOffset, bytesToCopy);
      destOffset += bytesToCopy;       //增加已经复制的byte数据长度 到  dest中的偏移量
      start += bytesToCopy;                 //RAMFile文件指针,用来确定bufferNumber 和bytesInBuffer   相当于内存中的分页
      remainder -= bytesToCopy;       //剩余的还未复制的字节数
    }
    pointer += len;//文件指针位置
  }

posted @ 2008-04-18 11:45 晓宇 阅读(209) | 评论 (0)编辑 收藏

org.apache.lucene.store.RAMOutputStream

这是OutputStream的一个子类,其输出设备是内存,准确来说是RAMFile,即将数据写入到RAMFile的Vector中去。
该类有一个最重要的方法,现在把它整个贴出来

public void flushBuffer(byte[] src, int len) {
    int bufferNumber = pointer/BUFFER_SIZE;   //buffer序列,即当前所写Buffer在RAMFile中的Vector中的序列号
    int bufferOffset = pointer%BUFFER_SIZE;   //偏移量,即当前所写字节在当前Buffer中的偏移量。
    int bytesInBuffer = BUFFER_SIZE - bufferOffset; //当前Buffer的剩余可写字节数
   //bytesToCopy是实际写入的字节数,如果当前Bufer的剩余字节数大于需要写的字节的总数则写入所有字节
   //否则,将当前Buffer写满即可,剩余的字节将写入下一个Buffer
    int bytesToCopy = bytesInBuffer >= len ? len : bytesInBuffer;

    if (bufferNumber == file.buffers.size())
      file.buffers.addElement(new byte[BUFFER_SIZE]); //在RAMFile中添加新的byte[1024]元素

    byte[] buffer = (byte[])file.buffers.elementAt(bufferNumber);
    System.arraycopy(src, 0, buffer, bufferOffset, bytesToCopy);

    if (bytesToCopy < len) {     // not all in one buffer,
      int srcOffset = bytesToCopy;
      bytesToCopy = len - bytesToCopy;    // remaining bytes 剩余的未写入的字节数
      bufferNumber++;                         //将buffer数增加1
      if (bufferNumber == file.buffers.size()) 
        file.buffers.addElement(new byte[BUFFER_SIZE]);
      buffer = (byte[])file.buffers.elementAt(bufferNumber); //剩余字节写入下一个Buffer
      System.arraycopy(src, srcOffset, buffer, 0, bytesToCopy);
    }
    pointer += len;
    if (pointer > file.length)
      file.length = pointer;        //移位文件指针          在原有的基础上加上实际写入的字节总数

    file.lastModified = System.currentTimeMillis(); //修改文件的最后修改时间为当前时间
  }

从指定的字节数组复制指定长度的字节到RAMFile中去。由于RAMFile中Vector的元素是byte[1024]所以可能存在做一次该操作
要操作两个Vector元素的情况。即先将当前byte[1024]数组填满,再新建一个元素装载剩余的字节。

另外还有一个writeTo(OutputStream out)方法,将RAMFile中的数据输出到另一个输出流



posted @ 2008-04-18 11:38 晓宇 阅读(212) | 评论 (0)编辑 收藏

org.apache.lucene.store.RAMFile

这个类比较简单
import java.util.Vector;
class RAMFile {
  Vector buffers = new Vector();
  long length;
  long lastModified = System.currentTimeMillis();
}

可以理解为一个存储在内存中的文件,buffers是存储数据的容器,length是容器中数据的总的字节数
lastModified 是最后修改时间。

在实际使用过程中容器buffers存放的对象是一个byte[1024]数组。

posted @ 2008-04-18 11:23 晓宇 阅读(276) | 评论 (0)编辑 收藏

org.apache.lucene.store.OutputStream

OutputStream
这是一个Abstract类,是Lucene自己的一个文件输出流的基类
BUFFER_SIZE = 1024  缓冲区 大小为 1024bit
bufferStart = 0 文件位置指针
bufferPosition = 0 内存缓冲区指针

 public final void writeByte(byte b) throws IOException {
    if (bufferPosition >= BUFFER_SIZE)
      flush();
    buffer[bufferPosition++] = b;
  }
几乎所有的写入函数都要调用这个函数,如果缓冲区的当前容量已经等于他的最大容量,则将缓冲区中的数据写入文件。

public final void writeBytes(byte[] b, int length) throws IOException
批量写byte进入内存缓冲

public final void writeInt(int i) throws IOException
写入整形数据

public final void writeLong(long i) throws IOException
写入长整型数据,即结合移位运算调用两次writeInt(int i)

另外,最值得注意的是在该类中有两个最特殊的函数
writeVInt(int i) /   writeVLong(long i),
先说
writeVInt(int  i )   {
 while ((i & ~0x7F) != 0) {                               
      writeByte((byte)((i & 0x7f) | 0x80));
      i >>>= 7;
    }
    writeByte((byte)i);
}
~0x7F==~(0111 1111)==(1000 0000)==0x80
((i & ~0x7F) != 0) 这一句判断i是否大于0x80,如果不是则说明该int只有一个字节的有效数据,其他字节都是0,直接转化为Byte写入。
如果大于0x80则
(i & 0x7f) | 0x80
i&0x7f 只对后7位进行处理,|0x80将第8位置1,与前面的7个bit构成一个字节,置1的原因是说明该字节并不是一个完整的整形数,需要与其他的字节合起来才能构成一个整形数字。
这个算法相当于将一个32bit的整形数字按照每7位编码成一个字节进行存储,将按照整形数的大小存储1-5个字节。

writeVLong(long i)方法大致与其相同。

final void writeChars(String s, int start, int length)
将字符串转化成UTF-8编码的格式进行存储。
附:

UNICODE值 UTF-8编码
U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx

可见对于在 0x00-0x7F范围内的UNICODE值(最大有效数位:7位),将会编码成单字节的,会大大节约存储空间。
对于在           0x80-0x7FF范围内的UNICODE(最大有效数位:11位),会编码成双字节的。先存储原字节低5位的数位,且将最高位和次高位都置1,再次高位置0(writeByte((byte)(0xC0 | (code >> 6)));)。然后存储后6位的字节,将前两位置10(writeByte((byte)(0x80 | (code & 0x3F)));)
对于其他的UNICODE值则
writeByte((byte)(0xE0 | (code >>> 12)));               4位
 writeByte((byte)(0x80 | ((code >> 6) & 0x3F)));   5位
 writeByte((byte)(0x80 | (code & 0x3F)));            3- 5位

final void writeString(String s) throws IOException
该函数首先用s.length()判断该String总共有多少个字符
然后首先调用writeVInt写入这个字符长度
再调用writeChars(s,s.length())写入字符


在inputStream中的readString()方法则与其相反,首先用readVInt()方法读取字符长度len 然后读取len长度的字符

protected final void flush() throws IOException
该方法调用另外一个方法flushBuffer将缓冲区中的数据输出,然后清空缓冲区;

abstract void flushBuffer(byte[] b, int len) throws IOException
可见flushBuffer方法是abstract的,即需要其子类对该方法进行覆写,以定位该输出流的输出方式。

final long getFilePointer() throws IOException
得到文件指针的位置,即得到输出流已经输出的字节数。

public void seek(long pos) throws IOException
输出缓冲区的内容,然后将文件指针定位到long所指示的文件位置。

abstract long length() throws IOException
返回文件中已有的字节数。需要子类实现。

















posted @ 2008-04-16 21:24 晓宇 阅读(208) | 评论 (0)编辑 收藏

org.apache.lucene.store.FSDirectory

FSDirectory继承了abstract类Directory
在该类中既有该类的一些初始化操作,又有对FSDirectory对象本身的一些操作,这是为什么把其构造函数设置为私有的一部分原因

static final Hashtable DIRECTORIES = new Hashtable();
每新建一个FSDirectory都会将其加入到该Hashtable中来。名称是FSDirectory对应的File   值是该FSDirectory。
注意:final对象并非是不可更改的

static final String LOCK_DIR =
    System.getProperty("org.apache.lucene.lockdir",
      System.getProperty("java.io.tmpdir"));
首先看用户是否注册了"org.apache.lucene.lockdir"属性,如果没有则用JAVA虚拟机固有的"java.io.tmpdir"属性
这个属性是一个路径,代表lucene的锁文件锁放的位置。

static final boolean DISABLE_LOCKS =
      Boolean.getBoolean("disableLuceneLocks") || Constants.JAVA_1_1;
如果用户注册了"disableLuceneLocks"属性且为false,或者JAVA的版本是1.1则无法使用锁。

static FSDirectory getDirectory(String path, boolean create)
static FSDirectory getDirectory(File file, boolean create)
从得到一个指定路径或者文件的FSDirectory如果在则取出,如果不存在则用其私有的构造函数构造一个
该类还有3个非static的类变量
  private File directory = null;      索引目录
  private int refCount;                   锁目录
  private File lockDir;                    索引目录数目
实际上,初始化一个FSDirectory只需要初始化这3个变量即可
如果create的值为true 则:如果索引目录是已经存在的目录,则会遍历该目录然后删除每一个文件,如果锁目录是已存在的也会用list返回所有的文件然后调用file.delete() 删除。 如果目录不存在则创建一个新的。

注意:list()方法   会先用文件名进行排序然后返回(a.txt会比b.txt先返回)    且delete方法删除文件夹时,只能删除空文件夹。如果失败则跳出程序,不会删除在该文件夹之后返回的文件。(如果有aa.txt , ab/b.txt , b.txt , 则删除时候由于a文件夹非空删除失败,则b.txt由于前面删除失败跳出程序,也不会被删除,但是aa.txt被正常删除)

private FSDirectory(File path, boolean create) throws IOException
私有的构造函数

private synchronized void create() throws IOException
创建新的directory /lockDir目录,当目录已存在时即清空该目录,不存在即创建新的目录。

final String[] list() throws IOException
以字符串文件名的形式返回索引目录的所有文件

final boolean fileExists(String name) throws IOException
在索引目录是否存在指定文件名的文件

final long fileModified(String name) throws IOException
static final long fileModified(File directory, String name)
返回该文件的最后修改时间,directory参数为相对路径,第一个函数的相对路径为索引目录

void touchFile(String name) throws IOException
将该文件的最后修改时间设置为当前时间

final long fileLength(String name) throws IOException
返回该文件的长度

final void deleteFile(String name) throws IOException
删除该文件

final synchronized void renameFile(String from, String to) throws IOException
重命名该文件
该方法会首先检测新的文件名命名的文件是否已经存在如果存在即删除该文件,然后再将文件重新命名为新的文件名。
doug cutting在该方法的注释上写到:
1.删除操作和重命名的操作不是原子的。
2.重命名操作在有些虚拟机上面不能正确的工作,如果重命名失败则会采用手动copy的方法。使用输入输出流将旧的文件的内容写入到新的文件中去,然后删除旧的文件。
注意:该方法必须是同步的。

final OutputStream createFile(String name) throws IOException
用指定的文件名创建一个新的可写的空文件  实际上返回的是FSOutputStream,注意这里的OutputStream并不是java的基础类。而是doug cutting自己写的一个文件随即访问类。同理FSInputStream和InputStream也是Lucene自己的类。

final InputStream openFile(String name) throws IOException
从一个存在的文件打开一个输入流

getLockPrefix()
在FSDirectory中还有
 private static MessageDigest DIGESTER;这个静态变量是提供加密功能的
DIGESTER=MessageDigest.getInstance("MD5"),-----MD5加密算法
或者可以DIGESTER=MessageDigest.getInstance("SHA"),-----SHA加密算法
用于对锁目录的   文件名的加密
用法如下:
digest = DIGESTER.digest(dirName.getBytes());  dirName是需要被加密的字符串,这里是索引文件的目录名,
在FSContext中,其应用在 getLockPrefix() 该方法是为某个索引目录创建其对应的锁目录文件名。
首先返回经过加密后的byte[] 数组digest,然后将digest按照每4个bit转化为一个16进制的字符,存进一个StringBuffer中
其转化类似与Base64编码方式,不过要简单得多。

方法
Lockl  makeLock(String name)
是从Directory中扩展而来的,该方法返回一个Lock对象,该对象将会在介绍完Lucene的输入输出流之后介绍。
该方法比较简单,首先是调用了getLockPrefix() 方法,返回文件锁的部分对象名,然后在该名称后面加上锁的特征名
譬如说读写锁 事务锁
其名称类似于下:
lucene-12c90c2c381bc7acbc4846b4ce97593b-write.lock
lucene-12c90c2c381bc7acbc4846b4ce97593b-commit.lock
这两种锁机制将会在后面介绍
最后通过一个匿名的内部类返回一个经过重载的Lock对象,该内部类中的方法有锁的创建,得到,释放,以及检测,另外还有一个toString()方法返回锁的名称。



在FSDirectory类中有OutputStream和InputStream的实现类,这两个虚类只是定义了一些操作,并没有定义输入或者输出的设备。
Lucene在输入输出的设计上,将会由子类定义输入输出的设备。
FSOutputStream
在FSOutputStream中有一个 RandomAccess File=new RandomAccessFile(path, "rw");
在对该输出流的操作将用调用该file的相应方法实现
最重要的
  public final void flushBuffer(byte[] b, int size) throws IOException {
    file.write(b, 0, size);
  }
flushBuffer的调用将会将byte中的0--size范围的数据写入到文件path中去。


FSInputStream
最重要的
protected final void readInternal(byte[] b, int offset, int len)
       throws IOException {
    synchronized (file) {
      long position = getFilePointer();     //得到该当前文件指针
      if (position != file.position) {
        file.seek(position);
        file.position = position;
      }
      int total = 0;
      do {

   //从文件中读取指定长度的字节到字节数组
   // 在其基类InputStream中的refill()方法  将会调用  readInternal(buffer, 0, bufferLength);首先从文件中读取字节到缓冲数组。
  //  在InputStream中每次读取操作都会调用readInternal方法,或者通过refill()方法间接调用该方法。
        int i = file.read(b, offset+total, len-total);       //将文件中的数据读到缓冲区
        if (i == -1)
          throw new IOException("read past EOF");
        file.position += i;
        total += i;
      } while (total < len);
    }
  }





















posted @ 2008-04-10 21:35 晓宇 阅读(1084) | 评论 (0)编辑 收藏

org.apache.lucene.document.DateField

该类提供了日期和字符串之间的相互转化,实际上是 long型和String型的相互转化,转化时用到了一个不常用的
Long.toString(long,int);方法。是按指定的方式对long型进行转化
第一个参数是要转化的long,第二个参数是转化时候的基数,如果基数是10就相当于方法Long.toString(long);
这里使用的参数是最大值,即36== 10个数字+26个英文字母。这样转化出来的字符串长度比较短,占用比较少的空间,
另外,在转化时,统一了转化后的字符串长度,如果不足9位(日期的long转化后最高为9位,1970之后的日期可正确转换),
统一长度后的字符串可以通过比较字符串来比较日期的大小。


日期转化成的字符串类似于
0fev8eza3
本来应该是fev8eza3 采取了不足9位补0的方法。

  private static int DATE_LEN = Long.toString(1000L*365*24*60*60*1000,
            Character.MAX_RADIX).length();
计算出从1970年开始后1000年的时间转化为字符串后的长度,所有转化后的时间都不应超过这个长度,如果不足则在前面补0

可以通过字符串转化为日期的函数计算出能表示的最大日期为
stringToTime("zzzzzzzzz");
打印出来是 Fri Apr 22 19:04:28 CST 5188  
所以该函数能转化的日期范围为 1970-1-1~~5188-4-22


日期转化为字符串
public static String timeToString(long time)

字符串转化为日期
public static long stringToTime(String s)


实际上 函数 LongToString(long i,int radix) 相当于  先将i转化为radix进制的整数,然后再用函数
LongToString(i)转化为字符串。所以radix的值应该在2--36之间如果不是 则按照10进制计算。

posted @ 2008-04-10 19:26 晓宇 阅读(320) | 评论 (0)编辑 收藏

org.apache.lucene.document.Document

Document是一些Field的集合,每个Field有一个名字和文本值,当中的某些Field可能会随着Documnet被存储。这样,每个Document应该至少包含一个可以唯一标示它的被存储的Field

//Field集合
List fields = new Vector();
//增强因子,作用于该Document的所有Field
private float boost = 1.0f;
//向Document中添加Field
public final void add(Field field) {
    fields.add(field);
  }
//删除指定名称的第一个Field
public final void removeField(String name)
//删除所有拥有指定名称的Field
public final void removeFields(String name)
//得到指定名称的第一个Field
public final Field getField(String name)
//以数组的形式返回指定名称的所有Field
public final Field[] getFields(String name)
//得到所有Field的一个枚举
public final Enumeration fields()

该类也重载了toString()方法
打印出所有Field的信息




posted @ 2008-04-08 20:27 晓宇 阅读(720) | 评论 (0)编辑 收藏

org.apache.lucene.document.Field类

package org.apache.lucene.document;   
Field
是Document的一部分,每个Field有两个部分组成 名字-值 对 名字是String 值 可以是String 和 Reader,如果是KeyWord类型的Field,那么值将不会被进一步处理,像URL,Date等等。Field被存储在Index中,以便于能以Hits的形式返回原有的Document
Field有3 个Boolean形的标识
  private boolean isStored = false;     被存储  
  private boolean isIndexed = true;    被索引
  private boolean isTokenized = true  被分割
通过调整这3个boolean的值,可以确定该Field的类型
  Keyword      true, true, false           一般存储 URL DATE 等关键字
  UnIndexed    true, false, false          一般是随HITS查询结果一起返回的信息
  Text              true, true, true           
  UnStored      false, true, true

另外,还有一个重载的toString方法 可以打印出该Field的类型

float boost = 1.0f;    增强因子,用于排序的评分,作用于拥有该域(field)的所有文档(document)

  
 

posted @ 2008-04-08 20:07 晓宇 阅读(922) | 评论 (0)编辑 收藏

数据库的事务 JDBC

TRANSACTION_NONE:
  正式地讲,TRANSACTION_NONE不是一个有效的事务级别。
  根据java.sql Connection API文件,这个级别表示事务是
  不被支持的,因此理论上说你不能使用TRANSACTION_NONE作
  为一个自变量赋给Connection.setTransactionIsolation()
  方法。事实上,虽然一些数据库实施了这个事务级别,但是
  Oracle9i却没有实施。


 脏读取(TRANSACTION_READ_UNCOMMITTE):
(允许的操作       读-读  读-写 写-读     (脏数据,不可重复读,虚读) )
   表示,这个事务级别
  允许读取脏数据,什么是脏数据?就是指还没有提交的数据.
  因为这个级别,是允许一个事务(A)读取另一个事务(B)
  还没有提交的数据.一旦事务B发生异常退出.而修改了的数据
  却还没提交,或者新插入的数据和删除了的数据都还没有
  提交,导致事务A拿到了一些脏数据,或者错误数据;
  因此在这个事务级别里是会发生脏读,重复读,错误读取;

 禁止脏读(TRANSACTION_READ_COMMITTED):
(允许的操作       读-读  读-写 (不可重复读,虚读))
  在这个级别中,事务A
  只能读取一些提交的数据,如事务B添加了一条记录,但是
  如果事务B没有提交,那么事务A是读不到的,所以该事务级别,
  把脏读给屏蔽掉了.---却允许重复读取,和错误读取.

  什么是重复读取呢?譬如,事务A读取了一个数据,这个数据
  的值为"helloworld",事务A准备利用这个数据来更新一下
  其他数据,但这个时候事务B开始对这个数据进行修改,并且
  提交---"hello 无名氏",由于是已经提交了,所以事务A是可以
  看到这个数据的,当事务A在没提交事务之前,它想看下数据
  是否正确,这个时候它发现,新读出的数据已经和原来的数据
  不一样了(这就是重复读取);




 允许重复读取(TRANSACTION_REPEATABLE_READ):
(允许的操作       读-读  读-写(仅允许插入,不允许删除和修改)(虚读))
  在这个级别中,
  是禁止了脏读,和取消了不可重复读取,但是没有禁止错误读取;
  这个级别的事务比较严格,当一个事务A在读取一个值的时候
  是不允许另一个事务对该值进行修改的;
  为了允许重复读取,可以选用该级别,因为TRANSACTION_READ_
  COMMITED这个事务级别,是允许重复读取提交的数据的,如果
  事务A在读取一个数值的时候,值为"Hello World!",但这个时
  候事务B对"Hello World"值进行修改了,改为"Hello EveryOne"
  然后提交,当事务A再次去读取这个值的时候,去发现原来读到
  的值改变了,变成了"Hello EveryOne",为了防止出现这种情况
  可以禁止重复提交,目的是为了重复读取不会出错!那么这个
  时候就可以选择
TRANSACTION_REPEATABLE_READ这个级别,
  这个级别就是用来禁止重复提交的.
(实际上是加了行锁,锁定了选中的数据,不允许修改,但是允许插入新的数据)
  虽然这个时候是禁止了重复提交,但却可以添加删除,
  比如事务A,作了个查询语句"select * from 无名氏 "; 这个时候是允许事务B做这样的操作的:
  "insert into 无名氏 values(2,'aaa')"; 这个时候,
  事务A再次做读取操作的时候,却发现数据莫名其妙的
  多了一条,这就是所谓的---幻影读取;




 禁止幻读(TRANSACTION_SERIALIZABLE):
 事务的最高级别(串行化  操作)事务级别最高,所耗费的性能也越多.
  禁止幻读禁止了脏读,禁止了重复提交和幻读.
  也就是当事务A在按条件查询的时候,事务A一旦没有提
  交,任何事务都不能对事务A的资源进行操作--- 保证
  事务A的操作真正的原子性!


 注意:在Oracle中只支持两种级别:

  TRANSACTION_READ_COMMITTED(默认的级别)(只有提交后
     才可以读取)而每一个终端进行自己的DML操作 都自动开启了一个事务

  TRANSACTION_SERIALIZABLE(窜行化操作)

posted @ 2008-03-05 09:34 晓宇 阅读(308) | 评论 (0)编辑 收藏

字符串池

Java运行环境有一个字符串池,由String类维护。执行语句String str="abc"时,首先查看字符串池中是否存在字符串"abc",如果存在则直接将"abc"赋给str,如果不存在则先在字符串池中新建一个字符串"abc",然后再将其赋给str。执行语句String str=new String("abc")时,不管字符串池中是否存在字符串"abc",直接新建一个字符串"abc"(注意:新建的字符串"abc"不是在字符串池中),然后将其付给str。前一语句的效率高,后一语句的效率低,因为新建字符串占用内存空间。String str = new String()创建了一个空字符串,与String str=new String("")相同。

public String intern()
返回字符串对象的规范化表示形式。

一个初始为空的字符串池,它由类 String 私有地维护。

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。

它遵循以下规则:对于任意两个字符串 st,当且仅当 s.equals(t)true 时,s.intern() == t.intern() 才为 true

  String.intern();
再补充介绍一点:存在于.class文件中的常量池,在运行期间被jvm装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,java查找常量池中是否有相同unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个unicode等于str的字符串并返回它的引用。
例3:
String   s0=”kvill”;
String   s1=new   String(“kvill”);
String   s2=new   String(“kvill”);
System.out.println(s0==s1);
S1.intern();
S2=s2.intern();
System.out.println(s0==s1);
System.out.prntln(s0==s1.intern());
System.out.println(s0==s2);
结果为:
False
False   //虽然执行了s1.intern(),但它的返回值没有赋给s1
True
True
最后再破除一个错误的理解:
有人说,“使用String.intern()方法可以将一个String类保存到一个全局的String表中,如果具有相同值的unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中”如果把这个全局的String表理解为常量吃的话,最后一句话“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的。
例4:
String   s1=new   String(“kvill”);
String   s2=s1.intern();
System.out.println(s1==s1.intern());
System.out.println(s1+”   ”+s2);
System.out.println(s2==s1.intern());
结果是:
False
Kvill   kvill
True
我们没有声明一个”kvill”常量,所以常量池中一开始没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“把自己的地址注册到常量池中”了。
例5:
String   str1=”java”;
String   str2=”blog”;
String   s=str1+str2;
System.out.println(s==”javablog”);
结果是false。Jvm确实对型如String   str1=”java”;的String对象放在常量池里,但是它是在编译时那么做的,而String   s=str1+str2;是在运行时刻才能知道,也就是说str1+str2是在堆里创建的,所以结果为false了。


比较两个已经存在于字符串池中字符串对象可以用"=="进行,拥有比equals操作符更快的速度

posted @ 2008-03-04 09:40 晓宇 阅读(831) | 评论 (0)编辑 收藏

JAVA中的异常处理

在JAVA中,如果某个方法不能够按照正常的途径完成它的任务,就可以通过另外一个途径退出方法。在这种情况下,方法并不立刻返回,而是抛出一个封装了错误信息的对象。需要注意的是,这个方法将会立刻退出,且并不返回正常情况下的值。此外,调用这个方法的代码也将无法执行,取而代之的是,异常处理机制开始搜索能够处理这种错误条件的异常处理器。

如果在子类中覆盖了超类的一个方法,那么子类方法声明的已检查异常不能超过超类方法中声明的异常围。(也就是说子类中抛出的异常范围更小或者根本不抛出异常)。需要说明的是,如果超类方法没有抛出任何异常,子类也不能抛出任何已检查异常。例如,如果覆盖JComponent.paintComponent()方法,那么    由于超类中没有抛出任何异常,所以自定义的paintComponent()方法也不能抛出任何异常。

如何抛出异常:
譬如在读一个输入流时,如果期待的字节数为1024,而当读到700字节的时候遇到一个EOF信号,这是我们未预期的,正是我们要抛出的异常,下面就是抛出改异常的语句

1String readData() throws EOFException {
2   while ({
3      if(!in.hasNext()) {
4          if(n<len)
5            throw new EOFException();
6     }
  
7  }

8}

这是抛出已存在标准的异常,步骤为:
1.找到一个合适的异常类
2.创建该类的对象
3.将异常抛出
一旦抛出异常,该方法就不可能返回到调用者,这就意味着不必为返回的默认值或错误代码担忧了。

在程序中可能会遇到任何标准异常都无法正确描述清楚的问题,这时候就需要定义自己的异常类,定义自己的异常类需要继承自Exception 或者其子类,如继承自IOException  一般要为自定义的Exception定义两个构造函数,一个是默认的无参的构造函数,另一个是带一个描述详细错误信息String的构造函数(超类的toString()方法会打印出这些详细信息。

1public class MyException extends IOException {
2
3    MyException () {}
4    
5    MyException (String info) {
6       super(info);
7   }

8}

现在就可以像使用标准Exception类一样使用自定义的Exception类了

1String readData() throws MyException {
2   while () {
3      if(!in.hasNext()) {
4          if(n<len)
5            throw new MyException();
6    }
  
7 }

8}

可以用超类Throwable类的getMessage方法得到详细的错误描述信息。或者toString()方法打印该信息。

捕获异常:
如果语句exp1有可能抛出一个异常,或者说是调用了能够抛出一个异常的方法,那么我们可以在程序中捕获该异常

1try {
2 exp1;
3 exp2;
4 .
5
6}
 catch(Exception e) {
7   e.
8}

9

或者是并不捕获,再次抛出该异常

再次抛出异常,异常链:
可以在catch块中再次抛出新的异常,这样可以隐藏原始的错误细节

1catch(SQLException sqle) {
2    throw new ServletException("caused by SQLException "+e.getMessage());
3}

另一种更好的方法
既抛出高级的异常,又不会丢失原始的异常信息。

1}catch(SQLException sqle) 
2    Throwable ta=new ServletException("caused by SQLException");
3    ta.setCause(e);
4    throw ta;
5}

6当捕获抛出的异常ta时,可以用ta.getCause(),得到原始的异常


finally子句,发生三种情况时,会执行finally子句
1.try代码正常执行
2.try代码抛出一个在catch子句中捕获的异常
3.try代码抛出一个未在catch子句中捕获的异常
就是说无论异常如何,finally子句中的代码都会执行
一般把一些资源回收的工作交给finally子句

最好把try-catch块和try-finally块分开,这样可以提高代码的清晰度

 1try {
 2   try {
 3      //可能抛出IOException的语句
 4    }
 finally {
 5    is.close();
 6 }

 7}
 catch(IOExceotion e) {
 8   
 9}

10

这样的另外一个好处是,可以catch到finally子句中抛出的异常。这也就引发了另外一个问题,如果在try和finally中都抛出了异常,而且是不同类型的,那么最后返回的是那一个呢?答案是finally中抛出的异常会覆盖try中抛出的异常。从而丢失了try当中抛出的异常信息,让抛出该方法的使用者不能跟踪异常信息。所以,在finally中执行的语句最好是不要抛出异常,但是不幸的是我们有时候不得不在finally中执行一些清楚操作如:关闭输入流InputStream,但是InputStream类的设计者并没有这么设计(将会抛出异常)。

由于finally子句总是会执行,所以在finally中包含return值时,是很危险的。假设我们利用return语句从try中返回,在方法返回之前finally子句的内容将会被执行。如果finally子句当中也包含一个return 语句,那么这个return语句所返回的值,将会覆盖原先return的值。

1public int f(int n) {
2  try {
3      return n*n;
4    }
 finally {
5     if(n==2)
6       return 2;
7  }

8}

在这里如果调用i=f(2);那么i的值将会是2而不是期望的2*2=4


最后最重要的一点,关于一个方法是捕获一个异常还是抛出给调用他的方法
有一点规则   
                                早抛出,晚捕获,尽量把异常抛出给调用它的函数

posted @ 2008-02-26 14:05 晓宇 阅读(516) | 评论 (0)编辑 收藏

JAVA 的字符编码的问题

首先讲一讲字符集
 JDK1.4版本引入了java.nio包加入了Charset类来统一字符集的转换,字符集给出了双字节Unicode码序列与本地字符编码中采用的字节序列之间的映射
字符集的名称不区分大小写,可以用官方名称或者任何一个别名调用静态方法forName来获得Charset

1Charset charset=Charset.forName("GBK");
Charset的aliases()方法返回一个Set<String>集合,表示该字符集所具有的所有别名
Set<String> aliases=charset.aliases();
查看虚拟机所支持的字符集以及名字可用Charset的静态方法availableCharsets()返回一个SortedMap
1 Map<String,Charset> map=Charset.availableCharsets();
2            for(String setname:map.keySet()) {
3                System.out.println(setname);
4            }
一旦有了字符集,就可以用他在本地字符串和Unicode字节序列之间转换
如下将一个字符串编码为gbk    charset.encode(str)函数返回一个ByteBuffer对象
1String str=new String("晓宇");
2            ByteBuffer  buff = charset.encode(str);
3            byte[] bytes=buff.array();
同样,可以将刚才按照GBK方式编码的字节流解码为正确的字符流
1ByteBuffer by=ByteBuffer.wrap(bytes, 0, bytes.length);
2      CharBuffer cb=charset.decode(by);//返回一个CharBuffer对象,已经将字节数组转化为字符数组
3      System.out.println("  "+cb.toString());


实际上String类中集成了Charset对象的使用,String类有两个用于编码和解码的函数

1byte[] bytes = str.getBytes("CharsetName");  //将字符串str按照指定名称的Charset进行编码成字节数组
2            String str2=new String(bytes,"CharsetName"); //将字节数组按照指定名称的Charset解码为字符串


在输入输出流中有两个用于将字节流转化为指定编码格式的字符流的类  InputStreamReader/OutputStreamReader
这两个类是读写流和读写器的桥梁 用法如下

1InputStreamReader isr = new InputStreamReader(in, "charsetname");
2            BufferedReader br = new BufferedReader(isr);//加入一个BufferedReader,可以用到该类的readLine()

该用法在我的另外一篇socket备忘的里面体现了,可将网络传过来的utf-8格式编码的字节流正确的解码,以至于显示的时候不会出现乱码





posted @ 2008-02-24 16:44 晓宇 阅读(484) | 评论 (0)编辑 收藏