對(duì)于處理文件,我們介紹了流的方式,57節(jié)介紹了字節(jié)流,58節(jié)介紹了字符流,同時(shí),也介紹了比較底層的操作文件的方式,60節(jié)介紹了隨機(jī)讀寫文件,61節(jié)介紹了內(nèi)存映射文件,我們也介紹了對(duì)象的序列化/反序列化機(jī)制,62節(jié)介紹了Java標(biāo)準(zhǔn)的序列化,63節(jié)介紹了如何用Jackson處理其他序列化格式如XML/JSON和MessagePack。

在日常編程中,我們還經(jīng)常會(huì)需要處理一些具體類型的文件,如CSV, Excel, HTML,直接使用前面幾節(jié)介紹的方式來處理一般是很不方便的,往往有一些第三方的類庫,基于之前介紹的技術(shù),提供了更為方便易用的接口。

本節(jié),我們就來簡要介紹如何利用Java SDK和一些第三方類庫,來處理如下五種類型的文件:

  • 屬性文件:屬性文件是常見的配置文件,用于在不改變代碼的情況下改變程序的行為。

  • CSV:CSV是Comma-Separated Values的縮寫,表示逗號(hào)分割值,是一種非常常見的文件類型,大部分日志文件都是CSV,CSV也經(jīng)常用于交換表格類型的數(shù)據(jù),待會(huì)我們會(huì)看到,CSV看上去很簡單但處理的復(fù)雜性經(jīng)常被低估。

  • Excel:Excel大家都知道,在編程中,經(jīng)常需要將表格類型的數(shù)據(jù)導(dǎo)出為Excel格式,以方便用戶查看,也經(jīng)常需要接受Excel類型的文件作為輸入以批量導(dǎo)入數(shù)據(jù)。

  • HTML:所有網(wǎng)頁都是HTML格式,我們經(jīng)常需要分析HTML網(wǎng)頁,以從中提取感興趣的信息。

  • 壓縮文件:壓縮文件有多種格式,也有很多壓縮工具,大部分情況下,我們可以借助工具而不需要自己寫程序處理壓縮文件,但某些情況,需要自己編程壓縮文件或解壓縮文件。 

屬性文件

屬性文件一般很簡單,一行表示一個(gè)屬性,屬性就是鍵值對(duì),鍵和值用等號(hào)(=)或冒號(hào)(:)分隔,一般用于配置程序的一些參數(shù)。比如,在需要連接數(shù)據(jù)庫的程序中,經(jīng)常使用配置文件配置數(shù)據(jù)庫信息,比如,有這么個(gè)文件config.properties,內(nèi)容大概如下所示:

db.host = 192.168.10.100db.port : 3306db.username = zhangsan
db.password = mima1234

處理這種文件使用字符流也是比較容易的,但Java中有一個(gè)專門的類java.util.Properties,它的使用也很簡單,有如下主要方法:

public synchronized void load(InputStream inStream)public String getProperty(String key)public String getProperty(String key, String defaultValue)

load用于從流中加載屬性,getProperty用于獲取屬性值,可以提供一個(gè)默認(rèn)值,如果沒有找到配置的值,則返回默認(rèn)值。對(duì)于上面的配置文件,可以使用類似下面的代碼進(jìn)行讀?。?/p>

Properties prop = new Properties();
prop.load(new FileInputStream("config.properties"));
String host = prop.getProperty("db.host");int port = Integer.valueOf(prop.getProperty("db.port", "3306"));

使用類Properties處理屬性文件的好處是:

  • 可以自動(dòng)處理空格,我們看到分隔符=前后的空格會(huì)被自動(dòng)忽略

  • 可以自動(dòng)忽略空行

  • 可以添加注釋,以字符#或!開頭的行會(huì)被視為注釋,進(jìn)行忽略

不過,使用Properties也有限制,它不能直接處理中文,在配置文件中,所有非ASCII字符需要使用Unicode編碼,比如,不能在配置文件中直接這么寫:

name=老馬

"老馬"需要替換為Unicode編碼,如下所示:

name=\u8001\u9A6C

在Java IDE如Eclipse中,如果使用屬性文件編輯器,它會(huì)自動(dòng)替換中文為Unicode編碼,如果使用其他編輯器,可以先寫成中文,然后使用JDK提供的命令native2ascii轉(zhuǎn)換為Unicode編碼,用法如下例所示:

native2ascii -encoding UTF-8 native.properties ascii.properties

native.properties是輸入,其中包含中文,ascii.properties是輸出,中文替換為了Unicode編碼,-encoding指定輸入文件的編碼,這里指定為了UTF-8。

CSV文件

CSV是Comma-Separated Values的縮寫,表示逗號(hào)分割值,一般而言,一行表示一條記錄,一條記錄包含多個(gè)字段,字段之間用逗號(hào)分隔。不過,一般而言,分隔符不一定是逗號(hào),可能是其他字符如tab符'\t'、冒號(hào)':',分號(hào)';'等。程序中的各種日志文件通常是CSV文件,在導(dǎo)入導(dǎo)出表格類型的數(shù)據(jù)時(shí),CSV也是經(jīng)常用的一種格式。

CSV格式看上去很簡單,比如,我們?cè)?a target="_blank" style="text-decoration-line: none; color: rgb(51, 153, 255);">58節(jié)保存學(xué)生列表時(shí),使用的就是CSV格式,如下所示:

張三,18,80.9李四,17,67.5

使用之前介紹的字符流,看上去就可以很容易處理CSV文件,按行讀取,對(duì)每一行,使用String.split進(jìn)行分割即可。但其實(shí)CSV有一些復(fù)雜的地方,最重要的是:

  • 字段內(nèi)容中包含分割符怎么辦?

  • 字段內(nèi)容中包含換行符怎么辦? 

對(duì)于這些問題,CSV有一個(gè)參考標(biāo)準(zhǔn),RFC-4180,https://tools.ietf.org/html/rfc4180,但實(shí)踐中不同程序往往有其他處理方式,所幸的是,處理方式大體類似,大概有兩種處理方式:

  1. 使用引用符號(hào)比如",在字段內(nèi)容兩邊加上",如果內(nèi)容中包含"本身,則使用兩個(gè)"

  2. 使用轉(zhuǎn)義字符,常用的是\,如果內(nèi)容中包含\,則使用兩個(gè)\ 

比如,如果字段內(nèi)容有兩行,內(nèi)容為:

hello, world \ abc"老馬"

使用第一種方式,內(nèi)容會(huì)變?yōu)椋?/p>

"hello, world \ abc""老馬"""

使用第二種方式,內(nèi)容會(huì)變?yōu)椋?/p>

hello\, world \\ abc\n"老馬"

CSV還有其他一些細(xì)節(jié),不同程序的處理方式也不一樣,比如:

  • 怎么表示null值?

  • 空行和字段之間的空格怎么處理?

  • 怎么表示注釋? 

由于以上這些復(fù)雜問題,使用簡單的字符流就難以處理了。有一個(gè)第三方類庫,Apache Commons CSV,對(duì)處理CSV提供了良好的支持,它的官網(wǎng)地址是:http://commons.apache.org/proper/commons-csv/index.html

本節(jié)使用其1.4版本,簡要介紹其用法。如果使用Maven管理項(xiàng)目,可引入以下文件中的依賴:https://github.com/swiftma/program-logic/blob/master/csv_lib/dependencies.xml。如果非Maven,可從下面地址下載依賴庫:https://github.com/swiftma/program-logic/tree/master/csv_lib

Apache Commons CSV中有一個(gè)重要的類CSVFormat,它表示CSV格式,它有很多方法以定義具體的CSV格式,如:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

//定義分隔符public CSVFormat withDelimiter(final char delimiter)//定義引號(hào)符public CSVFormat withQuote(final char quoteChar)//定義轉(zhuǎn)義符public CSVFormat withEscape(final char escape)//定義值為null的對(duì)象對(duì)應(yīng)的字符串值public CSVFormat withNullString(final String nullString)//定義記錄之間的分隔符public CSVFormat withRecordSeparator(final char recordSeparator)//定義是否忽略字段之間的空白public CSVFormat withIgnoreSurroundingSpaces(final boolean ignoreSurroundingSpaces)

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

比如,如果CSV格式定義為:使用分號(hào);作為分隔符,"作為引號(hào)符,使用N/A表示null對(duì)象,忽略字段之間的空白,CSVFormat可以這樣創(chuàng)建:

CSVFormat format = CSVFormat.newFormat(';')
        .withQuote('"').withNullString("N/A")
        .withIgnoreSurroundingSpaces(true);

除了自定義CSVFormat,CSVFormat類中也定義了一些預(yù)定義的格式,如:CSVFormat.DEFAULT, CSVFormat.RFC4180。

CSVFormat有一個(gè)方法,可以分析字符流:

public CSVParser parse(final Reader in) throws IOException

返回值類型為CSVParser,它有如下方法獲取記錄信息:

public Iterator<CSVRecord> iterator()public List<CSVRecord> getRecords() throws IOExceptionpublic long getRecordNumber()

CSVRecord表示一條記錄,它有如下方法獲取每個(gè)字段的信息:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

//根據(jù)字段列索引獲取值,索引從0開始public String get(final int i)//根據(jù)列名獲取值public String get(final String name)//字段個(gè)數(shù)public int size()//字段的迭代器public Iterator<String> iterator()

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

分析CSV文件的基本代碼如下所示:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

CSVFormat format = CSVFormat.newFormat(';')
        .withQuote('"').withNullString("N/A")
        .withIgnoreSurroundingSpaces(true);
Reader reader = new FileReader("student.csv");try{    for(CSVRecord record : format.parse(reader)){        int fieldNum = record.size();        for(int i=0; i<fieldNum; i++){
            System.out.print(record.get(i)+" ");
        }
        System.out.println();
    }
}finally{
    reader.close();
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

除了分析CSV文件,Apache Commons CSV也可以寫CSV文件,有一個(gè)CSVPrinter,它有很多打印方法,比如:

//輸出一條記錄,參數(shù)可變,每個(gè)參數(shù)是一個(gè)字段值public void printRecord(final Object... values) throws IOException//輸出一條記錄public void printRecord(final Iterable<?> values) throws IOException

看個(gè)代碼示例:

CSVPrinter out = new CSVPrinter(new FileWriter("student.csv"),
        CSVFormat.DEFAULT);
out.printRecord("老馬", 18, "看電影,看書,聽音樂");
out.printRecord("小馬", 16, "樂高;賽車;");
out.close();

輸出文件student.csv中的內(nèi)容為:

"老馬",18,"看電影,看書,聽音樂"
"小馬",16,樂高;賽車;

Excel

Excel主要有兩種格式,后綴名分別為.xls和.xlsx,.xlsx是Office 2007以后的默認(rèn)擴(kuò)展名。Java中處理Excel文件及其他微軟文檔廣泛使用POI類庫,其官網(wǎng)是http://poi.apache.org/。

本節(jié)使用其3.15版本,簡要介紹其用法。如果使用Maven管理項(xiàng)目,可引入以下文件中的依賴:https://github.com/swiftma/program-logic/blob/master/excel_lib/dependencies.xml。如果非Maven,可從下面地址下載依賴庫:https://github.com/swiftma/program-logic/tree/master/excel_lib

使用POI處理Excel文件,有如下主要類:

  • Workbook: 表示一個(gè)Excel文件對(duì)象,它是一個(gè)接口,有兩個(gè)主要類HSSFWorkbook和XSSFWorkbook,前者對(duì)應(yīng).xls格式,后者對(duì)應(yīng).xlsx格式。

  • Sheet: 表示一個(gè)工作表

  • Row: 表示一行

  • Cell: 表示一個(gè)單元格

比如,保存學(xué)生列表到student.xls,代碼可以為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public static void saveAsExcel(List<Student> list) throws IOException {
    Workbook wb = new HSSFWorkbook();
    Sheet sheet = wb.createSheet();    for (int i = 0; i < list.size(); i++) {
        Student student = list.get(i);
        Row row = sheet.createRow(i);
        row.createCell(0).setCellValue(student.getName());
        row.createCell(1).setCellValue(student.getAge());
        row.createCell(2).setCellValue(student.getScore());
    }
    OutputStream out = new FileOutputStream("student.xls");
    wb.write(out);
    out.close();
    wb.close();
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

如果要保存為.xlsx格式,只需要替換第一行為:

Workbook wb = new XSSFWorkbook();

使用POI也可以方便的解析Excel文件,使用WorkbookFactory的create方法即可,如下所示:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public static List<Student> readAsExcel() throws Exception  {
    Workbook wb = WorkbookFactory.create(new File("student.xls"));
    List<Student> list = new ArrayList<Student>();    for(Sheet sheet : wb){        for(Row row : sheet){
            String name = row.getCell(0).getStringCellValue();            int age = (int)row.getCell(1).getNumericCellValue();            double score = row.getCell(2).getNumericCellValue();
            list.add(new Student(name, age, score));
        }
    }    
    wb.close();    return list;
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

以上我們只是介紹了基本用法,如果需要更多信息,如配置單元格的格式、顏色、字體,可參看http://poi.apache.org/spreadsheet/quick-guide.html。

HTML

HTML是網(wǎng)頁的格式,如果不熟悉,可以參看http://www.w3school.com.cn/html/html_intro.asp。在日常工作中,可能需要分析HTML頁面,抽取其中感興趣的信息。有很多HTML分析器,我們簡要介紹一種,jsoup,其官網(wǎng)地址為https://jsoup.org/。

本節(jié)使用其1.10.2版本。如果使用Maven管理項(xiàng)目,可引入以下文件中的依賴:https://github.com/swiftma/program-logic/blob/master/html_lib/dependencies.xml。如果非Maven,可從下面地址下載依賴庫:https://github.com/swiftma/program-logic/tree/master/html_lib。

我們通過一個(gè)簡單例子來看jsoup的使用,我們要分析的網(wǎng)頁地址是:http://www.cnblogs.com/swiftma/p/5631311.html 

瀏覽器中看起來的樣子是這樣的(部分截圖):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

將網(wǎng)頁保存下來,其HTML代碼看上去是這樣的(部分截圖):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

假定我們要抽取網(wǎng)頁主題內(nèi)容中每篇文章的標(biāo)題和鏈接,怎么實(shí)現(xiàn)呢?jsoup支持使用CSS選擇器語法查找元素,如果不了解CSS選擇器,可參看http://www.w3school.com.cn/cssref/css_selectors.asp。

定位文章列表的CSS選擇器可以是

#cnblogs_post_body p a

我們來看代碼(假定文件為articles.html):

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

Document doc = Jsoup.parse(new File("articles.html"), "UTF-8");
Elements elements = doc.select("#cnblogs_post_body p a");for(Element e : elements){
    String title = e.text();
    String href = e.attr("href");
    System.out.println(title+", "+href);
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

輸出為(部分):

計(jì)算機(jī)程序的思維邏輯 (1) - 數(shù)據(jù)和變量, http://www.cnblogs.com/swiftma/p/5396551.html計(jì)算機(jī)程序的思維邏輯 (2) - 賦值, http://www.cnblogs.com/swiftma/p/5399315.html

jsoup也可以直接連接URL進(jìn)行分析,比如,上面代碼的第一行可以替換為:

String url = "http://www.cnblogs.com/swiftma/p/5631311.html";
Document doc = Jsoup.connect(url).get();

關(guān)于jsoup的更多用法,請(qǐng)參看其官網(wǎng)。

壓縮文件

壓縮文件有多種格式,Java SDK支持兩種:gzip和zip,gzip只能壓縮一個(gè)文件,而zip文件中可以包含多個(gè)文件。下面我們介紹Java SDK中的基本用法,如果需要更多格式,可以考慮Apache Commons Compress:http://commons.apache.org/proper/commons-compress/

先來看gzip,有兩個(gè)主要的類:

java.util.zip.GZIPOutputStream
java.util.zip.GZIPInputStream

它們分別是OutputStream和InputStream的子類,都是裝飾類,GZIPOutputStream加到已有的流上,就可以實(shí)現(xiàn)壓縮,而GZIPInputStream加到已有的流上,就可以實(shí)現(xiàn)解壓縮。比如,壓縮一個(gè)文件的代碼可以為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public static void gzip(String fileName) throws IOException {
    InputStream in = null;
    String gzipFileName = fileName + ".gz";
    OutputStream out = null;    try {
        in = new BufferedInputStream(new FileInputStream(fileName));
        out = new GZIPOutputStream(new BufferedOutputStream(                new FileOutputStream(gzipFileName)));
        copy(in, out);
    } finally {        if (out != null) {
            out.close();
        }        if (in != null) {
            in.close();
        }
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

調(diào)用的copy方法是我們?cè)?a target="_blank" style="text-decoration-line: none; color: rgb(51, 153, 255);">57節(jié)介紹的。解壓縮文件的代碼可以為:

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

public static void gunzip(String gzipFileName, String unzipFileName)        throws IOException {
    InputStream in = null;
    OutputStream out = null;    try {
        in = new GZIPInputStream(new BufferedInputStream(                new FileInputStream(gzipFileName)));
        out = new BufferedOutputStream(new FileOutputStream(
                unzipFileName));
        copy(in, out);
    } finally {        if (out != null) {
            out.close();
        }        if (in != null) {
            in.close();
        }
    }
}

大學(xué)生就業(yè)培訓(xùn),高中生培訓(xùn),在職人員轉(zhuǎn)行培訓(xùn),企業(yè)團(tuán)訓(xùn)

http://www.cnblogs.com/swiftma/p/6379834.html