`
j2eetop
  • 浏览: 64401 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

TinyDBF-用200行的DBF解析器来展示良好架构设计

 
阅读更多
由于工作关系,需要工作当中,需要读取DBF文件,找了一些DBF读取开源软件,要么是太过庞大,动不动就上万行,要么是功能有问题,编码,长度,总之是没有找到一个非常爽的。在万般无奈之下,我老人家怒从心头起,恶向胆边生,决定自己写一下。结果只用了不到300行代码就搞定了,当然搞定不是唯一目标,还要优雅简洁的搞定,亲们跟随我的脚步一起感受一下简洁的设计与实现吧。
在开始编码之前,先介绍一下DBF,这个DBF可是个老东西,在DOS时代就已经出现,并且风骚了相当一段时间,后来随着大型数据库的应用,它逐步没落,但是由于其简洁易用的特点,还是应用在大量的数据交换当中。但是其发展过程中,也形成了许多种版本,不同版本的结构不一样,也就决定 了其解析程序也是不一样的。
今天我只实现了Foxbase/DBaseIII的解析,但是也为扩展各种其它版本做好了准备。
接口设计

083756_dzpL_1245989.jpg (37.32 KB, 下载次数: 0)

下载附件

6天前 上传


上面一共就两个类,一个接口,Field和Header就是两个简单的POJO类,分别定义了文件头及字段相关的信息。
Reader接口是DBF文件读取的接口,主要定义了获取文件类型,编码,字段以及记录移动相关的方法。
代码实现 首先实现Reader的抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
public abstract class DbfReader implements Reader {
protected String encode = "GBK";
private FileChannel fileChannel;
protected Header header;
protected List<Field> fields;
private boolean recordRemoved;
int position = 0;
static Map<Integer, Class> readerMap = new HashMap<Integer, Class>();

static {
addReader(3, FoxproDBase3Reader.class);
}

public static void addReader(int type, Class clazz) {
readerMap.put(type, clazz);
}

public static void addReader(int type, String className) throws ClassNotFoundException {
readerMap.put(type, Class.forName(className));
}

public byte getType() {
return 3;
}

public String getEncode() {
return encode;
}

public Header getHeader() {
return header;
}

public List<Field> getFields() {
return fields;
}


public boolean isRecordRemoved() {
return recordRemoved;
}

public static Reader parse(String dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException {
return parse(new File(dbfFile), encode);
}

public static Reader parse(String dbfFile) throws IOException, IllegalAccessException, InstantiationException {
return parse(new File(dbfFile), "GBK");
}

public static Reader parse(File dbfFile) throws IOException, IllegalAccessException, InstantiationException {
return parse(dbfFile, "GBK");
}

public static Reader parse(File dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException {
RandomAccessFile aFile = new RandomAccessFile(dbfFile, "r");
FileChannel fileChannel = aFile.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1);
fileChannel.read(byteBuffer);
byte type = byteBuffer.array()[0];
Class<Reader> readerClass = readerMap.get((int) type);
if (readerClass == null) {
fileChannel.close();
throw new IOException("不支持的文件类型[" + type + "]。");
}
DbfReader reader = (DbfReader) readerClass.newInstance();
reader.setFileChannel(fileChannel);
reader.readHeader();
reader.readFields();
return reader;
}

public void setFileChannel(FileChannel fileChannel) {
this.fileChannel = fileChannel;
}


protected abstract void readFields() throws IOException;

public void moveBeforeFirst() throws IOException {
position = 0;
fileChannel.position(header.getHeaderLength());
}

/**
* @param position 从1开始
* @throws java.io.IOException
*/
public void absolute(int position) throws IOException {
checkPosition(position);
this.position = position;
fileChannel.position(header.getHeaderLength() + (position - 1) * header.getRecordLength());
}

private void checkPosition(int position) throws IOException {
if (position >= header.getRecordCount()) {
throw new IOException("期望记录行数为" + (this.position + 1) + ",超过实际记录行数:" + header.getRecordCount() + "。");
}
}

protected abstract Field readField() throws IOException;

protected abstract void readHeader() throws IOException;


private void skipHeaderTerminator() throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1);
readByteBuffer(byteBuffer);
}

public void close() throws IOException {
fileChannel.close();
}

public void next() throws IOException {
checkPosition(position);
ByteBuffer byteBuffer = ByteBuffer.allocate(1);
readByteBuffer(byteBuffer);
this.recordRemoved = (byteBuffer.array()[0] == '*');
for (Field field : fields) {
read(field);
}
position++;
}

public boolean hasNext() {
return position < header.getRecordCount();
}

private void read(Field field) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(field.getLength());
readByteBuffer(buffer);
field.setStringValue(new String(buffer.array(), encode).trim());
field.setBuffer(buffer);
}

protected void readByteBuffer(ByteBuffer byteBuffer) throws IOException {
fileChannel.read(byteBuffer);
}
}




这个类是最大的一个类,值得注意的是几个静态方法:addReader和parse, addReader用于增加新的类型的Reader,parse用于解析文件。
parse的执行过程是首先读取第一个字节,判断是否有对应的解析实现类,如果有,就有对应的解析实现类去解析,如果没有,则抛出错误声明不支持。
下面写实现类就简单了,下面是FoxproDBase3的解析器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class FoxproDBase3Reader extends DbfReader {
protected void readFields() throws IOException {
fields = new ArrayList<Field>();
for (int i = 0; i < (header.getHeaderLength() - 32 - 1) / 32; i++) {
fields.add(readField());
}
}

public byte getType() {
return 3;
}

protected Field readField() throws IOException {
Field field = new Field();
ByteBuffer byteBuffer = ByteBuffer.allocate(32);
readByteBuffer(byteBuffer);
byte[] bytes = byteBuffer.array();
field.setName(new String(bytes, 0, 11, encode).trim().split("\0")[0]);
field.setType((char) bytes[11]);
field.setDisplacement(Util.getUnsignedInt(bytes, 12, 4));
field.setLength(Util.getUnsignedInt(bytes, 16, 1));
field.setDecimal(Util.getUnsignedInt(bytes, 17, 1));
field.setFlag(bytes[18]);
return field;
}

protected void readHeader() throws IOException {
header = new Header();
ByteBuffer byteBuffer = ByteBuffer.allocate(31);
readByteBuffer(byteBuffer);
byte[] bytes = byteBuffer.array();
header.setLastUpdate((Util.getUnsignedInt(bytes, 0, 1) + 1900) * 10000 + Util.getUnsignedInt(bytes, 1, 1) * 100 + Util.getUnsignedInt(bytes, 2, 1));
header.setRecordCount(Util.getUnsignedInt(bytes, 3, 4));
header.setHeaderLength(Util.getUnsignedInt(bytes, 7, 2));
header.setRecordLength(Util.getUnsignedInt(bytes, 9, 2));
}
}


测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DbfReaderTest {
static String[] files = {"BESTIMATE20140401", "BHDQUOTE20140401"};

public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
for (String file : files) {
printFile(file);
}
}

public static void printFile(String fileName) throws IOException, InstantiationException, IllegalAccessException {
Reader dbfReader = DbfReader.parse("E:\\20140401\\" + fileName + ".DBF");
for (Field field : dbfReader.getFields()) {
System.out.printf("name:%s %s(%d,%d)\n", field.getName(), field.getType(), field.getLength(), field.getDecimal());
}
System.out.println();
for (int i = 0; i < dbfReader.getHeader().getRecordCount(); i++) {
dbfReader.next();
for (Field field : dbfReader.getFields()) {
System.out.printf("%" + field.getLength() + "s", field.getStringValue());
}
System.out.println();
}
dbfReader.close();

}
}


可以看到最后的使用也是非常简洁的。
代码统计

085959_EzQV_1245989.jpg (15.06 KB, 下载次数: 0)

下载附件

6天前 上传


总共的代码行数是282行,去掉import和接口声明之类的,真正干活的代码大概就200行了:
总结 上面不仅展示了如何实现DBF文件的解析,同时还展示了如何在现在面临的需求与未来的扩展进行合理均衡的设计方式。
比如:要实现另外一个标准的DBF文件支持,只要类似上面FoxproDBase3Reader类一样,简单实现之后,再调用DbfParser.addReader(xxxReader);
好的设计需要即避免过度设计,搞得太复杂,同时也要对未来的变化与扩展做适当考虑,避免新的需求来的时候需要这里动动,那里改改导致结构上的调整与变化,同时要注意遵守DRY原则,可以这样说如果程序中有必要的大量的重复,就说明一定存在结构设计上的问题。
所有的代码都可以在下面的连接看到:
https://git.oschina.net/tinyframework/tiny/tree/master/framework/org.tinygroup.dbf/src/main/java/org/tinygroup/dbf
分享到:
评论

相关推荐

    dbf-jdbc-wisecoders,JAVA 读写DBF文件工具包

    1. **JDBC接口**:`dbf-jdbc-wisecoders`通过提供一个类似于JDBC(Java Database Connectivity)的接口,让Java开发者可以使用他们熟悉的SQL查询来访问和操作DBF文件。这大大简化了代码编写,使得DBF文件的处理如同...

    sjshq.dbf-------用于测试服务器端原代码----股票实时行情---------深圳dbf

    2. **服务器端编程**:这里的服务器端原代码可能是用Java、Python、C#或其他服务器端语言编写的,用于处理股票数据,可能包括数据解析、计算、存储和返回给客户端等功能。 3. **实时数据处理**:服务器端需要实时...

    node-dbf:用纯JavaScript编写的高效dBase DBF文件解析器

    如果您有兴趣获得所有权,请在以下位置发表评论: : DBF解析器这是基于事件的dBase文件解析器,用于非常有效地从*.dbf文件读取数据。 该代码库使用ES6 JavaScript编写,但在npm模块中编译为纯JavaScript。 要开始...

    java解析dbf文件方案.pdf

    在这个方案中,我们可以使用两种方法来解析 DBF 文件:一种是使用 ODBC 驱动来连接 DBF 文件,另一种是使用 javadbdf 库来直接读取 DBF 文件。 使用 ODBC 驱动来连接 DBF 文件需要安装 Microsoft Visual FoxPro ...

    常用工具包_DBF解析_20080304.rar

    这个“常用工具包_DBF解析_20080304.rar”似乎是一个用于处理DBF文件的工具集合,特别针对DBF文件的解析。 在IT领域,DBF解析通常涉及以下知识点: 1. **DBF文件结构**:DBF文件由一系列记录组成,每条记录包含多...

    行业分类-设备装置-DBF数据导出平台及其导出方法.zip

    这个压缩包中的"DBF数据导出平台及其导出方法.pdf"文档,很可能是详细介绍如何设计和实现一个平台,用于处理和导出DBF文件中的数据。这个平台可能包含以下几个关键知识点: 1. **数据读取**:平台首先需要有能力...

    DBF解析工具类

    DBF数据表文件的结构分析: DBF文件由两部分组成, 第一部分是结构描述,即文件头信息;共32个字节(0~31) 第二部分是表本身的内容,该部分又分为两个部分: 前一部分是表的结构说明,共32个字节具体的内容见下表...

    股票历史数据导出工具--Day2Dbf

    Day2Dbf工具正是这样一款专为投资者和分析师设计的实用软件,它能够帮助用户高效地从行情软件中导出历史数据,并将其转化为易于处理的DBF文件格式。本文将深入探讨Day2Dbf工具的功能、工作原理以及如何使用,以期为...

    DBF解析工具 javadbf-0.4.0.jar

    DBFReader dBFReader = new DBFReader(new FileInputStream(new File&#40;path&#41;)); Object[] fieldValues = null; while((fieldValues=dBFReader.nextRecord())!= null){ for(int i=0;...解析超简单

    使用caigen解析DBF文件

    本文将详细介绍如何使用`caigen`来解析DBF文件,并探讨相关知识点。 首先,让我们理解DBF文件。DBF(dBase File)是基于表格的数据存储格式,包含列名、数据类型、记录等信息。每个DBF文件都有一个对应的.CDX索引...

    php-xbase, 使用PHP的*.dbf 文件的简单解析器.zip

    php-xbase, 使用PHP的*.dbf 文件的简单解析器 PHP XBase一个处理数据库( 如dBase和 FoxPro )的简单库。 代码是由 Erwin Kooi 编写的,更新到一个兼容的代码并针对性能进行了调整,并解决了一些原始代码所具有的问题...

    java 写dbf文件

    打包文件路径 : dbf4j\artifacts\dbf4j_jar java -jar dbf4j.jar test.xml 20190416 test.xml 是配置文件,20190416是日期参数

    java解析dbf文件三种方法、以及解析驱动

    1. **按行解析DBF文件** 2. **将DBF文件当作表进行操作(需要安装驱动)** 3. **将DBF文件当作表进行操作(无需安装驱动)** 下面将详细介绍这三种方法的具体实现过程。 ### 按行解析DBF文件 这种方式通常不需要...

    DBF文件格式 DBF文件格式

    总结来说,DBF文件格式是一种早期的数据库存储方式,尽管在当今已不常见,但在某些场景下仍然有其价值,特别是在处理历史数据或与旧系统集成时。随着技术的进步,更先进、功能更丰富的数据库格式正在取代它,但对于...

    DBFPlus DBF编辑工具

    个人觉得很好用的一个DBF编辑工具,是一个绿色软件,比以前找的要好用些,有需要的可以下哟..

    解决ArcMap Desktop 10.1-10.6 导出dbf出现乱码.rar

    总之,解决ArcMap Desktop导出dbf文件时出现中文乱码的问题需要理解dbf文件的性质、编码的重要性以及如何利用插件或更新软件来解决此类兼容性问题。通过正确的方法和工具,可以确保在处理中文数据时不会出现乱码,...

    DBF数据库查看器

    DBF数据库查看器是一款专为处理DBF格式文件设计的工具,它允许用户快速查看和操作DBF数据库的结构和内容。DBF是Dbase、FoxPro等早期数据库管理系统所使用的文件格式,常见于一些历史数据存储或老式应用程序中。这款...

    Java解析DBF文件

    Java解析某些DBF文件时喜欢出错误,错误一:Failed to parse Number: For input string: "-.---" , 错误二:有时候有些被标记为删除的数据读取不出来,已打成jar包

    dbf阅读器。rar

    "dbf阅读器"是一个专为打开、查看和管理DBF文件而设计的工具,其优势在于它的强大兼容性,能够处理不同版本的DBF文件。 这个压缩包“dbf阅读器。rar”包含的"DBF Viewer"很可能是一个实用的轻量级应用,旨在提供...

Global site tag (gtag.js) - Google Analytics