posts - 27,  comments - 0,  trackbacks - 0
          由于工作關系,需要工作當中,需要讀取DBF文件,找了一些DBF讀取開源軟件,要么是太過龐大,動不動就上萬行,要么是功能有問題,編碼,長度,總之是沒有找到一個非常爽的。在萬般無奈之下,我老人家怒從心頭起,惡向膽邊生,決定自己寫一下。結果只用了不到300行代碼就搞定了,當然搞定不是唯一目標,還要優雅簡潔的搞定,親們跟隨我的腳步一起感受一下簡潔的設計與實現吧。
          在開始編碼之前,先介紹一下DBF,這個DBF可是個老東西,在DOS時代就已經出現,并且風騷了相當一段時間,后來隨著大型數據庫的應用,它逐步沒落,但是由于其簡潔易用的特點,還是應用在大量的數據交換當中。但是其發展過程中,也形成了許多種版本,不同版本的結構不一樣,也就決定 了其解析程序也是不一樣的。
          今天我只實現了Foxbase/DBaseIII的解析,但是也為擴展各種其它版本做好了準備。
          接口設計


          上面一共就兩個類,一個接口,Field和Header就是兩個簡單的POJO類,分別定義了文件頭及字段相關的信息。
          Reader接口是DBF文件讀取的接口,主要定義了獲取文件類型,編碼,字段以及記錄移動相關的方法。
          代碼實現 首先實現Reader的抽象類

           

            1public abstract class DbfReader implements Reader {
            2     protected String encode = "GBK";
            3     private FileChannel fileChannel;
            4     protected Header header;
            5     protected List<Field> fields;
            6     private boolean recordRemoved;
            7     int position = 0;
            8     static Map<Integer, Class> readerMap = new HashMap<Integer, Class>();
            9 
           10    static {
           11         addReader(3, FoxproDBase3Reader.class);
           12     }

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

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

           21 
           22    public byte getType() {
           23         return 3;
           24     }

           25 
           26    public String getEncode() {
           27         return encode;
           28     }

           29 
           30    public Header getHeader() {
           31         return header;
           32     }

           33 
           34    public List<Field> getFields() {
           35         return fields;
           36     }

           37 
           38
           39    public boolean isRecordRemoved() {
           40         return recordRemoved;
           41     }

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

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

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

           54 
           55    public static Reader parse(File dbfFile, String encode) throws IOException, IllegalAccessException, InstantiationException {
           56         RandomAccessFile aFile = new RandomAccessFile(dbfFile, "r");
           57         FileChannel fileChannel = aFile.getChannel();
           58         ByteBuffer byteBuffer = ByteBuffer.allocate(1);
           59         fileChannel.read(byteBuffer);
           60         byte type = byteBuffer.array()[0];
           61         Class<Reader> readerClass = readerMap.get((int) type);
           62         if (readerClass == null{
           63             fileChannel.close();
           64             throw new IOException("不支持的文件類型[" + type + "]。");
           65         }

           66         DbfReader reader = (DbfReader) readerClass.newInstance();
           67         reader.setFileChannel(fileChannel);
           68         reader.readHeader();
           69         reader.readFields();
           70         return reader;
           71     }

           72 
           73    public void setFileChannel(FileChannel fileChannel) {
           74         this.fileChannel = fileChannel;
           75     }

           76 
           77
           78    protected abstract void readFields() throws IOException;
           79 
           80    public void moveBeforeFirst() throws IOException {
           81         position = 0;
           82         fileChannel.position(header.getHeaderLength());
           83     }

           84 
           85    /**
           86      * @param position 從1開始
           87      * @throws java.io.IOException
           88      */

           89     public void absolute(int position) throws IOException {
           90         checkPosition(position);
           91         this.position = position;
           92         fileChannel.position(header.getHeaderLength() + (position - 1* header.getRecordLength());
           93     }

           94 
           95    private void checkPosition(int position) throws IOException {
           96         if (position >= header.getRecordCount()) {
           97             throw new IOException("期望記錄行數為" + (this.position + 1+ ",超過實際記錄行數:" + header.getRecordCount() + "");
           98         }

           99     }

          100 
          101    protected abstract Field readField() throws IOException;
          102 
          103    protected abstract void readHeader() throws IOException;
          104 
          105
          106    private void skipHeaderTerminator() throws IOException {
          107         ByteBuffer byteBuffer = ByteBuffer.allocate(1);
          108         readByteBuffer(byteBuffer);
          109     }

          110 
          111    public void close() throws IOException {
          112         fileChannel.close();
          113     }

          114 
          115    public void next() throws IOException {
          116         checkPosition(position);
          117         ByteBuffer byteBuffer = ByteBuffer.allocate(1);
          118         readByteBuffer(byteBuffer);
          119         this.recordRemoved = (byteBuffer.array()[0== '*');
          120         for (Field field : fields) {
          121             read(field);
          122         }

          123         position++;
          124     }

          125 
          126    public boolean hasNext() {
          127         return position < header.getRecordCount();
          128     }

          129 
          130    private void read(Field field) throws IOException {
          131         ByteBuffer buffer = ByteBuffer.allocate(field.getLength());
          132         readByteBuffer(buffer);
          133         field.setStringValue(new String(buffer.array(), encode).trim());
          134         field.setBuffer(buffer);
          135     }

          136 
          137    protected void readByteBuffer(ByteBuffer byteBuffer) throws IOException {
          138         fileChannel.read(byteBuffer);
          139     }

          140 }

          141


          這個類是最大的一個類,值得注意的是幾個靜態方法:  addReader和parse, addReader用于增加新的類型的Reader,parse用于解析文件。
          parse的執行過程是首先讀取第一個字節,判斷是否有對應的解析實現類,如果有,就有對應的解析實現類去解析,如果沒有,則拋出錯誤聲明不支持。
          下面寫實現類就簡單了,下面是FoxproDBase3的解析器:

           1public class FoxproDBase3Reader extends DbfReader {
           2    protected void readFields() throws IOException {
           3        fields = new ArrayList<Field>();
           4        for (int i = 0; i < (header.getHeaderLength() - 32 - 1/ 32; i++{
           5            fields.add(readField());
           6        }

           7    }

           8
           9    public byte getType() {
          10        return 3;
          11    }

          12
          13    protected Field readField() throws IOException {
          14        Field field = new Field();
          15        ByteBuffer byteBuffer = ByteBuffer.allocate(32);
          16        readByteBuffer(byteBuffer);
          17        byte[] bytes = byteBuffer.array();
          18        field.setName(new String(bytes, 011, encode).trim().split("\0")[0]);
          19        field.setType((char) bytes[11]);
          20        field.setDisplacement(Util.getUnsignedInt(bytes, 124));
          21        field.setLength(Util.getUnsignedInt(bytes, 161));
          22        field.setDecimal(Util.getUnsignedInt(byt
           1public class DbfReaderTest {
           2    static String[] files = {"BESTIMATE20140401""BHDQUOTE20140401"};
           3
           4    public static void main(String[] args) throws IOException, IllegalAccessException, InstantiationException {
           5        for (String file : files) {
           6            printFile(file);
           7        }

           8    }

           9
          10    public static void printFile(String fileName) throws IOException, InstantiationException, IllegalAccessException {
          11        Reader dbfReader = DbfReader.parse("E:\\20140401\\" + fileName + ".DBF");
          12        for (Field field : dbfReader.getFields()) {
          13            System.out.printf("name:%s %s(%d,%d)\n", field.getName(), field.getType(), field.getLength(), field.getDecimal());
          14        }

          15        System.out.println();
          16        for (int i = 0; i < dbfReader.getHeader().getRecordCount(); i++{
          17            dbfReader.next();
          18            for (Field field : dbfReader.getFields()) {
          19                System.out.printf("%" + field.getLength() + "s", field.getStringValue());
          20            }

          21            System.out.println();
          22        }

          23        dbfReader.close();
          24
          25    }

          26}
          es, 
          171));
          23        field.setFlag(bytes[18]);
          24        return field;
          25    }

          26
          27    protected void readHeader() throws IOException {
          28        header = new Header();
          29        ByteBuffer byteBuffer = ByteBuffer.allocate(31);
          30        readByteBuffer(byteBuffer);
          31        byte[] bytes = byteBuffer.array();
          32        header.setLastUpdate((Util.getUnsignedInt(bytes, 01+ 1900* 10000 + Util.getUnsignedInt(bytes, 11* 100 + Util.getUnsignedInt(bytes, 21));
          33        header.setRecordCount(Util.getUnsignedInt(bytes, 34));
          34        header.setHeaderLength(Util.getUnsignedInt(bytes, 72));
          35        header.setRecordLength(Util.getUnsignedInt(bytes, 92));
          36    }

          37}


           



          測試用例 


          可以看到最后的使用也是非常簡潔的。
          代碼統計

           

          總共的代碼行數是282行,去掉import和接口聲明之類的,真正干活的代碼大概就200行了:
          總結 上面不僅展示了如何實現DBF文件的解析,同時還展示了如何在現在面臨的需求與未來的擴展進行合理均衡的設計方式。
          比如:要實現另外一個標準的DBF文件支持,只要類似上面FoxproDBase3Reader類一樣,簡單實現之后,再調用DbfParser.addReader(xxxReader);
          好的設計需要即避免過度設計,搞得太復雜,同時也要對未來的變化與擴展做適當考慮,避免新的需求來的時候需要這里動動,那里改改導致結構上的調整與變化,同時要注意遵守DRY原則,可以這樣說如果程序中有必要的大量的重復,就說明一定存在結構設計上的問題。


           

           

          歡迎關注:http://web.j2ee.top。本例涉及的代碼和框架資料,將會在這里分享。也歡迎進加入QQ群:228977971,讓我們一起成長!

          posted on 2015-06-08 23:26 柏然 閱讀(76) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          <2015年6月>
          31123456
          78910111213
          14151617181920
          21222324252627
          2829301234
          567891011

          常用鏈接

          留言簿

          隨筆檔案

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 深州市| 赣州市| 定南县| 湟源县| 正定县| 广德县| 海阳市| 三原县| 岫岩| 牡丹江市| 平度市| 修武县| 万全县| 如皋市| 汝南县| 韶关市| 常宁市| 宁阳县| 宜阳县| 岳阳县| 松溪县| 滦平县| 定日县| 栾川县| 舟山市| 黄骅市| 昂仁县| 阳信县| 汽车| 漳平市| 安多县| 汤阴县| 和林格尔县| 淄博市| 千阳县| 彰化市| 大安市| 齐齐哈尔市| 麦盖提县| 阿瓦提县| 山阴县|