FastExcel: Version 0.3.3
一、Compound File类设计
和复合文档有关类存放在如下几个package:
edu.npu.fastexcel.compound.io; 把对操作系统文件的IO操作封装成Reader和Writer,主要基于RandomAccessFile。为了提高文件读取速度,使用了Memory Mapped File,基于MappedByteBuffer。
edu.npu.fastexcel.compound.stream; 将Reader和Writer封装成ReadableStream和WritableStream。这里的Stream类就是对应于复合文档里的Stream。
edu.npu.fastexcel.compound; 将Stream包装成StreamReader和StreamWriter,比如SectorStreamReader就是读取复合文档中的扇区。最后提供复合文档的FileReader和FileWriter。
这部分的类设计和Java的IO类设计很类似,再查看一下Java的IO类设计,体味一下,加深理解和印象。
此外这部分还包含一个SummaryInformation类,这个类有点独立,对于Excel读写来说是可有可无的。当初作者可能一时兴起,把它也写出来了。Office文档一般都有Summary Information Stream。加上这个类,整个Compound File类体系就比较完整了。
二、BIFF8类设计
1)BIFF8中比较有用的Record类别:
BOF,EOF:Substream的开始,结尾
SST:Shared String Table,存放所有出现在Worksheet中的字符串
SHEET:存在于Workbook Globals Substream,代表Workbook中的一个Sheet,描述了该Sheet的名字,类别,状态,偏移位置等信息。
DIMENSION:包含当前Sheet的有效范围,起止行号,起止列号。
LABELSST:单元格的字符串内容通过索引指向SST中的某个字符串。
2)Record类设计:
所有支持的BIFF8 Record类别都有对应的Record和Parser(RecordParser)类。比如SSTRecord,SSTParser。Record类用来处理写入,Parser类用来处理读取。
Record类组织成如下几个package:
edu.npu.fastexcel.biff.record; 处理通用的Record类别,可能出现各个Substream里,比如BOF
edu.npu.fastexcel.biff.record.globals; 处理那些只会出现在Globals Substream里的Record类别,比如SHEET
edu.npu.fastexcel.biff.record.sheet; 处理那些只会出现在Worksheet Substream里的Record类别,比如DIMENSION
edu.npu.fastexcel.biff.record.cell; 处理和单元格密切相关的Record类别,比如LABELSST
RecordFactory提供方法返回各种Record类的实例。RecordFactory使用Singleton模式。实现Singleton模式的方法是采用静态内嵌类的静态字段,不知道为什么这么实现?有什么好处?实现代码片段如下:
static class InstanceHolder {
static RecordFactory instance = new RecordFactory();
}
类似Parser类也组织成如下几个package:
edu.npu.fastexcel.biff.parser; 处理通用的Record类别,可能出现各个Substream里,比如BOF
edu.npu.fastexcel.biff.parser.globals; 处理那些只会出现在Workbook Globals Substream里的Record类别,比如SHEET
edu.npu.fastexcel.biff.parser.sheet; 处理那些只会出现在Worksheet Substream里的Record类别,比如DIMENSION
edu.npu.fastexcel.biff.parser.cell; 处理和单元格密切相关的Record类别,比如LABELSST
所有的Parser类都在ParserFactory里登记,存放在一个Map里,Map的键就是Record的类型号,值就是一个Parser对象。这里没有使用Singleton模式,反而有点Factory模式的味道。读取Worksheet Substream时有两种模式:全读取模式和事件模式。全读取模式就是在访问某个Sheet里的某个单元格之前,该Sheet的内容都已经读取完毕,访问时,就是简单的返回内容。事件模式就是在解析一个Sheet的过程中,解析出一个单元格就触发事件,客户程序可以按意愿监听事件。这两种模式有点类似XML的两种解析器,DOM和SAX。显然,事件模式采用监听器模式。监听器接口是SheetReadListener。抽象类SheetReadAdapter implements SheetReadListener,对监听器提供默认实现,这种实现方法应该叫 Adapter模式吧,类似于Java AWT里的MouseEvent,MouseEventListener,MouseAdapter。EventBasedSheetStream是事件的触发器,它有一个字段保存当前的监听器。监听器的设置是通过get和set方法,而不是add和remove方法,可见只支持一个监听器,不支持事件的多播。也许这个情境下的多播没啥意义。
3)BIFF8 File类设计
主要是三个package:
edu.npu.fastexcel.biff; 读取和写入都公用的一些东西,这里只有所有的Record Type常量,放在Types类中。
edu.npu.fastexcel.biff.write; 写入相关的几个Writer。Worksheet Substream和Globals Substream都有各自的Writer。BIFFWriter包含一个WorkBookGlobalsStreamWriter,而WorkBookGlobalsStreamWriter又包含一个SheetStreamWriters的list。BIFFWriter就是利用各个Substream的Writer来实现写入的。当然这些BIFF的Writer最终都要写入到一个复合文档的一个Worksheet Stream里,复合文档的Worksheet Stream的Writer又通过Java IO写入物理文件。也就是说BIFF的Writer使用了Compound File的Writer。这里还没有Excel文件和复合文档的概念,有的还只是Writer和Reader的概念。Excel文件是一个复合文档,看起来设计时要采用继承。而这里只有Writer和Reader的概念,使用的却是组合。我想用继承也是可以的,不过这里用的是组合,组合有组合的好处。有哪些好处呢?
edu.npu.fastexcel.biff.read; 读取相关的类,设计思路和write部分相似。一般来说read部分的类会稍微比较多,处理稍微复杂,因为一般要读取的文件都是MS Excel产生的标准xls文件,内容比较全,包含各种额外的信息,比如格式信息,字体信息。写入生成一个xls文档就比较简单了,只把文本内容写入就行。MS Excel打开这样的文档会用默认的格式展现数据,重要的是文本内容显示出来了,而并不在意文本显示出来是怎样的。
三、用户API设计
这里说的API就是指提供给最终用户使用哪些类。最终用户可能不关心底层的实现细节,他想要了解的是,一个Excel有几个Sheet,每个Sheet有什么内容。因此最高层的API包装应该只是提供诸如Workbook,Sheet等概念。API在edu.npu.fastexcel这个package里,Workbook,Sheet都是接口,FastExcel类的设计应该使用了Facade模式,仅仅提供两个方法,创建一个可读取Workbook和创建一个可写入Workbook,把具体如何使用Writer和Reader实现所需功能的细节全部隐藏起来。这个package里还包含监听器接口SheetReadListener,因为这个接口也是用户所关心的。如果用户程序打算监听事件,就需要实现这个接口。在这个package还有ReadableWorkbook、WritableWorkbook、ReadableSheet、WritableSheet类,我感觉可以把它们放到其他package里更好。
四、建议
1)在每个package下面都包含一个描述文件,描述这个package里的类是干嘛的,就更清晰了。
2)我感觉可以把edu.npu.fastexcel这个package下的ReadableWorkbook、WritableWorkbook、ReadableSheet、WritableSheet类,放到其他package里更好。
3)是否应该使用一个示意为私有package的前缀,从而将一些包含具体实现细节,不易于最终用户重用的package标明为私有,比如在edu.npu.fastexcel下建立一个edu.npu.fastexcel.private子package,然后让Compound,BIFF8等实现全部放到edu.npu.fastexcel.private包下。因为提供诸如可重用的Compound File类并不是作者的意图。
1. static class InstanceHolder {
回复删除2. static RecordFactory instance = new RecordFactory();
3. }
线程安全
恩,很厉害,代码看得恨透~~~