12Java進階-IO與XML

小島的每一段verse發表於2021-07-26

1.File

File:java.io.File:代表一個實際的檔案或目錄。

常用構造方法File file = new File("path");

其它構造方法:

  • File(String parent, String child):建立一個新的 File 例項,該例項的存放路徑是由 parent 和 child 拼接而成的。
  • File(File parent, String child):建立一個新的 File 例項。 parent 代表目錄, child 代表檔名,因此該例項的存放路徑是 parent 目錄中的 child 檔案。
  • File(URI uri):建立一個新的 File 例項,該例項的存放路徑是由 URI 型別的引數指定的。

構造File時,路徑需要符合作業系統的命名規則。

路徑分隔符File.pathSeperator:在windows中是“;”,在Linux中是":"

路徑分隔符File.pathSeperatorChar:在windows中是’;‘,在Linux中是':'

層次路徑分隔符File.seperator:在windows中是"\",在Linux中是"/"

層次路徑分隔符File.seperatorChar:在windows中是’\‘,在Linux中是’/’

File常用方法:

File.listRoots():列出根目錄

file.exists():檔案是否存在

fie.isDirectory():檔案是否是目錄

String[] list():返回一個字串陣列,這些字串代表此抽象路徑名錶示的目錄中的檔案和目錄。
String[] list(FilenameFilter filter):返回一個字串陣列,這些字串代表此抽象路徑名錶示的目錄中,滿足過濾器 filter 要求的檔案和目錄。
File[] listFiles():返回一個 File 物件陣列,表示此當前 File 物件中的檔案和目錄。
File[] listFiles(FilenameFilter filter):返回一個 File 物件陣列,表示當前 File 物件中滿足過濾器 filter 要求的檔案和目錄。

2.IO流

抽象輸入位元組流介面:InputStream 抽象輸出位元組流介面:OutputStream

常見的輸入流:new Scanner(System.in) 常見的輸出流:System.out.print();

按照傳輸的單位分為:位元組流和字元流。

位元組流通常用來處理二進位制檔案,如音樂、圖片檔案等,並且由於位元組是任何資料都支援的資料型別,因此位元組流實際可以處理任意型別的資料。而對於字元流,因為 Java 採用 Unicode 編碼,Java 字元流處理的即 Unicode 字元,所以在操作文字、國際化等方面,字元流具有優勢。

  • FileInputStream:把一個檔案作為輸入源,從本地檔案系統中讀取資料位元組,實現對檔案的讀取操作。
  • ByteArrayInputStream:把記憶體中的一個緩衝區作為輸入源,從記憶體陣列中讀取資料位元組。
  • ObjectInputStream:對以前使用 ObjectOutputStream 寫入的基本資料和物件進行反序列化,用於恢復那些以前序列化的物件,注意這個物件所屬的類必須實現 Serializable 介面。
  • PipedInputStream:實現了管道的概念,從執行緒管道中讀取資料位元組。主要線上程中使用,用於兩個執行緒間的通訊。
  • SequenceInputStream:其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直至到達檔案末尾,接著從第二個輸入流讀取,依次類推。
  • System.in:從使用者控制檯讀取資料位元組,在 System 類中,in 是 InputStream 型別的靜態成員變數。

InputStream常見方法:

int read():從輸入流中讀取資料的下一位元組,返回 0 ~ 255 範圍內的整型位元組值;如果輸入流中已無新的資料,則返回 -1。
int read(byte[] b):從輸入流中讀取一定數量的位元組,並將其儲存在位元組陣列 b 中,以整數形式返回實際讀取的位元組數(要麼是位元組陣列的長度,要麼小於位元組陣列的長度)。
int read(byte[] b, int off, int len):將輸入流中最多 len 個資料位元組讀入位元組陣列 b 中,以整數形式返回實際讀取的位元組數,off 指陣列 b 中將寫入資料的初始偏移量。
void close():關閉此輸入流,並釋放與該流關聯的所有系統資源。
int available():返回可以不受阻塞地從此輸入流讀取(或跳過)的估計位元組數。
void mark(int readlimit):在此輸入流中標記當前的位置。
void reset():將此輸入流重新定位到上次 mark 的位置。
boolean markSupported():判斷此輸入流是否支援 mark() 和 reset() 方法。
long skip(long n):跳過並丟棄此輸入流中資料的 n 位元組。

字元的輸入輸出流:

抽象字元輸入流:Reader 抽象字元輸出流:Writer

  • FileReader :與 FileInputStream 對應,從檔案系統中讀取字元序列。
  • CharArrayReader :與 ByteArrayInputStream 對應,從字元陣列中讀取資料。
  • PipedReader :與 PipedInputStream 對應,從執行緒管道中讀取字元序列。
  • StringReader :從字串中讀取字元序列。

字元輸出流的常用方法:

Writer append(char c):將指定字元 c 追加到此 Writer,此處是追加,不是覆蓋。
Writer append(CharSequence csq):將指定字元序列 csq 新增到此 Writer。
Writer append(CharSequence csq, int start, int end):將指定字元序列 csq 的子序列,追加到此 Writer。
void write(char[] cbuf):寫入字元陣列 cbuf。
void write (char[] cbuf, int off, int len):寫入字元陣列 cbuf 的某一部分。
void write(int c):寫入單個字元 c。
void write(String str):寫入字串 str。
void write(String str, int off, int len):寫入字串 str 的某一部分。
void close():關閉當前流。

位元組流、字元流都是無緩衝的輸入、輸出流,每次的讀、寫操作都會交給作業系統來處理。對系統的效能造成很大的影響,因為每次操作都可能引發磁碟硬體的讀、寫或網路的訪問,這些磁碟硬體讀、寫和網路訪問會佔用大量系統資源,影響效率。

3.裝飾器模式

通過方法,將物件進行包裝。

比如FileOutputStream放在緩衝位元組流BufferedOutputStream的構造方法中時,就變成了BufferedOutputStream 

再把BufferedOutputStream放在DataOutputStream的構造方法中,就變成了DataOutputStream。

雖然外觀都是OutputStream,但是功能得到了增強,提供了更加豐富的API。

4.Buffered流

緩衝流的目的是讓原位元組流、字元流新增緩衝的功能。

  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter

5.位元組流轉換為字元流:

使用InputStreamReader將位元組流轉換成InputStreamReader物件,再通過字元流的建構函式轉換成字元流。

Java沒有提供位元組流轉換成字元流的方式,因為位元組流是一個通用的流,而字元流只能傳輸文字型別的資源,但是傳輸效率較快。

6.Data流

DataStream允許流直接操作基本資料型別和字串。常用的方法有

dos.writeUTF(); 

dos.writeInt(); 

dis.readUTF();

dis.readInt();

注意讀取順序要和寫入順序一致。

    public static void main(String[] args) {
        try {
            DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(new File("D:\\ideaproject\\Java02\\data.txt"))));
            dos.writeUTF("this");
            dos.writeUTF("is");
            dos.writeInt(4);
            dos.writeUTF("leellamarz");
            dos.close();
            DataInputStream dis  = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("D:\\ideaproject\\Java02\\data.txt"))));
            String a = dis.readUTF();
            String b = dis.readUTF();
            int d = dis.readInt();
            String c = dis.readUTF();
            System.out.println(a+" "+b+" "+d+" "+c);
            dis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

7.XML

XML是可擴充標記語言,可以用來儲存資料、系統配置、資料交換。

XML的標籤可以自定義。

XML 文件總是以 XML 宣告開始,即告知處理程式,本文件是一個 XML 文件。在 XML 宣告中,通常包括版本、編碼等資訊,以 <? 開始,以 ?> 結尾。

<?xml version = "1.0" encoding="UTF-8"?>

XML 文件由元素組成,一個元素由一對標籤來定義,包括開始和結束標籤,以及其中的內容。元素之間可以巢狀(但不能交叉),也就是說元素的內容裡還可以包含元素。

標籤可以有屬性(屬性值要加引號)。屬性是對標籤的進一步描述和說明,一個標籤可以有多個屬性,每個屬性都有自己的名字和值,屬性是標籤的一部分。

8.解析XML

解析XML的技術主要有:

  • DOM 即org.w3c.dom,W3C 推薦的用於使用 DOM 解析 XML 文件的介面
  • SAX 即org.xml.sax,用 SAX 解析 XML 文件的介面

DOM 把一個 XML 文件對映成一個分層物件模型,而這個層次的結構,是一棵根據 XML 文件生成的節點樹。DOM 在對 XML 文件進行分析之後,不管這個文件有多簡單或多複雜,其中的資訊都會被轉化成一棵物件節點樹。在這棵節點樹中,有一個根節點,其他所有的節點都是根節點的子節點。節點樹生成之後,就可以通過 DOM 介面訪問、修改、新增、刪除樹中的節點或內容了。

DOM解析過程:

  1. 通過getInstance()建立DocumentBuilderFactory,即解析器工廠
  2. 通過build()建立DocumentBuilder
  3. 解析檔案得到Document物件
  4. 通過NodeList,開始解析結點(標籤)

9.Node常用方法

NodeList getChildNodes():返回此節點的所有子節點的 NodeList。
Node getFirstChild():返回此節點的第一個子節點。
Node getLastChild():返回此節點的最後一個子節點。
Node getNextSibling():返回此節點之後的節點。
Node getPreviousSibling():返回此節點之前的節點。
Document getOwnerDocument():返回與此節點相關的 Document 物件。
Node getParentNode():返回此節點的父節點。
short getNodeType():返回此節點的型別。
String getNodeName():根據此節點型別,返回節點名稱。
String getNodeValue():根據此節點型別,返回節點值。

String getTextContent():返回此節點的文字內容。
void setNodeValue(String nodeValue):根據此節點型別,設定節點值。
void setTextContent(String textContent):設定此節點的文字內容。
Node appendChild(Node newChild):將節點 newChild 新增到此節點的子節點列表的末尾。
Node insertBefore(Node newChild,Node refChild):在現有子節點 refChild 之前插入節點 newChild。
Node removeChild(Node oldChild):從子節點列表中移除 oldChild 所指示的子節點,並將其返回。
Node replaceChild(Node newChild, oldChild):將子節點列表中的子節點 oldChild 替換為 newChild,並返回 oldChild 節點。

10.Document常用方法

Element getDocumentElement():返回代表這個 DOM 樹根節點的 Element 物件。
NodeList getElementsByTagName(String tagname):按文件順序返回包含在文件中且具有給定標記名稱的所有 Element 的 NodeList。

NodeList常用方法:

int getLength():返回有序集合中的節點數。
Node item(int index):返回有序集合中的第 index 個項。

11.SAX解析

SAX是事件驅動的。通過繼承DefaultHandler類,重寫五個關鍵方法實現解析。

startDocument():開始文件的標誌

endDocument():結束文件的標誌

startElement(String uri, String localName, String qName, Attributes attributes):通過比較localName,找到指定的元素,開啟元素

endElement(String uri, String localName, String qName):通過比較localName,找到指定的元素,結束元素

characters(char[] ch, int start, int length):解析每個元素時呼叫的方法

@Override
    public void startDocument() throws SAXException {
        System.out.println("books2文件開始解析");
    }

    @Override
    public void endDocument() throws SAXException {
        System.out.println("books2文件結束解析");
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if (qName.equals("book")) {
           for(int i=0;i<attributes.getLength();i++){
               System.out.println("編號:"+attributes.getValue(i));
           }

        }
        this.tagName = qName;
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if("book".equals(localName)){}
        this.tagName = null;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (this.tagName != null) {
            String data = new String(ch, start, length);
            if (this.tagName.equals("bookname")) {
                System.out.println("書名:"+data);
            }
            if (this.tagName.equals("bookauthor")) {
                System.out.println("作者:"+data);
            }

            if (this.tagName.equals("bookprice")) {
                System.out.println("價格:"+data);
            }


        }
    }

12.練習

 

 如果子類異常塊放在父類異常塊後面,就會報編譯錯誤。

  try {
            int[] a = {1,2,3};
            System.out.print(a[3]);
            System.out.print(1);
        } catch(Exception e) {
            System.out.print(2);
            System.exit(0);//2
        } finally {
            System.out.print(3);
        }

不同於return,System.exit(0)的優先順序高於finally,在前面遇到會直接退出程式。

 

 異常向外丟擲,再被外部trycatch接受,會造成死迴圈

ArrayList<String> a = new ArrayList<String>();
a.add(true);
a.add(123);
a.add("abc");
System.out.print(a);
//執行後,控制檯輸出為?編譯錯誤
//集合定義時加了泛型後,就不能新增不匹配泛型的元素。
List a = new ArrayList();
a.add(1);
a.add(2);
a.add(3);
a.remove(1);
System.out.print(a);
//執行後,控制檯輸出為? 1 3
//ArrayList 有 2 個刪除方法:a.remove(Object o); 和 a.remove(int index); 那麼這裡的 1 到底是匹配 Object 還是 int 型別呢?我們考慮一下這兩個方法的來歷就行了。
//a.remove(Object o); 是父介面的方法,a.remove(int index); 是子類重寫的方法,所以這裡應該是呼叫子類重寫的方法。
Set ts = new TreeSet();
ts.add("zs");
ts.add("ls");
ts.add("ww");
System.out.print(ts);
//執行後,控制檯輸出為?
//TreeSet 對於字串來說預設按照字典升序進行排序,所以答案為:[ls, ww, zs]
//假設檔案 c:/a.txt 的內容為 abc
//以下程式碼
try {
    File f = new File("c:/a.txt");
    System.out.print(f.length());
    OutputStream out = new FileOutputStream(f);
    System.out.print(f.length());
    out.write(97);
    System.out.print(f.length());
    out.close();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
//執行後,控制檯輸出為?301

File 物件 new 出來後,f.length() 返回值為 3。

FileOutputStream 物件 new 出來後,由於預設方法是覆蓋已經存在的檔案,所以f.length() 返回值為 0,如果想不覆蓋,應該使用new FileOutputStream(f,false);。

out.write(97) 寫入字母 a 後,f.lenght() 返回值為 1。

if(node2 instanceof Element){
String string = node2.getNodeName();
String ste = node2.getTextContent();
System.out.println(string+" "+ste);
}

出現這種問題的原因主要是使用org.w3c.dom.Node的進行解析的,它會將你的回車也作為一個節點。在你的程式碼中你列印str.getLenth();得到的數值肯定比你寫的節點要多。
如果:node2 instanceof Text,則輸出:#text
如果:node2 instanceof Element,則輸出:標籤名

或者將檔案中多餘的空格和回車都去掉。

相關文章