在開始編碼之前,先介紹一下DBF,這個DBF可是個老東西,在DOS時代就已經出現,并且風騷了相當一段時間,后來隨著大型數據庫的應用,它逐步沒落,但是由于其簡潔易用的特點,還是應用在大量的數據交換當中。但是其發展過程中,也形成了許多種版本,不同版本的結構不一樣,也就決定 了其解析程序也是不一樣的。
今天我只實現了Foxbase/DBaseIII的解析,但是也為擴展各種其它版本做好了準備。
接口設計

上面一共就兩個類,一個接口,Field和Header就是兩個簡單的POJO類,分別定義了文件頭及字段相關的信息。
Reader接口是DBF文件讀取的接口,主要定義了獲取文件類型,編碼,字段以及記錄移動相關的方法。
代碼實現 首先實現Reader的抽象類
1public abstract class DbfReader implements Reader {
2protected String encode = "GBK";
3private FileChannel fileChannel;
4protected Header header;
5protected List<Field> fields;
6private boolean recordRemoved;
7int position = 0;
8static Map<Integer, Class> readerMap = new HashMap<Integer, Class>();
9![]()
10static {
11addReader(3, FoxproDBase3Reader.class);
12}
13![]()
14public static void addReader(int type, Class clazz) {
15readerMap.put(type, clazz);
16}
17![]()
18public static void addReader(int type, String className) throws ClassNotFoundException {
19readerMap.put(type, Class.forName(className));
20}
21![]()
22public byte getType() {
23return 3;
24}
25![]()
26public String getEncode() {
27return encode;
28}
29![]()
30public Header getHeader() {
31return header;
32}
33![]()
34public List<Field> getFields() {
35return fields;
36}
37![]()
38
39public boolean isRecordRemoved() {
40return recordRemoved;
41}
42![]()
43public static Reader parse(String dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException {
44return parse(new File(dbfFile), encode);
45}
46![]()
47public static Reader parse(String dbfFile) throws IOException, IllegalAccessException, InstantiationException {
48return parse(new File(dbfFile), "GBK");
49}
50![]()
51public static Reader parse(File dbfFile) throws IOException, IllegalAccessException, InstantiationException {
52return parse(dbfFile, "GBK");
53}
54![]()
55public static Reader parse(File dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException {
56RandomAccessFile aFile = new RandomAccessFile(dbfFile, "r");
57FileChannel fileChannel = aFile.getChannel();
58ByteBuffer byteBuffer = ByteBuffer.allocate(1);
59fileChannel.read(byteBuffer);
60byte type = byteBuffer.array()[0];
61Class<Reader> readerClass = readerMap.get((int) type);
62if (readerClass == null) {
63fileChannel.close();
64throw new IOException("不支持的文件類型[" + type + "]。");
65}
66DbfReader reader = (DbfReader) readerClass.newInstance();
67reader.setFileChannel(fileChannel);
68reader.readHeader();
69reader.readFields();
70return reader;
71}
72![]()
73public void setFileChannel(FileChannel fileChannel) {
74this.fileChannel = fileChannel;
75}
76![]()
77
78protected abstract void readFields() throws IOException;
79![]()
80public void moveBeforeFirst() throws IOException {
81position = 0;
82fileChannel.position(header.getHeaderLength());
83}
84![]()
85/**
86* @param position 從1開始
87* @throws java.io.IOException
88*/
89public void absolute(int position) throws IOException {
90checkPosition(position);
91this.position = position;
92fileChannel.position(header.getHeaderLength() + (position - 1) * header.getRecordLength());
93}
94![]()
95private void checkPosition(int position) throws IOException {
96if (position >= header.getRecordCount()) {
97throw new IOException("期望記錄行數為" + (this.position + 1) + ",超過實際記錄行數:" + header.getRecordCount() + "。");
98}
99}
100![]()
101protected abstract Field readField() throws IOException;
102![]()
103protected abstract void readHeader() throws IOException;
104![]()
105
106private void skipHeaderTerminator() throws IOException {
107ByteBuffer byteBuffer = ByteBuffer.allocate(1);
108readByteBuffer(byteBuffer);
109}
110![]()
111public void close() throws IOException {
112fileChannel.close();
113}
114![]()
115public void next() throws IOException {
116checkPosition(position);
117ByteBuffer byteBuffer = ByteBuffer.allocate(1);
118readByteBuffer(byteBuffer);
119this.recordRemoved = (byteBuffer.array()[0] == '*');
120for (Field field : fields) {
121read(field);
122}
123position++;
124}
125![]()
126public boolean hasNext() {
127return position < header.getRecordCount();
128}
129![]()
130private void read(Field field) throws IOException {
131ByteBuffer buffer = ByteBuffer.allocate(field.getLength());
132readByteBuffer(buffer);
133field.setStringValue(new String(buffer.array(), encode).trim());
134field.setBuffer(buffer);
135}
136![]()
137protected void readByteBuffer(ByteBuffer byteBuffer) throws IOException {
138fileChannel.read(byteBuffer);
139}
140}
141
這個類是最大的一個類,值得注意的是幾個靜態方法: addReader和parse, addReader用于增加新的類型的Reader,parse用于解析文件。
parse的執行過程是首先讀取第一個字節,判斷是否有對應的解析實現類,如果有,就有對應的解析實現類去解析,如果沒有,則拋出錯誤聲明不支持。
下面寫實現類就簡單了,下面是FoxproDBase3的解析器:
1public class FoxproDBase3Reader extends DbfReader {
2protected void readFields() throws IOException {
3fields = new ArrayList<Field>();
4for (int i = 0; i < (header.getHeaderLength() - 32 - 1) / 32; i++) {
5fields.add(readField());
6}
7}
8
9public byte getType() {
10return 3;
11}
12
13protected Field readField() throws IOException {
14Field field = new Field();
15ByteBuffer byteBuffer = ByteBuffer.allocate(32);
16readByteBuffer(byteBuffer);
17byte[] bytes = byteBuffer.array();
18field.setName(new String(bytes, 0, 11, encode).trim().split("\0")[0]);
19field.setType((char) bytes[11]);
20field.setDisplacement(Util.getUnsignedInt(bytes, 12, 4));
21field.setLength(Util.getUnsignedInt(bytes, 16, 1));
22field.setDecimal(Util.getUnsignedInt(byt
1es, 17, 1));public class DbfReaderTest {
2static String[] files = {"BESTIMATE20140401", "BHDQUOTE20140401"};
3
4public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
5for (String file : files) {
6printFile(file);
7}
8}
9
10public static void printFile(String fileName) throws IOException, InstantiationException, IllegalAccessException {
11Reader dbfReader = DbfReader.parse("E:\\20140401\\" + fileName + ".DBF");
12for (Field field : dbfReader.getFields()) {
13System.out.printf("name:%s %s(%d,%d)\n", field.getName(), field.getType(), field.getLength(), field.getDecimal());
14}
15System.out.println();
16for (int i = 0; i < dbfReader.getHeader().getRecordCount(); i++) {
17dbfReader.next();
18for (Field field : dbfReader.getFields()) {
19System.out.printf("%" + field.getLength() + "s", field.getStringValue());
20}
21System.out.println();
22}
23dbfReader.close();
24
25}
26}
23field.setFlag(bytes[18]);
24return field;
25}
26
27protected void readHeader() throws IOException {
28header = new Header();
29ByteBuffer byteBuffer = ByteBuffer.allocate(31);
30readByteBuffer(byteBuffer);
31byte[] bytes = byteBuffer.array();
32header.setLastUpdate((Util.getUnsignedInt(bytes, 0, 1) + 1900) * 10000 + Util.getUnsignedInt(bytes, 1, 1) * 100 + Util.getUnsignedInt(bytes, 2, 1));
33header.setRecordCount(Util.getUnsignedInt(bytes, 3, 4));
34header.setHeaderLength(Util.getUnsignedInt(bytes, 7, 2));
35header.setRecordLength(Util.getUnsignedInt(bytes, 9, 2));
36}
37}
測試用例
可以看到最后的使用也是非常簡潔的。
代碼統計
總共的代碼行數是282行,去掉import和接口聲明之類的,真正干活的代碼大概就200行了:
總結 上面不僅展示了如何實現DBF文件的解析,同時還展示了如何在現在面臨的需求與未來的擴展進行合理均衡的設計方式。
比如:要實現另外一個標準的DBF文件支持,只要類似上面FoxproDBase3Reader類一樣,簡單實現之后,再調用DbfParser.addReader(xxxReader);
好的設計需要即避免過度設計,搞得太復雜,同時也要對未來的變化與擴展做適當考慮,避免新的需求來的時候需要這里動動,那里改改導致結構上的調整與變化,同時要注意遵守DRY原則,可以這樣說如果程序中有必要的大量的重復,就說明一定存在結構設計上的問題。
歡迎關注:http://web.j2ee.top。本例涉及的代碼和框架資料,將會在這里分享。也歡迎進加入QQ群:228977971,讓我們一起成長!