Java基礎—IO流(三)

IO流(三)

File類

一、概述

    File類用於將文件或文件夾封裝成對象,方便對文件和文件夾的屬性信息進行操作。該類可以作爲參數傳遞給IO流的構造函數,彌補流對象在操作文件和文件夾上的缺陷。

二、File類的使用

    1.構造方法
      1)File(String FileName)
        示例:File f1 = new File("C:\\abc\\a.txt");

      2)File(Strng, parent, String FileName)
        示例:File f2 = new File("C:\\abc", "b.txt");
        該構造方法的好處在於對文件的操作更加靈活,出現文件目錄固定,文件名需要改變的時候,這個構造方法更好

      3)File(File parent, String FileName)
        示例:File d = new File("C:\\abc");
           File f3 = new File(d, "c.txt");

示例代碼:

package com.heisejiuhuche.io;

import java.io.File;

public class FileDemo {
    public static void main(String[] args) {
        /* 使用File類的不同構造方法封裝文件對象 */
        File f1 = new File("Demo1.txt");
        File f2 = new File("C:/Users/jeremy/Documents/javaTmp/", "Demo2.txt");
        File d = new File("C:/Users/jeremy/Documents/javaTmp/");
        File f3 = new File(d, "Demo3.txt");

        /* 打印各個文件對象 */
        System.out.println("f1:" + f1);
        System.out.println("f2:" + f2);
        System.out.println("f3:" + f3);

    }
}

程序輸出結果:

f1:Demo1.txt
f2:C:\Users\jeremy\Documents\javaTmp\Demo2.txt
f3:C:\Users\jeremy\Documents\javaTmp\Demo3.txt

打印封裝文件對象時的絕對路徑或相對路徑。

    2.成員方法
      1)創建操作
        -boolean createNewFile():文件名不存在的情況下創建新文件
        -boolean mkdir():創建一級目錄
        -boolean mkdirs():創建多級目錄

      2)刪除操作
        -boolean delete():刪除指定文件
        -void deleteOnExit():虛擬機退出的時候刪除指定文件

      3)判斷操作
        -boolean canExecute():判斷文件對象是否可執行
        -boolean canRead():判斷文件對象是否可讀
        -boolean canWrite():判斷文件對象是否可寫
        -boolean exists():判斷文件對象是否存在
        -boolean isDirectory():判斷文件對象是否是文件夾,判斷之前,必須先判斷該文件對象是否存在
        -boolean isFile():判斷文件對象是否是文件,判斷之前,必須先判斷該文件對象是否存在
        -boolean isHidden():判斷文件對象是否是隱藏文件
        -boolean isAbsolute():判斷文件對象路徑是否是絕對路徑
        -int compareTo(File pathname):比較兩個文件對象,以自然順序排序

      4)獲取操作
        -String getName():獲取文件對象名
        -String getParent():獲取文件對象的父母路,必須明確指定文件路徑
        -String getPath():獲取文件對象相對路徑
        -String getAbsolutePath():獲取文件對象絕對路徑,文件可以存在也可以不存在
        -long lastModified():獲取文件對象最近一次被修改的時間
        -long length():獲取文件對象大小

      5)修改操作
        -boolean renameTo(File dest):將文件修改爲指定文件名並存入指定目錄

      6)其他重要方法
        -static File[] listRoots():獲取有效盤符
        -String[] list():獲取指定目錄中的文件和文件夾,包括隱藏文件;必須是存在目錄調用,文件調用會空指針
        -String[] list(FilenameFileter filter):獲取指定格式的文件
        -File[] listFiles()
        -File[] listFiles(FileFilter filter)
        -File[] listFiles(FilenameFilter filter)

    3.遞歸
      用遞歸遍歷文件夾裏的所有內容並以層級結構打印。

示例代碼:

package com.heisejiuhuche.io;

import java.io.File;

public class RecursionDemo {
    public static void main(String[] args) {
        File dir = new File("C:/Users/jeremy/Documents/javaTmp/testfile");
        recur(dir, 0);
    }

    /* 目錄層級結構 */
    private static String getLevel(int level) {
        StringBuilder sb = new StringBuilder();
        /* 樹形結構圖形 */
        sb.append("|--");
        for(int x= 0; x < level; x++) {
            /* 根據層級的不同在樹形結構圖形前補上製表符 */
            sb.insert(0, "\t");
        }
        return sb.toString();
    }

    /* 遞歸遍歷文件夾,並打印所有文件和文件夾 */
    private static void recur(File dir, int level) {
        /* 列出所有文件到文件數組 */
        File[] files = dir.listFiles();
        /* 打印文件夾的名稱 */
        System.out.println(getLevel(level) + dir.getName());
        /* 打印完文件夾的名稱,層級自增1 */
        level++;
        /* 遍歷所有文件,如果是文件夾,則遞歸遍歷裏面的文件和文件夾 */
        for(int x = 0; x < files.length; x++) {
            if(files[x].isDirectory()) {
                recur(files[x], level);
            }
            else
                System.out.println(getLevel(level) + files[x].getName()); //如果不是文件夾,打印文件
        }
    }
}

程序輸出結果:

|--testfile
    |--aaa
        |--ccc
            |--ddd
                |--h.txt
            |--ddd - Copy (2).txt
            |--ddd - Copy - Copy.txt
            |--ddd - Copy.txt
            |--ddd.txt
        |--ccc - Copy (2).txt
        |--ccc - Copy - Copy.txt
        |--ccc - Copy.txt
        |--ccc.txt
    |--aaa - Copy (2).txt
    |--aaa - Copy - Copy.txt
    |--aaa - Copy.txt
    |--aaa.txt
    |--bbb
        |--eee
            |--f.txt
        |--eee - Copy (2).txt
        |--eee - Copy - Copy.txt
        |--eee - Copy.txt
        |--eee.txt

注意:
遞歸的使用必須要明確控制條件,便面無限循環;遞歸要慎重使用,調用次數過多,會造成內存溢出。

    4.刪除帶內容目錄

示例代碼:

package com.heisejiuhuche.io;

import java.io.File;

public class DelDirWithContentDemo {
    public static void main(String[] args) {
        File dir = new File("C:/Users/jeremy/Documents/javaTmp/testfile");

        recur(dir);
    }

    /* 遍歷所有文件夾,刪除文件,並刪除文件夾 */
    private static void recur(File dir) {
        File[] files = dir.listFiles();

        for(int x = 0; x < files.length; x++) {
            if(files[x].isDirectory())
                recur(files[x]);
            else
                /* 打印文件名及文件刪除結果 */
                System.out.println(files[x].getName() + "--files--" + files[x].delete());
        }
        /* 打印文件夾名及文件夾刪除結果 */
        System.out.println(dir.getName() + "**dir**" + dir.delete());
    }
}

程序輸出結果:

demo - Copy (2) - Copy.txt--files--true
ddd**dir**true
demo - Copy (2) - Copy - Copy - Copy.txt--files--true
demo - Copy (2) - Copy - Copy.txt--files--true
ccc**dir**true
demo - Copy (2) - Copy - Copy.txt--files--true
demo - Copy (2) - Copy.txt--files--true
demo - Copy (2).txt--files--true
demo - Copy (3) - Copy.txt--files--true
aaa**dir**true
demo - Copy - Copy - Copy.txt--files--true
demo - Copy - Copy.txt--files--true
demo - Copy - Copy - Copy.txt--files--true
eee**dir**true
bbb**dir**true
demo - Copy (2) - Copy.txt--files--true
demo - Copy (2).txt--files--true
demo - Copy - Copy (2).txt--files--true
demo - Copy - Copy (3).txt--files--true
demo - Copy - Copy - Copy.txt--files--true
demo - Copy - Copy.txt--files--true
demo - Copy.txt--files--true
demo.txt--files--true
testfile**dir**true

注意:
Java無法訪問windows中的隱藏目錄,所以,最好在for循環中判斷的時候加上:
if(!files[x].isHidden() && files[x].isDirectory())
忽略隱藏的文件夾

    5.練習
      FileWriter接收File的構造方法。

示例代碼:

package com.heisejiuhuche.io;

/**
 * 接收鍵盤錄入,然後寫入指定目錄下的文件中
 * 爲了測試FileWriter接收File類型數據作爲構造方法參數
 */

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

public class FileWriterTest {
    public static void main(String[] args) {
        try {
            writeToFile(createFile("C:\\Users\\jeremy\\Documents\\FileWriter.txt"));
        } catch(IOException e) {
            e.printStackTrace();
        }
    }

    private static File createFile(String filepath) throws IOException {
        File file = new File(filepath);
        if(!file.exists())
            file.createNewFile();
        return file;
    }

    private static void writeToFile(File file) {
        BufferedReader bufr = null;
        BufferedWriter bufw = null;

        try {
            bufr = new BufferedReader(new InputStreamReader(System.in));
            bufw = new BufferedWriter(new FileWriter(file));

            String line = null;

            while((line = bufr.readLine()) != null) {
                if(line.equals("over"))
                    break;
                bufw.write(line);
                bufw.newLine();
                bufw.flush();
            }
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(bufr != null)
                    bufr.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
            try {
                if(bufw != null)
                    bufw.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        } 
    }
}

其他類及功能性流對象

一、Properties類

    1.概述
      Properties類是HashTable的子類。該類具備Map集合的特點,儲存字符串作爲鍵值對。Properties類是集合中和IO技術相結合的容器。該類可以用於鍵值對形式的配置文件。配置文件用於保存軟件運行時的各項參數。配置完成之後將會被持久化存儲,每次運行軟件都會加載該配置文件。

    2.應用
      1)設置並打印鍵值對

示例代碼:

package com.heisejiuhuche.io;

import java.util.Properties;
import java.util.Set;

public class PropertiesDemo2 {
    public static void main(String[] args) {
        /* 創建Property對象 */
        Properties prop = new Properties();
        /* 設置鍵值對 */
        prop.setProperty("zhangsan", "18");
        prop.setProperty("wangwu", "20");
        /* 通過鍵獲取值,並打印 */
        System.out.println(prop.getProperty("zhangsan"));

        /* 將鍵都返回並裝進Set */
        Set<String> value = prop.stringPropertyNames();
        /* 遍歷集合並打印鍵值 */
        for(String str : value) {
            System.out.println(str + "::" + prop.getProperty(str));
        }
    }
}

程序輸出結果:

18
zhangsan::18
wangwu::20

      2)從文件加載配置並修改

示例代碼:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Properties;

public class PropertiesLoadDemo {
    public static void main(String[] args) throws IOException {
        Properties prop = new Properties();
//      loadProp();
        /* 創建緩衝輸入流對象並關聯配置文件 */
        BufferedReader bufr = new BufferedReader(new FileReader(
                "C:/Users/jeremy/Documents/javaTmp/info.txt"));
        /* 調用prop獨享的load方法加載配置文件 */
        prop.load(bufr);
        /* 在控制檯列出所有鍵值對 */
        prop.list(System.out);

        /* 修改鍵值對 */
        prop.setProperty("李四", "50");
        /* 調用store方法修改配置文件並保存 */
        prop.store(new OutputStreamWriter(new FileOutputStream(
                "C:/Users/jeremy/Documents/javaTmp/info.txt"), "GBK"), "Test");
        prop.list(System.out);

    }
    /* 方法一:讀取文件,將每一行按=號分割,將鍵值對存入Properties對象 */
    private static void loadProp() {
         BufferedReader bufr = null;
         Properties prop = null;
         try {
             bufr = new BufferedReader(new FileReader("C:/Users/jeremy/Documents/javaTmp/info.txt"));
             prop = new Properties();
             String line = null;

             while((line = bufr.readLine()) != null) {
                 String[] kv = line.split("=");
                 prop.setProperty(kv[0], kv[1]);
             }
         } catch(IOException e) {
             e.printStackTrace();
         } finally {
             try {
                 if(bufr != null)
                     bufr.close();
             } catch(IOException e) {
                 e.printStackTrace();
             }
         }
         System.out.println(prop);
    }
}

程序輸出結果:

-- listing properties --
王五=19
張三=20
李四=90
-- listing properties --
王五=19
張三=20
李四=50

注意:
-#都是註釋,不會被Properties加載
-Properties加載的配置文件必須有固定格式;通常爲:鍵=值

      3)記錄程序運行次數
        創建配置文件記錄程序運行次數,到達5此,提示用戶註冊。

示例代碼:

package com.heisejiuhuche.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

public class PropertiesTest {
    public static void main(String[] args) throws IOException {
        try {
            checkAuth();
        } catch(IOException e) {
            throw new RuntimeException("配置文件加載異常...");
        }
    }

    private static void checkAuth() throws IOException {
        /* 創建配置文件對象 */
        File file = new File("C:/Users/jeremy/Documents/javaTmp/auth.txt");
        /* 如果文件不存在,創建文件,避免異常 */
        if(!file.exists())
            file.createNewFile();
        /* 創建輸出流對象 */
        FileInputStream fis = new FileInputStream(file);
        /* 創建Properties對象 */
        Properties prop = new Properties();
        /* 加載配置文件 */
        prop.load(fis);

        String val = null;
        int count = 0;
        /* 如果讀取鍵值爲空,說名是第一次運行,那麼將計數器count+1,然後和鍵time一起存入prop對象
         * 並寫入配置文件
         */
        if((val = prop.getProperty("time")) != null) {
            /* 如果取到了值,說明不是第一次運行,那麼讓計數器count等於time鍵所對應的值
             * 再+1,然後和time鍵一起再次存入配置文件
             */
            count = Integer.parseInt(val);
            /* 判斷,如果count = 5,說明使用次數已到,結束程序,提示註冊 */
            if(count >= 5) {
                System.out.println("請註冊...");
                return;
            }
        }

        count++;

        prop.setProperty("time", count + "");
        /* 輸出流不能在load語句前聲明,否則將會覆蓋配置文件,導致配置信息清空 */
        FileOutputStream fos = new FileOutputStream(file);
        /* 將配置文件更新 */
        prop.store(fos, "CheckAuth");

        fis.close();
        fos.close();

    }
}

二、打印流

    1.PrintStream類
      1)概述
      PrintStream類是字節打印流,其爲其他流添加了功能,使它們能夠方便打印各種數據值形式。該類不會拋出IOException,同時內部有刷新機制,無須手動刷新緩衝區。PrintStream可以直接操作文件對象。

      2)常用方法
        -PrintStream(File file):接收File對象
        -PrintStream(OutputStream out):接收字節輸出流
        -PrintStream(String filename):接收字符串路徑
        -println():打印所有基本數據類型,保持數據原樣

      3)PrintStream示例

示例代碼:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;

public class PrintStreamDemo {
    public static void main(String[] args) throws IOException {
        /* 創建輸入流和PrintStream對象並關聯文件 */
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        PrintStream ps = new PrintStream("C:/Users/jeremy/Documents/javaTmp/PrintStream.txt");

        String len = null;

        /* 接收鍵盤輸入並寫入文件 */
        while((len = bufr.readLine()) != null) {
            if(len.equals("over"))
                break;
            ps.println(len);
            ps.flush();
        }
        bufr.close();
        ps.close();
    }
}

該類的使用方法和下面介紹的PrintWriter類基本相同。

    2.PrintWriter類
      1)概述
      字符打印流可以打印基本舒蕾型。其最強大的功能是println方法,可以實現自動換行並直接操作基本數據類型。

      2)常用方法
        -PrintWriter(File file):接收File對象
        -PrintWriter(File file, String csn):接收File對象,並制定字符集
        -PrintWriter(OutputStream out):接收字節輸出流
        -PrintWriter(OutputStream out, boolean autoflush):接收字節輸出流,設置自動刷新
        -PrintStream(Writer out):接收字符輸出流
        -PrintWriter(String filename):接收字符串路徑

      3)PrintWriter示例

示例代碼:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class PrintWriterDemo {
    public static void main(String[] args) throws IOException {
        /* 創建輸入流對象和打印流對象 */
        BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(System.out, true);

        String len = null;

        while((len = bufr.readLine()) != null) {
            /* 如果len等於over,就停止程序 */
            if(len.equals("over"))
                break;
            /* 調用println方法,在打印的時候自動換行 */
            pw.println(len.toUpperCase());
        }

        bufr.close();
        pw.close();
    }
}

注意:
在PrintWriter構造方法中設置true,就無須調用flush()刷新緩衝。

三、序列流

    1.SequenceInputStream類
      1)概述
      SequenceInputStream類標識其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到達到文件末尾;接着從第二個輸入流讀取,以此類推,直到到達包含的最後一個輸入流的文件末尾爲止。

      2)應用
        將三個文件的內容使用SequenceInputStream寫入到一個文件中。

示例代碼:

package com.heisejiuhuche.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;

public class SequenceInputStreamDemo {
    public static void main(String[] args) {
        write();
    }

    private static void write() {
        SequenceInputStream sis = null;
        ArrayList<FileInputStream> list = null;
        FileOutputStream fos = null;

        try {
            /* 創建集合對象 */
            list = new ArrayList<FileInputStream>();
            /* 將輸入流對象存入集合 */
            for(int x = 1; x <= 3; x++) {
                list.add(new FileInputStream(
                "C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + x + ".txt"));
            }

            Iterator<FileInputStream> it = list.iterator();
            /* 得到裝有輸入流對象的Enumeration */
            Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
                public boolean hasMoreElements() {
                    return it.hasNext();
                }

                public FileInputStream nextElement() {
                    return it.next();
                }
            };
            /* 創建序列流對象和輸出流對象 */
            sis = new SequenceInputStream(en);
            fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\4.txt");

            byte[] buf = new byte[1024];

            int len = 0;
            /* 合併文件 */
            while((len = sis.read(buf)) != -1) {
                fos.write(buf, 0, len);
                fos.flush();
            }
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(sis != null)
                    sis.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
            try {
                if(fos != null)
                    fos.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
}

      3)切割文件後再合併
        將一個MP3文件按1M切割,最後合併,要求保證合併後的文件可以播放。

示例代碼:

package com.heisejiuhuche.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;

public class SplitMp3Test {
    public static void main(String[] args) {
        split();
        merge();
    }

    private static void merge() {
        /* 創建集合,用於存放輸入流對象 */
        ArrayList<FileInputStream> list = new ArrayList<FileInputStream>();
        SequenceInputStream sis = null;
        FileOutputStream fos = null;

        try {
            /* 將四個文件輸入流對象存入集合 */
            for(int x = 1; x <= 4; x++) {
                list.add(new FileInputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + x + ".part"));
            }

            Iterator<FileInputStream> it = list.iterator();
            /* 拿到有輸入流對象的Enumeration */
            Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
                public boolean hasMoreElements() {
                    return it.hasNext();
                }

                public FileInputStream nextElement() {
                    return it.next();
                }
            };

            /* 創建序列流對象 */
            sis = new SequenceInputStream(en);
            /* 創建輸出流對象,並關聯文件 */
            fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\back.mp3");

            byte[] buf = new byte[1024];
            int len = 0;

            /* 合併文件 */
            while((len = sis.read(buf)) != -1) {
                fos.write(buf, 0, len);
                fos.flush();
            }
        } catch(FileNotFoundException e) {
            throw new RuntimeException("文件不存在..."); 
        } catch(IOException e) {
            throw new RuntimeException("合併失敗...");
        } finally {
            try {
                if(sis != null)
                    sis.close();
            } catch(IOException e) {
                throw new RuntimeException("資源關閉失敗...");
            }
            try {
                if(fos != null)
                    fos.close();
            } catch(IOException e) {
                throw new RuntimeException("資源關閉失敗...");
            }
        }
    }

    private static void split() {
        File file = null;
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            /* 創建輸入流對象 */
            file = new File("C:\\Users\\jeremy\\Documents\\javaTmp\\Backseat.mp3");
            fis = new FileInputStream(file);

            byte[] buf = new byte[1024 * 1024];

            int count = 1;
            int len = 0;
            /* 將文件按1M大小分割,分爲count個文件 */
            while((len = fis.read(buf)) != -1) {
                fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + count++ + ".part");
                fos.write(buf, 0, len);
                fos.flush();
            }
        } catch(IOException e) {
            throw new RuntimeException("分割失敗...");
        } finally {
            try {
                if(fis != null)
                    fis.close();
            } catch(IOException e) {
                throw new RuntimeException("輸入資源關閉失敗");
            }
            try {
                if(fos != null)
                    fos.close();
            } catch(IOException e) {
                throw new RuntimeException("輸出資源關閉失敗");
            }
        }
    }
}

四、對象流

    1.ObjectOutputStream和ObjectInputStream類
      1)概述
      ObjectOutputStream類可以將Java對象的基本數據類型和圖形寫入OutputStream,再利用ObjectInputStream讀取(重構)該對象。該類用於將對象持久化存儲在硬盤上,以便程序下次啓動的時候能再次加載該對象。

      2)常用方法
        ObjectOutputStream具備直接操作基本數據類型的一些列write()方法及操作對象的writeObject()方法。以一個示例來演示該類的使用方法及注意事項。

示例代碼:

package com.heisejiuhuche.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class ObjectOutputInputStreamDemo {
    static final File file = new File("C:/Users/jeremy/Documents/javaTmp/person.txt");
    public static void main(String[] args) throws Exception {
        writeObj();
    }

    private static void readObj() throws Exception {
        /* 創建對象輸入流對象,傳入一個字節輸入流,並關聯文件 */
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        /* 讀取Person對象,向下轉型爲Person類型 */
        Person p = (Person)ois.readObject();
        /* 打印Person對象 */
        System.out.println(p);
        /* 關閉資源 */
        ois.close();
    }

    private static void writeObj() throws Exception {
        /* 創建對象輸出流對象,傳入一個字節輸出流,並關聯文件 */
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        /* 調用writeObject方法將Person對象寫入文件 */
        oos.writeObject(new Person("zhangsan", 28));
        /* 寫入文件後讀取並打印在控制檯 */
        readObj();
        /* 關閉資源  */
        oos.close();
    }
}

class Person {
    private String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return this.name + "::" + this.age;
    }
}

注意:
上面的代碼報出異常:
Exception in thread “main” java.io.NotSerializableException: com.heisejiuhuche.io.Person
在使用ObjectOutputStream寫入對象的時候,被寫入的對象必須實現Serializable接口

      3)Serializable接口
      Serializable接口是一個標記接口,它沒有任何方法。該接口給對象定義一個固定的數字標識,將該對象類序列化。使用該標識作爲對象存儲和讀取是否一致的判斷標準。這個數字標識叫做SerialVersionUID,是根據每個對象的成員,由Java計算出來的一個固定值。如果讀取的時候,該值發生了變化,會拋出異常。下面會用代碼演示這一現象及序列化的其他特點。

修改上述代碼,按要求使Person類實現Serializable接口。

示例代碼:

class Person implements Serializable {

    private String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return this.name + "::" + this.age;
    }
}

程序運行結果:

zhangsan::28

此時,如果將Person類中name成員的private關鍵字去掉,在寫入和讀取時,就會發生UID不匹配的情況,拋出異常。

示例代碼:

class Person implements Serializable {
    /* 去掉了private關鍵字 */
    String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return this.name + "::" + this.age;
    }
}

程序運行結果:

Exception in thread "main" java.io.InvalidClassException
這證明了UID是根據成員的數字標識算出來的一個固定值。

如果要實現更改成員之後還能讀取該類,只需手動指定UID。下面的代碼仍然去掉了private關鍵字,但是讀取無誤。

示例代碼:

package com.heisejiuhuche.io;

import java.io.Serializable;

class Person implements Serializable {
    /* 手動指定UID */
    private static final long serialVersionUID = 37L;

     String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return this.name + "::" + this.age;
    }
}

程序運行結果:zhangsan::28

注意:
-已被賦值的靜態成員,在序列化後,值不能修改;如,static String country = “cn”;之後再修改country的值,讀取的時候,還是”cn”;
-不想序列化某非靜態成員,可以加上transient關鍵字:transient private int age;

五、管道流

    1.概述
      管道流能通過結合線程技術,實現輸入流和輸出流的直接連接。管道輸入和輸出流必須連接在一起使用。某個線程從PipedInputStream讀取數據,並由另一個線程寫入到PipedOutputStream。這是涉及多線程技術的IO流

    2.PipedInputStream和PipedOutputStream類
      管道流可以通過構造方法連接,也可以通過調用connect()方法連接。

示例代碼:

package com.heisejiuhuche.io;

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class PipedStreamDemo {
    public static void main(String[] args) throws IOException {
        /* 創建管道流對象並連接 */
        PipedInputStream pis = new PipedInputStream();
        PipedOutputStream pos = new PipedOutputStream();
        pis.connect(pos);
        /* 啓動線程 */
        new Thread(new Read(pis)).start();
        new Thread(new Write(pos)).start();
    }

}

class Read implements Runnable {
    private PipedInputStream pis;

    Read(PipedInputStream pis) {
        this.pis = pis;
    }

    public void run() {
        /* 等待管道輸出流寫入數據,讀到輸出流數據前,處於阻塞狀態 */
        try {
            byte[] buf = new byte[1024];
            System.out.println("等待讀取...");
            int len = pis.read(buf);
            System.out.println("讀取結束...");
            System.out.println(new String(buf, 0, len));
        } catch(IOException e) {
            throw new RuntimeException("讀取失敗");
        }
    }
}

class Write implements Runnable {
    private PipedOutputStream pos;

    Write(PipedOutputStream pos) {
        this.pos = pos;
    }

    public void run() {
        /* 往管道輸入流中寫入數據,寫入先等待6秒,模擬寫入過程 */
        try {
            System.out.println("數據寫入中...");
            Thread.sleep(6000);
            pos.write("數據來啦!!!!!".getBytes());
            pos.close();
        } catch(Exception e) {
            throw new RuntimeException("寫入失敗");
        }
    }
}

程序運行結果:

等待讀取...
數據寫入中...
讀取結束...
數據來啦!!!!!

六、RandomAccessFile類

    1.概述
      RandomAccessFile類不是IO體系中的子類,而是直接繼承自Object。但是它仍然是IO包中的成員,因爲它具備讀寫功能。該類支持對隨機訪問文件的讀取和寫入。該類的內部封裝了數組,通過文件指針對數據進行操作,可以通過getFilePointer()方法獲取指針位置;通過seek()方法改變指針的位置。RandomAccessFile類只能操作文件,並必須設定操作模式。

    2.應用
      演示RandomAccessFile的基本方法及特點。

示例代碼:

package com.heisejiuhuche.io;

import java.io.IOException;
import java.io.RandomAccessFile;

public class RandomAccessFileDemo {
    public static void main(String[] args) throws IOException {
        write();
        read();
    }

    private static void write() throws IOException {
        /* 創建RnadomAccessFile對象 */
        RandomAccessFile raf = new RandomAccessFile("C:/Users/jeremy/Documents/javaTmp/random.txt", "rw");

        raf.write("李四".getBytes());
        /* write()方法的特點是,只寫出數據的最低8位,會造成數據丟失,出現亂碼 */
//      raf.write(258);
        /* 保證數據不丟失,要調用writeInt()方法,寫入4個8位 */
        raf.writeInt(97);
        raf.write("王五".getBytes());
        raf.writeInt(99);
        /* 讓指針回到文件起始位置 */
        raf.seek(0);
        /* 寫入數據,那麼李四的數據將被修改 */
        raf.write("趙六".getBytes());
        raf.writeInt(103);
        raf.close();
    }

    private static void read() throws IOException {
        RandomAccessFile raf = new RandomAccessFile("C:/Users/jeremy/Documents/javaTmp/random.txt", "rw");
        /* 創建4個字節的數組 */
        byte[] buf = new byte[4];

        /* 如果想要直接取王五的數據,調用seek方法讓指針指向第二個8位即可 */
        raf.seek(8);
        /* 如果想要直接取王五的數據,調用skepBytes方法跳過前8位 */
        raf.skipBytes(8);

        /* 讀取4個字節到數組中 */
        raf.read(buf);
        /* 用readInt讀取下4個字節 */
        int age = raf.readInt();

        System.out.println(new String(buf) + age);
    }
}

注意:
-write()方法只寫數據的最低8位,會造成數據丟失;在寫入int類型數據時,調用writeInt()方法;
-skipBytes()方法只能往前跳過指定字節數,不能往回跳轉;
-RandomAccessFile對象不僅能寫如數據,還能修改該數據;
-RandomAccessFile對象在創建時,如果模式爲”r”,就不會創建文件;如果該被讀取文件不存在,會拋出異常;模式爲”rw”,會自動創建該文件;如果文件已存在,則不會覆蓋,繼續添加內容;
-RandomAccessFile類可以結合多線程,實現數據的分段寫入,提高寫入效率

七、其他流對象

    1.DataInputStream和DataOutputStream類
      用於操作基本數據類型的流對象。

示例代碼:

package com.heisejiuhuche.io;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataStreamDemo {
    public static void main(String[] args) throws IOException {
        write();
        read();
        writeUTF();
        readUTF();
    }

    private static void readUTF() throws IOException {
        DataInputStream dos = new DataInputStream(new FileInputStream(
                "C:/Users/jeremy/Documents/javaTmp/utf-8.txt"));

        System.out.println(dos.readUTF());
    }

    private static void writeUTF() throws IOException {
        DataOutputStream dos = new DataOutputStream(new FileOutputStream(
                "C:/Users/jeremy/Documents/javaTmp/utf-8.txt"));
        /* 使用UTF-8修改版字符集寫入數據 */
        dos.writeUTF("你好");

        dos.close();
    }

    private static void read() throws IOException {
        /* 創建DataInputStream對象並關聯文件  */
        DataInputStream dos = new DataInputStream(new FileInputStream(
                "C:/Users/jeremy/Documents/javaTmp/data.txt"));
        /* 讀取基本數據類型 */
        int num = dos.readInt();
        boolean b = dos.readBoolean();
        double d = dos.readDouble();

        System.out.println("num = " + num);
        System.out.println("b = " + b);
        System.out.println("d = " + d);
    }

    private static void write() throws IOException {
        /* 創建DataOutputStream對象並關聯文件  */
        DataOutputStream dos = new DataOutputStream(new FileOutputStream(
                "C:/Users/jeremy/Documents/javaTmp/data.txt"));
        /* 寫入基本數據類型 */
        dos.writeInt(234);
        dos.writeBoolean(false);
        dos.writeDouble(124.135252);

        dos.close();
    }
}

程序運行結果:

num = 234
b = false
d = 124.135252
你好

    2.ByteArrayInputStream和ByteArrayOutputStream類
      用於操作字節數組的流對象。該類的對象無須關閉,因爲沒有調用任何系統底層資源;如果關閉,仍可以調用其方法,同時沒有任何IO異常ByteArrayInputStream在初始化的時候需要接收一個字節數組;ByteArrayOutputStream內部的緩衝區,會隨着數據的不斷寫入而自動增長。這兩個類用流的讀寫思想來操作數組,用於往內存中暫時寫入數據。

示例代碼:

package com.heisejiuhuche.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

public class ByteArrayStreamDemo {
    public static void main(String[] args) {
        readWrite();
    }

    private static void readWrite() {
        /* 創建字節數組流對象並關聯文件 */
        ByteArrayInputStream bis = new ByteArrayInputStream("abcdefg".getBytes());
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        int ch = 0;
        /* 循環讀取所有字節並寫去字節數組輸出流對象 */
        while((ch = bis.read()) != -1) {
            bos.write(ch);
        }

        /* 打印數組大小,並打印內容 */
        System.out.println(bos.size());
        System.out.println(new String(bos.toByteArray()));
        System.out.println(bos.toString());
    }
}

程序運行結果:

7
abcdefg
abcdefg

注意:
和字節數組輸入輸出流類似的流對象還有:

分類 輸入 輸出
字符數組流 CharArrayReader CharArrayWriter
字符串流 StringReader StringWriter


這些類的使用方式和字節數組流相似。


字符編碼

一、概述

    字符編碼表是由計算機二進制數1010的排列組合和文字的對應關係形成的。它的出現可以讓計算機識別各個國家不同的文字和符號。

二、常見字符編碼表

    1.ASCII碼錶
      美國標準信息交換碼,用一個字節的7位標識一個字符,最高位爲0

    2.ISO8859-1
      拉丁碼錶(歐洲碼錶),用一個字節的8位標識一個字符。

    3.GB2312和GBK
      GB2312GBK都是中文字符編碼表。後者是前者的升級版,囊括更多的中文文字和符號。

    4.Unicode
      國際標準碼,融合了全世界多種文字和符號。

    5.UTF-8
      Unicode編碼表的優化版,最多用3個字節標識一個字符。

三、亂碼問題

    亂碼問題的產生,原因在於解碼的時候沒有使用編碼時的編碼表。比如,在寫入中文數據的時候,指定了GBK編碼表,但是在讀取數據的時候卻用了UTF-8編碼表。GBK是兩個字節表示一個字符,UTF-8是三個字節表示一個字符。如果輸入的字符是“你好”,對應的GBK編碼表上的數字是【-60,-29,-70,-61】;讀取的時候誤用了UTF-8編碼表,那麼會讀取【-60,-29,-70】這三個字節,到UTF-8碼錶裏面尋找有沒有對應的字符,如果沒有,返回?;接着拿【-61】去找,如果沒有匹配的字符,返回?。反過來的過程相同。“你好”對應的UTF-8的數字是【-28,-67,-96,-27,-91,-67】,如果在讀取時誤用了GBK碼錶,那麼就拿每兩個數字去GBK表中查找對應字符,因此出現亂碼。

四、編碼解碼

    1.定義
      編碼就是將字符串轉換爲字節數組的過程;解碼就是將字節數組轉換爲字符串的過程。

    2.解決亂碼問題
      實際開發過程中,web服務器端通常默認使用ISO8859-1的編碼表。如果出現使用GBK編碼,而數據傳到服務器端之後出現亂碼的問題,只需要將亂碼的字符串再次用ISO8859-1進行編碼,在用GBK解碼即可。

示例代碼:

package com.heisejiuhuche.io;

public class EncodeDemo {
    public static void main(String[] args) throws Exception {
        String s1 = "你好";
        /* 使用gbk編碼 */
        byte[] b1 = s1.getBytes("GBK");
        /* 使用iso8859-1解碼 */
        String s2 = new String(b1, "ISO8859-1");
        /* 出現亂碼???? */
        System.out.println(s2);
        /* 是同iso8859-1再次編碼 */
        byte[] b2 = s2.getBytes("ISO8859-1");
        /* 使用gbk解碼 */
        String s3 = new String(b2, "GBK");
        /* 還原字符串成功 */
        System.out.println(s3);
    }
}

程序運行結果:

????
你好

五、聯通的問題

    在文本文件中輸入“聯通”二字,保存退出;第二次打開時會出現亂碼。

這裏寫圖片描述

圖中可見“聯通”二字的二進制儲存形式,符合UTF-8兩個字節存儲的格式要求;最高位分別爲11010。所以當“聯通”二字以GBK編碼表形式編碼存儲後,記事本在讀取的時候誤認爲是UTF-8編碼表編碼,會到UTF-8碼錶中查找字符,形成亂碼。

六、練習

    5個學生,每個學生有3們課程的成績。從鍵盤錄入以上數據,格式爲:姓名,數學成績,語文成績,英語成績;並計算出總成績。將最後的學生信息和計算出的總分按從高到低的順序存入文件“stud.txt”中。

示例代碼:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;

public class StudentInfoToFileTest {
    public static void main(String[] args) {
        /* 反轉比較器 */
        Comparator<Student> student = Collections.reverseOrder();
        /* 獲得學生對象並寫入文件 */
        StudentTools.infoToFile(StudentTools.getStudentInfo(student));
    }
}

class StudentTools {
    public static TreeSet<Student> getStudentInfo(Comparator<Student> comp) {
        BufferedReader bufr = null;
        try {
            /* 創建TreeSet對象,用於儲存學生對象並排序  */
            TreeSet<Student> stuSet = null;
            /* 接收鍵盤錄入 */
            bufr = new BufferedReader(new InputStreamReader(System.in));
            /* 判斷有無比較器參數,分別創建有比較器和沒有比較器的兩個TreeSet對象 */
            if(comp == null)
                stuSet = new TreeSet<Student>();
            else
                stuSet = new TreeSet<Student>(comp);
            String tmp = null;
            /* 不斷接收鍵盤按固定格式的錄入,如果收到over,結束程序 */
            while((tmp = bufr.readLine()) != null) {
                if(tmp.equals("over"))
                    break;
                /* 將字符串按指定格式切割 */
                String[] info = tmp.split(",");
                /* 將每段信息傳入學生對象封裝 */
                Student stu = new Student(info[0], Integer.parseInt(info[1]), 
                                                    Integer.parseInt(info[2]), 
                                                    Integer.parseInt(info[3]));
                /* 將封裝號的學生對象存入集合以便排序 */
                stuSet.add(stu);
            }
            return stuSet;
        } catch(IOException e) {
            throw new RuntimeException("文件讀入失敗...");
        } finally {
            try {
                if(bufr != null)
                    bufr.close();
            } catch(IOException e) {
                throw new RuntimeException("輸入流關閉失敗...");
            }
        }
    }

    public static void infoToFile(TreeSet<Student> stuSet) {
        File file = new File("C:/Users/jeremy/Documents/javaTmp/StudentInfo.txt");
        BufferedWriter bufw = null;

        try {
            bufw = new BufferedWriter(new FileWriter(file));
            /* 將集合中的數據寫入文件 */
            for(Student stus : stuSet) {
                bufw.write(stus.toString() + "\t");
                bufw.write(stus.getSum() + "");
                bufw.newLine();
                bufw.flush();
            }
        } catch(IOException e) {
            throw new RuntimeException("文件寫入失敗...");
        } finally {
            try {
                if(bufw != null)
                    bufw.close();
            } catch(IOException e) {
                throw new RuntimeException("輸入流關閉失敗...");
            }
        }
    }
}

/* 創建學生類 */
class Student implements Comparable<Student> {
    private String name;
    private int math, cn, en, sum;

    Student(String name, int math, int cn, int en) {
        this.name = name;
        this.math = math;
        this.cn = cn;
        this.en = en;
        sum = math + cn + en;
    }

    public void setName(String name) { this.name = name; }

    public void setMath(int math) { this.math = math; }

    public void setCn(int cn) { this.cn = cn; }

    public void setEn(int en) { this.en = en; }

    public String getName() { return name; }

    public int getMath() { return math; }

    public int getCn() { return cn; }

    public int getEn() { return en; }

    public int getSum() { return sum; }

    /* 複寫hashCode方法 */
    public int hashCode() {
        return this.name.hashCode() + this.sum * 37;
    }
    /* 複寫equals方法 */
    public boolean equals(Object obj) {
        if(!(obj instanceof Student))
            throw new ClassCastException("類型不匹配");
        Student stu = (Student)obj;

        return this.name.equals(stu.name) && this.sum == stu.sum;
    }
    /* 複寫compareTo方法 */
    public int compareTo(Student stu) {
        int flag = new Integer(this.sum).compareTo(new Integer(stu.sum));
        if(flag == 0)
            return this.name.compareTo(stu.name);
        return flag;
    }
    /* 複寫toString方法 */
    public String toString() {
        return "Student[" + this.name + "," + math + "," + cn + "," + en + "]";
    }
}

程序運行結果:

Student[zhouqi,70,70,70]    210
Student[zhaoliu,60,60,60]   180
Student[wangwu,50,50,50]    150
Student[lisi,40,40,40]      120
Student[zhangsan,30,30,30]  90
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章