1. 程式人生 > 程式設計 >Java 用註解實現通用功能-csv檔案的讀取為例

Java 用註解實現通用功能-csv檔案的讀取為例

引言

使用java註解可以實現一些共通的功能,假設有幾種格式的csv檔案,編碼,分隔符,頭部行數之類的定義各不相同,但我們想統一的處理他們,那就需要一個共通的方法。

也許有人說,不用註解,只用個共通工具類不就行了嗎?但是註解讓程式碼更優雅,而且當你增加其他一些需求,比如其他csv格式的時候,只需要加幾個註解就能輕鬆的擴張你的功能。

那麼看程式碼吧。

1. 定義註解

定義一個csv格式的註解,包含檔案的分隔符,編碼等等資訊。如果業務需求增多,可以繼續新增功能,比如換行符之類。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileFormat {
	// 分隔符
	char delimiter() default ','
; // 引用符 char encloseChar() default Character.MIN_VALUE; // 編碼 String fileEncoding() default "UTF-8"; // 頭部行數 int headerLineCount() default 0; // 輸出檔案是否覆蓋 boolean overWriteFlg() default false; } 複製程式碼

2. 使用註解

FreeTextCSVFormat使用了FileFormat的註解,分隔符,編碼等都使用預設值,並沒有進行特別的設定。

@Data
@FileFormat()
public class FreeTextCSVFormat  {
	private String nexcoNumber;
	private String msgNumber;
	private String cmdMode;
	private String text;
}
複製程式碼

3. 處理註解,讀取檔案中的一行資料

根據註解的設定,讀取一行資料。不管是什麼編碼,或者換行符,都是用通用的readDataLine()方法。

@Data
public class CSVFileLineIterator<T> implements AutoCloseable {
    private Class<T> format;
    private File file;
    // 分隔符
    private char delimiter;
    // 編碼
    private String encoding;
    // 頭部行數
    private int headerLineCount;
    private int count;
    private BufferedReader reader;
    
    public CSVFileLineIterator(File file,Class<T> format) {
        FileFormat fileformat = checkParams(file,format);
        this.file = file;
        this.format = format;
        // 從註解中獲取分隔符
        this.delimiter = fileformat.delimiter();
        // 從註解中獲取編碼
        this.encoding = fileformat.fileEncoding();
        // 從註解中獲取頭部行數
        this.headerLineCount = fileformat.headerLineCount();
    }

    // 檢查引數
    private FileFormat checkParams(File file,Class<T> format) {
        Optional.ofNullable(file).orElseThrow(
                () -> new FileException("nullArgument"
)); Optional.ofNullable(format).orElseThrow( () -> new FileException("nullArgument")); OptionalBoolean.of(!file.exists()).orElseThrow( () -> new FileException("fileNoFound")); FileFormat fileformat = format.getAnnotation(FileFormat.class); Optional.ofNullable(fileformat).orElseThrow( () -> new FileException("noFormatAnnotation")); OptionalBoolean.of(fileformat.delimiter() == Character.MIN_VALUE).orElseThrow( () -> new FileException("illegalDelimiter")); return fileformat; } public void open() { try { reader = new BufferedReader(new InputStreamReader(new FileInputStream(file),this.encoding)); } catch (UnsupportedEncodingException | FileNotFoundException e) { throw new FileException("csvOpenFailure",e); } } public String[] readDataLine() { String line = null; try { // 讀取一行 while ((line = reader.readLine()) != null) { count++; if (count <= this.headerLineCount) { // 跳過頭部行數 continue; } break; } } catch (IOException e) { throw new FileException("csvReadFailure",e); } // 按照指定的分隔符把行內容分隔成字串陣列 return line == null ? null : line.split(String.valueOf(this.delimiter)); } public void close() { try { this.reader.close(); this.count = 0; } catch (IOException e) { throw new FileException("csvCloseFailure",e); } } 複製程式碼

測試一下

public static void main(String[] args) {
    try(CSVFileLineIterator c = new CSVFileLineIterator(new File("C:\\SiLED\\FRU20191209151700.csv"),FreeTextCSVFormat.class)) {
        c.open();
        String[] s;
        while ((s = c.readDataLine()) != null) {
            System.out.println("直接readline: " + Arrays.asList(s));
        }
    }
}
複製程式碼

測試結果

直接readline: [001,301,001,內容1]
直接readline: [001,302,002,內容2,303,003,內容3,304,004,內容4]
複製程式碼

CSV檔案-FRU20191209151700.csv

001,內容1
001,內容4
複製程式碼

4. 功能擴充套件-讀取資料,並封裝到類中

剛才只是讀取一行,返回字串陣列。但是我們有時候想把資料封裝到類裡,比如上述的FreeTextCSVFormat類。那麼可以再定義一個檔案內容的註解。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FileColumn {
	// 列index
	int columnIdex() default 0;
	// 是不是迴圈列
	boolean isLoop() default false;
}
複製程式碼

給FreeTextCSVFormat,新增FileColumn註解。

@Data
@FileFormat()
public class FreeTextCSVFormat {
	@FileColumn(columnIdex = 0)
	private String nexcoNumber;
	
	@FileColumn(columnIdex = 1,isLoop = true)
	private String msgNumber;
	
	@FileColumn(columnIdex = 2,isLoop = true)
	private String cmdMode;
	
	@FileColumn(columnIdex = 3,isLoop = true)
	private String text;
}
複製程式碼

最後,可以使用反射獲取columnIdex,並把讀取的內容封裝進去。具體實現就不貼出來了。

結語

使用註解能夠提升擴充套件性,比如新增一種新的csv樣式,並不需要修改讀取檔案的方法,只需要新增使用註解的類就可以了。這樣做能夠更優雅,還能幫你瞭解java反射,畢竟平時用框架的註解很多,自己寫的機會卻很少。