1. 程式人生 > >資料儲存方式之 TXT 文字

資料儲存方式之 TXT 文字

Java 操作檔案輸入流與輸出流,具體內容包括 File 類、檔案位元組流與字元流、緩衝流。最後以網路爬蟲實戰案例,講解其具體的使用方式。 輸入流、輸出流簡介 在 Java 中,流是從源到目的地的位元組的有序序列。Java 中有兩種基本的流——輸入流和輸出流。輸入流與輸出流提供了一條通道,使用該通道可以讀取源中的資料或者把資料傳送到目的地。示意圖如下: 在這裡插入圖片描述 Java 中 java.io 包幾乎包含了所有操作輸入、輸出需要的類。Java 把 InputStream 抽象類的子類建立的流物件稱作為位元組輸入流(FileInputStream)、OutputStream 抽象類的子類建立的流物件是位元組輸出流(FileOutputStream)。再者,Java 把 Reader 的抽象類的子類建立的流物件稱作為字元輸入流(FileReader),將 Writer 抽象子類建立的流物件稱之為輸出流(FileWriter);另外,Java 中也提供了更高階的流——緩衝輸入流 BufferedReader、輸出流 BufferedWriter。在本篇中,將講解這些操作的使用。 File 類的使用 File 物件主要用來獲取檔案本身的一些資訊,包括檔案所在的目錄、檔案是否可讀、檔案是否存在、檔案長度等等,不會涉及到檔案的具體讀寫操作。

以下是 File 類經常使用的一些方法: 在這裡插入圖片描述 在使用 File 類時,第一步需要建立一個檔案物件,使用如下方式進行建立:

File file = new File("data/");

以下是一個具體的案例,大家可以看看該類中一些方法的使用。

File root = new File("data/");
//判斷檔案是否問一個目錄
Boolean is_directory = root.isDirectory();
System.out.println(root.isDirectory());
//如果是一個目錄
if (is_directory) {
    //獲取目錄下所有檔案和目錄的絕對路徑,得到的是File陣列
    File[] files = root.listFiles(); 
    for ( File file : files ){
        System.out.println("檔名稱為:" + file.getName());
        System.out.println("檔案可讀否:" + file.canRead());
        System.out.println("絕對路徑:"+file.getAbsolutePath());
        System.out.println("檔案的長度為:" + file.length());
    }
}

在上述程式中,data/ 為一個目錄,該目錄中有兩個檔案(1.txt 和 2.txt),執行上述程式可得到如下結果: 在這裡插入圖片描述 檔案位元組流 位元組流,處理的單元為1個位元組,用於操作位元組和位元組陣列,其能夠很好的處理圖片、PDF、音訊等檔案。但使用位元組流處理中文,經常會出現亂碼,主要原因是一箇中文漢字佔用了2個位元組。因此,在操作包含中文字元的字串時,不建議使用位元組流操作。在 Java 中,位元組流類繼承自 InputStream 和 OutputStream,其子類主要有 FileInputStream 和 FileOutputStream。

下面顯示了四個構造方法,分別是建立檔案位元組輸入流以及檔案位元組輸出流的方法:

//建立檔案位元組輸入流的兩種方式
FileInputStream inputStream = new FileInputStream("data/1.txt");
FileInputStream inputStream = new FileInputStream(new File("data/1.txt"));
//建立檔案位元組輸出流的兩種方式
FileOutputStream outputStream = new FileOutputStream("data/out.txt");
FileOutputStream outputStream = new FileOutputStream(new File("data/out.txt"));

其中,以位元組為單位讀寫檔案主要用到的方法有:

read();  //順序讀取檔案的單個位元組
read(byte b[]);  //byte數值用於臨時成塊存放位元組
write(byte b[]);//位元組寫入檔案
write(byte b[], int off, int len); //從給定位元組陣列中起始於偏移量off處寫len個位元組

讀寫操作完成之後,需要使用 close() 方法,關閉開啟的流。以下給出了一個簡單的使用案例:

//建立檔案位元組輸入流與輸出流
//FileInputStream inputStream = new FileInputStream("data/1.txt");
FileInputStream inputStream = new FileInputStream(new File("data/1.txt"));
FileOutputStream outputStream = new FileOutputStream("data/out.txt");
//FileOutputStream outputStream = new FileOutputStream(new File("data/out.txt"));
int temp;
//讀寫操作
while ((temp = inputStream.read()) != -1) {
    System.out.print((char)temp);
    outputStream.write(temp);
}
//流的關閉
outputStream.close();
inputStream.close();

檔案字元流 在上面一小節中,提到檔案位元組流不能很好地處理中文字元,這時可以使用字元流操作。與 FileInputStream 和 FileOutputStream 位元組流相對應的是 FileReader 和 FileWriter,它們分別繼承自 Reader 和 Writer 這兩個抽象類。其基本構造方法如下:

//兩種檔案字元輸入流建立方式
FileReader fileReader = new FileReader("data/1.txt");
//FileReader fileReader = new FileReader(new File("data/1.txt"));
//兩種檔案字元輸出流建立方式
FileWriter fileWriter = new FileWriter("data/outtest.txt");
//FileWriter fileWriter = new FileWriter(new File("data/outtest.txt"));

字元輸入流和輸出流的 read 和 write 方法,以字元為單位讀寫資料。其基本使用方法與位元組流相同。

read();  //順序讀取檔案的單個字元
read(char b[]);  //用於臨時成塊存放字元
write(char b[]);//字元寫入檔案
write(char b[], int off, int len); //從給定字元陣列中起始於偏移量off處寫len個字元

以下給出了一個具體的案例程式,其中讀和寫的文字皆為中文字元:

//兩種檔案字元輸入流建立方式
FileReader fileReader = new FileReader("data/3.txt");
//FileReader fileReader = new FileReader(new File("data/1.txt"));
//兩種檔案字元輸出流建立方式
FileWriter fileWriter = new FileWriter("data/outtest.txt");
//FileWriter fileWriter = new FileWriter(new File("data/outtest.txt"));
int temp;
while ((temp = fileReader.read()) != -1) {
    System.out.print((char)temp);
    fileWriter.write((char)temp);
}
fileWriter.close();
fileReader.close();

緩衝流 位元組流與字元流都是無緩衝的輸入、輸出流,每一次的讀寫都涉及到磁碟的讀寫操作,相比於記憶體操作要慢得多。所以,使用位元組流和字元流的操作效率要比緩衝流操作低。另外,緩衝流提供了很好的 readLine 操作,即按行操作。在一些機器學習演算法的輸入中,經常使用到緩衝流的按行讀取操作,例如,分詞與句子情感計算、主題模型(每一行表示一個文件)等。在網路爬蟲中,經常使用到緩衝流來讀取需要爬取的 URL 列表以及儲存爬取的字元型資料。

Java 中,經常使用到的是 BufferedReader 和 BufferedWriter(緩衝流中的字元流)。其主要構造方法如下:

//輸入流
BufferedReader(Reader in, int sz) //建立一個使用指定大小輸入緩衝區的緩衝字元輸入流。
BufferedReader(Reader in) //建立一個使用預設大小輸入緩衝區的緩衝字元輸入流
//輸出流
BufferedWriter(Writer out, int sz) //建立一個使用給定大小輸出緩衝區的新緩衝字元輸出流
BufferedWriter(Writer out) //建一個使用預設大小輸出緩衝區的緩衝字元輸出流

經常使用到的方法是 readLine() 操作,即讀取一行。而寫操作主要是 write(),下面通過程式帶大家瞭解它們的具體使用方法:

/****** 檔案讀取第一種方式  ******/
File file = new File("data/3.txt");
//FileReader讀取檔案
FileReader fileReader = new FileReader(file);
//根據FileReader建立緩衝流
BufferedReader bufferedReader = new BufferedReader(fileReader);
String s = null;
//按行讀取
while ((s = bufferedReader.readLine())!=null) {
    System.out.println(s);
}
//流關閉
bufferedReader.close();
fileReader.close();
/****** 檔案讀取第二種方式  ******/
//這裡簡寫了,已成了一行。可以新增字元編碼
BufferedReader reader = new BufferedReader( new InputStreamReader( new FileInputStream( new File( "data/3.txt")),"utf-8"));
String s1=null;
while ((s1 = reader.readLine())!=null) {
    System.out.println(s1);
}
//流關閉
reader.close();
/****** 檔案寫入第一種方式  ******/
/*File file1 = new File("data/bufferedout.txt","gbk");
FileOutputStream fileOutputStream = new FileOutputStream(file1);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
BufferedWriter bufferedWriter1 = new BufferedWriter(outputStreamWriter);*/
/****** 檔案寫入快捷方式******/
BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( new File("data/bufferedout.txt")),"gbk"));
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(0, "http://pic.yxdown.com/list/2_0_2.html");
map.put(1, "http://pic.yxdown.com/list/2_0_3.html");
map.put(2, "http://pic.yxdown.com/list/2_0_4.html");
//map遍歷資料 
for( Integer key : map.keySet() ){
    writer.append("key:"+key+"\tvalue:"+map.get(key));
    writer.newLine(); //寫入換行操作
}
//流關閉
writer.close();

網路爬蟲中的文字儲存例項 接下來,我們將通過一個具體實戰案例,講解網路爬蟲中涉及到的文字操作。爬取的網站為網易汽車某論壇(網址為:http://baa.bitauto.com/CS55/)。

在爬取資料前,應確定要爬取的資料內容,例如我要爬取的是該網頁的帖子 ID 以及帖子標題。 在這裡插入圖片描述 接著,根據自己所要爬取的內容,建立 Bean 物件。具體程式如下:

public class PostModel {
    private String post_id; //帖子id
    private String post_title; //帖子標題
    public String getPost_id() {
        return post_id;
    }
    public void setPost_id(String post_id) {
        this.post_id = post_id;
    }
    public String getPost_title() {
        return post_title;
    }
    public void setPost_title(String post_title) {
        this.post_title = post_title;
    }
}

下一步,確定需要使用的網頁請求工具,這裡使用較為簡單的 jsoup 請求(通過如下 Maven 配置所需 Jar 包):

<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.11.3</version>
</dependency>

在瀏覽器中,定位所要爬取內容對應的標籤(當然,網路抓包是有必要的),如下: 在這裡插入圖片描述 最後,編寫獲取資料,解析資料,儲存資料的程式:

public class CrawlerTest {
    public static void main(String[] args) throws IOException {
        //緩衝流的建立,以utf-8寫入文字
        BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( new File("data/crawlerbitauto.txt")),"utf-8"));
        List<PostModel> data = crawerData("http://baa.bitauto.com/CS55/");
        for (PostModel model : data) {
            //所爬資料寫入文字
            writer.write(model.getPost_id() + "\t" + model.getPost_title() + "\r\n");
        }
        //流的關閉
        writer.close();
    }
    static List<PostModel> crawerData(String url) throws IOException{
        //所爬資料封裝於集合中
        List<PostModel> datalist = new ArrayList<PostModel>();
        //獲取URL對應的HTML內容
        Document doc = Jsoup.connect(url).timeout(5000).get();
        //定位需要採集的每個帖子
        Elements elements = doc.select("div[class=line-bg]").select("div[class=postslist_xh]"); 
        //遍歷每一個帖子
        for (Element ele : elements) {
            String post_id = ele.select("li.bt").select("a").attr("href").split("-")[1].replaceAll("\\D", "");
            String post_title = ele.select("li.bt").select("a").text();
            //建立物件和封裝資料
            PostModel model = new PostModel();
            model.setPost_id(post_id);
            model.setPost_title(post_title);
            datalist.add(model);
        }
        return datalist;
    }
}

上述程式執行後,便會發現所爬取採集的資料,已成功儲存到了工程目錄下的 data/crawlerbitauto.txt 文字中。該文字中的資料截圖為: 在這裡插入圖片描述 網路爬蟲下載圖片實戰案例 在採集資料時,有時需要採集圖片、Zip 等檔案,此時便可以通過位元組寫入的方式下載這些內容。

以下我們將通過一個實戰案例進行說明,所爬取的資料為遊訊相簿的資料(網址為:http://pic.yxdown.com/list/204.html)。 在這裡插入圖片描述 首先,需要通過抓包確認所爬取的每一張圖片對應的 URL 地址。我們發現抓包對應的地址和瀏覽器檢查圖片元素對應的地址有所差異,但通過兩個地址都可正常訪問圖片。 加粗樣式 下載圖片,我們使用的是 HttpClient 請求網頁內容的方式(Maven 配置 Jar 包),如下:

 <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.5</version>
        </dependency>

請求某個具體的 URL,獲取實體 HttpEntity,對應的方法如下:

 //請求某一個URL,獲得請求到的內容
    public static HttpEntity getEntityByHttpGetMethod(String url){
        HttpGet httpGet = new HttpGet(url);
        //獲取結果
        HttpResponse httpResponse = null;
        try {
          httpResponse = httpClient.execute(httpGet);
        } catch (IOException e) {
          e.printStackTrace();
        }
        HttpEntity entity = httpResponse.getEntity();
        return entity;
    }

其中,HttpClient 設定成 private static 變數:

private static  HttpClient httpClient = HttpClients.custom().build();

下面,我寫了一個方法,通過給定的圖片地址,實現相應圖片下載及儲存的功能。該方法呼叫了 getEntityByHttpGetMethod(String url) 方法,具體程式如下:

//任意輸入地址便可以下載圖片
    static void saveImage(String url, String savePath) throws IOException{
        //圖片下載儲存地址
        File file=new File(savePath);
        //如果檔案存在則刪除
        if(file.exists()){
            file.delete();
        }
        //緩衝流
        BufferedOutputStream bw = new BufferedOutputStream(new FileOutputStream(savePath)); 
        //請求圖片資料
        try {
            HttpEntity entity = getEntityByHttpGetMethod(url);
            //以位元組的方式寫入
            byte[] byt= EntityUtils.toByteArray(entity); 
            bw.write(byt);
            System.out.println("圖片下載成功!");
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //關閉緩衝流
        bw.close();
    }

可以看出,下載圖片,這裡使用的是緩衝流 BufferedOutputStream,並且寫入的是位元組陣列。

最後,是程式的主方法。在主方法中,給定待爬的地址(http://pic.yxdown.com/list/204.html),獲取該地址對應的 HTML 內容,解析 HTML 內容獲取所有圖片的連結地址,即圖片對應的 URL。針對每個圖片的 URL,呼叫 saveImage() 圖片下載方法,便可成功爬取該頁面中的所有圖片。具體程式如下:

  public static void main(String[] args) throws IOException{
        String url = "http://pic.yxdown.com/list/2_0_4.html";
        HttpEntity entity = getEntityByHttpGetMethod(url);
        //獲取所有圖片連結
        String html = EntityUtils.toString(entity);
        Elements elements = Jsoup.parse(html).select("div.cbmiddle > a.proimg > img");
        for (Element ele : elements) {
            String pictureUrl = ele.attr("src");
            saveImage(pictureUrl,"image/" + pictureUrl.split("/")[7] );
        }
        //測試程式
//        saveImage("http://i-4.yxdown.com/2018/6/11/KDE5Mngp/ae0c2d4d-04fb-4066-872c-a8c7a7c4ea4f.jpg","image/1.jpg");
    }

使用該主方法,便可成功將圖片下載到指定目錄下:

在這裡插入圖片描述 便於讀者學習,這裡提供了另外一種下載任意圖片的操作方法,對應的程式如下:

 //另外,一種操作方式
    static void saveImage1(String url, String savePath) throws UnsupportedOperationException, IOException {
        //獲取圖片資訊,作為輸入流
        InputStream in = getEntityByHttpGetMethod(url).getContent();
        byte[] buffer = new byte[1024];
        BufferedInputStream bufferedIn = new BufferedInputStream(in);
        int len = 0;
        //建立緩衝流
        FileOutputStream fileOutStream = new FileOutputStream(new File(savePath));
        BufferedOutputStream bufferedOut = new BufferedOutputStream(fileOutStream);
        //圖片寫入
        while ((len = bufferedIn.read(buffer, 0, 1024)) != -1) {
            bufferedOut.write(buffer, 0, len);
        }
        //緩衝流釋放與關閉
        bufferedOut.flush();
        bufferedOut.close();
    }