1. 程式人生 > >JavaSE | IO流

JavaSE | IO流

 

java.io.File類(檔案和目錄路徑名的抽象表示形式)

如果希望在程式中操作檔案和目錄都可以通過File類來完成,File類能新建、刪除、重新命名檔案和目錄。

File類是檔案或目錄的路徑,而不是檔案本身,因此File類不能直接訪問檔案內容本身,如果需要訪問檔案內容本身,則需要使用輸入/輸出流。

File類代表磁碟或網路中某個檔案或目錄的路徑名稱,

但他不能直接通過File物件讀取和寫入資料,如果要操作資料,需要IO流。就好比“地址”不代表“水庫”,要“存取”裡面的水到你“家裡”,需要“管道”。

* java.io包
 * 1、java.io.File類:
 
* 例如:d:/1.txt 檔案的路徑名 * D:/新建資料夾 目錄的路徑名 * /2.txt 檔案的路徑名 * ../../3.txt 目錄的路徑名 * * 在Java中一個File物件可能表示一個檔案,也可能表示一個目錄(資料夾) * 所以在IO流操作時,要注意,我們的讀寫資料必須對應的是代表檔案的File, * 如果是代表目錄的File物件,是否無法直接讀寫資料的。 * * 接觸: * java.lang:String,Object,System,Math,Comparable,Iterable... * java.util:集合,Arrays,Comparator,Scanner,日期
* java.text:SimpleDateFormat,Collator * java.math:BigInteger,BigDecimal * .... * * * 2、File類的常用的方法 * (1)構造器:new File(路徑) * file物件不一定代表的是已存在的檔案或目錄 * (2)路徑: * getPath():構造器中指定的路徑 * getAbsolutePath():絕對路徑 * getCanonicalPath():規範路徑,會對..等進行解析 * (3)檔名 * getName() * getLength():位元組 只能獲取檔案的大小,不能獲取目錄的大小 如果不存在返回0
* lastModified():毫秒 如果不存在返回0 * (4)其他的屬性:如果不存在,返回false * exists() * isFile() * isDirectory() * isHidden() * canRead() * ... * * 這些屬性,如果檔案或目錄存在,在建立File物件是,它會自動從作業系統層面獲取資料為屬性賦值,否則都是預設值 * 但是path是根據你建立File物件時指定路徑進行賦值的 * * (5)檔案和目錄的建立、刪除等操作 * 建立目錄: * mkdir() * mkdirs() * 刪除目錄: * delete()只能刪除空目錄 如果要刪除非空目錄,需要先刪除裡面的東西,在刪除它 * * 建立檔案: * createNewFile():如果對應的路徑不存在,會報異常 * 刪除檔案: * delete() * * 重新命名: * renameTo(File 新) * * (6)獲取父目錄和獲取目錄的下一級 * String getParent() * File getParentFile() * String[] list() * File[] listFiles() * * (7)如何統計目錄的大小 * * (8)如何刪除一個非空目錄

 路徑名

File類可以使用檔案路徑字串來建立File例項,該檔案路徑字串既可以是絕對路徑,也可以是相對路徑,預設情況下,系統總是依據使用者的工作路徑來解釋相對路徑,這個路徑由系統屬性“user.dir”指定,通常也就是執行Java虛擬機器時所作的路徑。

  @Test
    public void test1() throws IOException{
  //路徑名的表示方式;絕對路徑、相對路徑 File fil
= new File("F:\\code\\day01_全天資料\\day01_exam"); File file = new File("/day01_exam/day01_words.txt"); //因為我的工作空間在D盤;相對路徑,相對專案 File file2 = new File("F:"+ File.separator+ "code" + File.separator + "day01_全天資料" + File.separator + "day01_exam");//直接使用File.separator常量值表示分隔符
     System.out.println("user.dir =" + System.getProperty("user.dir")); System.out.println(
"檔名:" + file.getName()); //如果路徑是目錄or檔名,就列印目錄資料夾or檔名字; System.out.println("檔案路徑:" + file.getPath()); //構造器中指定的路徑 System.out.println("檔案絕對路徑:" + file.getAbsolutePath());//絕對路徑; window的路徑分隔符使用“\”,而Java程式中的“\”表示轉義字元,所以在Windows中表示路徑,需要用“\\”。或者直接使用“/”也可以 System.out.println("檔案規範路徑:" + file.getCanonicalFile());//規範路徑,會對..等進行解析
     System.out.println("File物件的父目錄名:" + file.getParentFile()); //父目錄,上一級的;
     System.out.println("File物件的父目錄名所對應的物件:" + file.getParentFile());

        
    }
    @Test
    public void test2(){
        File file = new File("F:\\code\\day01_全天資料\\day01_video\\day01_01Java歷史概述.avi");
        
        System.out.println("檔案的大小:" + file.length()); //對應的檔案的內容的長度(位元組數),如果File物件對應的是目錄,則結果是不確定的。
        System.out.println("檔案的最後修改時間(毫秒值):" + file.lastModified());
        long time = file.lastModified();
        Date date = new Date(time);
        System.out.println(date);//Mon Nov 12 09:57:56 GMT+08:00 2018
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //System.out.println(dateFormat.format(date)); //2018-11-12 09:57:56
        
    }
    
    @Test
    public void test3(){
        File file = new File("");
        file.exists(); //檔案或目錄是否存在
        file.isFile();//是否是檔案
        file.isDirectory(); //是否是資料夾
        file.isHidden(); //是否隱藏
        file.canRead(); //是否只讀;
     file.isAbsolute();//判斷File物件對應的檔案或目錄是否是絕對路徑
File file1 = new File("D:/HAH"); //file1.mkdirs();////建立hehe,因為xixi和haha不存在,一併建立了 file1.mkdir();//建立hehe,因為xixi和haha不存在,所以失敗,沒有報異常;只能建立單層資料夾 file1.delete();//只是刪除最裡層的檔案或者資料夾,一層層的刪(也要確保file1的path地址是對應的); } //建立目錄| 建立檔案| 刪除檔案| 重新命名檔案 @Test public void test11() throws IOException{ File dir = new File("D:/Jiing/heihei/day03.txt");

     dir.mkdir();//建立目錄(只能建立一個),不存在不會報錯,只是沒建立成功,; dir.mkdirs();
//只能建立目錄檔案;如果帶.txt會給你建立一個資料夾;如果前面目錄都沒有就都給你一塊建立了; dir.createNewFile(); //在目錄存在的情況下才能建立檔案.txt等,不能建立目錄; 如果指定的檔案不存在併成功地建立,則返回 true;如果指定的檔案已經存在,則返回 false。 //dir.renameTo(new File ("D:/Jiing/heihei/day03.txt")); //重新命名 //dir.delete(); //如果資料夾裡邊有檔案,則delete這個資料夾是刪除不了的,要先刪除裡邊的檔案才能刪除這個資料夾;只能一個個的刪 System.out.println(dir.listFiles());//[Ljava.io.File;@5cb0d902 }

 

 

public static void test1(File file){
        
        //String[] list = file.list(); //列出當前目錄的檔案的名稱
        File[] listFiles = file.listFiles();//列出當前目錄對應的File物件,檔案路徑
        for (File string : listFiles) {
            System.out.println(string);
        }
    }

//遍歷遞迴出目錄下的所有檔案路徑
    public static void listAllFile(File file){
        
        if(file.isDirectory()){
            File[] listFiles = file.listFiles();
            for (File file2 : listFiles) {
                listAllFile(file2);
            }
            System.out.println(file);
        }
    }
--->>>
D:\Jing\1
D:\Jing\2\22
D:\Jing\2
D:\Jing\新建資料夾
D:\Jing

public void listAllFiles(File dir){
        if(dir.isDirectory()){  //如果是資料夾
            System.out.println(dir); //輸出目錄(資料夾)
            File[] list = dir.listFiles();
            //System.out.println(list);
            for (File file : list) {   //每一個file都有可能有下一級
                listAllFiles(file);  //自己呼叫自己叫遞迴
            }
        }else{
            System.out.println(dir);//如果是檔案直接列印

//把 file 的所有的下一級(包括下一級的下一級)的檔案的大小全部加起來
    public long getSize(File file){
        if(file.isFile()){  //如果是檔案,直接返回它的大小
            return file.length();
        }else if(file.isDirectory()){
            File[] list = file.listFiles();  //(1)列出dir的下一級
            long sum = 0;            //2)累加每一個下一級的大小
            for (File file2 : list) {
                sum += getSize(file2);   //file2可能是檔案也可能是目錄
            }
            retur 0L;



//遞迴刪除目錄檔案
    public static void deleteDir(File file){
        
        if(file.isDirectory()){
            File[] listFiles = file.listFiles();
            for (File sub : listFiles) {
                deleteDir(sub);
            }
        file.delete(); //遞迴刪除空資料夾
        }
        //file.delete(); //刪除所有的資料夾
    }
    //跟上邊是一樣的
    public static void deleteAllSubFile(File file){
        if(file.isDirectory()){
            File[] listFiles = file.listFiles();
            for (File file2 : listFiles) {
                if(file2.length() == 0){
                    file2.delete();
                }
                deleteAllSubFile(file2);
            }
        }
        
    }
    

 

遍歷資料夾裡邊的檔案或者遍歷一個目錄下有幾個資料夾

    @Test
    public void test1(){
        File file = new File("D:\\code\\test\\me");
        
        String[] list = file.list();  //把一個資料夾裡邊的檔案全列出來;
        for (String string : list) {
            System.out.println(string);
        }
    }

列出某個目錄的所有下一級(遞迴)

        public void listAllFiles(File dir){
        if(dir.isDirectory()){  //如果是資料夾
            System.out.println(dir); //輸出目錄(資料夾)
            File[] list = dir.listFiles();
            //System.out.println(list);
            for (File file : list) {   //每一個file都有可能有下一級
                listAllFiles(file);  //自己呼叫自己叫遞迴
            }
        }else{
            System.out.println(dir);//如果是檔案直接列印;如果下一級沒有從這出去
        }
    }

計算所有檔案的大小

把 file 的所有的下一級(包括下一級的下一級)的檔案的大小全部加起來

        public long getSize(File file){
        if(file.isFile()){  //如果是檔案,直接返回它的大小
            return file.length();
        }else if(file.isDirectory()){
            File[] list = file.listFiles();  //(1)列出dir的下一級
            long sum = 0;            //2)累加每一個下一級的大小
            for (File file2 : list) {
                sum += getSize(file2);   //file2可能是檔案也可能是目錄
            }
            return sum;
        }
        return 0L;
    }

 

/*     遞迴:
     方法自己呼叫自己
     注意:一定要有“出口”,否則就死迴圈,就棧溢位
      遞迴的效率比較低,能用迴圈解決的就不要用遞迴*/
    
    @Test
    public void test1(){
        System.out.println(diGui(10));
        System.out.println(printSum(10));
    }
    public long diGui(int n){
        
        if(n == 0){
            return 1;
        }
        return n * diGui(n - 1);
    }
    public long printSum(int n){
        long sum = 1;
        for (int i = 1; i <= n; i++) {
            sum *= i;
        }
        return sum;
    }

 

 

IO的四個超級父類,抽象基類。

IO的四個超級父類,抽象基類。

  位元組輸入流:InputStream

   位元組輸出流:OutputStream

  字元輸入流:Reader

  字元輸出流:Writer

IO流類的設計選用了“裝飾者”設計模式,即IO流分為兩大類,“被裝飾”的元件和“裝飾”的元件。

例如:以InputStream為例

其中FileInputStream、ByteArrayInputStream等是“被裝飾”的元件,依次用來連線和讀取“檔案”、“記憶體中的位元組陣列”的等。

BufferedInputStream、DataInputStream、ObjectInputStream等是用來“裝飾”的元件,依次是給其他InputStream的IO流提供裝飾的輔助功能的,依次可以增加“提高效率的緩衝功能”、“按照Java資料型別讀取資料的能力”、“讀取並恢復Java物件的能力”等

你會發現OutputStream、Reader、Writer系列的流設計方式也是一樣的。

Java的IO流是單向的,只能從輸入流(Input、Reader)中讀取(read)資料,也只能往輸出流(Output、Writer)中寫(write、print)出資料。

IO流的選取可以通過以下幾個分類來簡化選取過程。

按照IO流的方向:輸入流和輸出流

l  I:代表Input

l  O:代表Output

Java的IO流是單向的,只能從輸入流(Input、Reader)中讀取(read)資料,也只能往輸出流(Output、Writer)中寫(write、print)出資料。

按照IO流的處理資料的基本單位分:

  位元組流(XxxStream):直接處理二進位制,一個位元組一個位元組處理,它適用於一切資料,包括純文字、doc、xls、圖片、音訊、視訊等等

    字元流(XxxReader和XxxWriter):一個字元一個字元處理,只能純文字類的資料。

按照角色分:節點流、處理流

 

l  節點流:連線源頭、目的地,即裝飾者IO流

l  處理流:增強功能,提高效能,即裝飾者IO流

節點流處於IO操作的第一線,所有操作必須通過他們進行;處理流是通過包裝節點流來完成功能的,處理流可以增加很多層。處理流必須依賴和包裝節點流,而不能單獨存在。

裝飾模式(Decorator Pattern)也稱為包裝模式(Wrapper Pattern),其使用一種對客戶端透明的方式來動態地擴充套件物件的功能,它是通過繼承擴充套件功能的替代方案之一。
在現實生活中你也有很多裝飾者的例子,例如:人需要各種各樣的衣著,不管你穿著怎樣,但是,對於你個人本質來說是不變的,充其量只是在外面加上了一些裝飾,有,“遮羞的”、“保暖的”、“好看的”、“防雨的”....

 

讀檔案(位元組) FileInputStream ,read

* 讀檔案的步驟:
* (1)先建立輸入流
* (2)讀取資料
* (3)關閉IO流

* InputStream:所有read的方法,如果流中沒資料,返回-1
* (1)int read():一次讀一個位元組,返回的是讀到的位元組的值
* (2)int read( byte[ ] data ):一次讀多個位元組,把讀到的數就放大data[0]....,返回的本次實際讀取的位元組數,最多讀data.length
* (3)int read(byte[ ] data, int offset, int len):一次讀多個位元組,把讀到的數就放大data[offset]....,返回的本次實際讀取的位元組數,最多讀len個

FileInputStream 位元組流 
    public
static void main(String[] args) throws IOException { FileInputStream fil = new FileInputStream("sourse/1.txt"); byte[] data = new byte[10]; int len; StringBuilder s = new StringBuilder(); //可變字串 while((len = fil.read(data)) != -1){ s.append(new String(data, 0, len)); //先讀入字串中 ; s.append( data,0 ,len byte()),沒有針對位元組的字串append方法 } System.out.println(s); fil.close(); }

 

讀檔案(字元) FileReader

* 讀檔案的步驟:
* (1)先建立輸入流
* (2)讀取資料
* (3)關閉IO流
*
* Reader:所有read的方法,如果流中沒資料,返回-1
* (1)int read():一次讀一個字元,返回的是讀到的字元的編碼值
* (2)int read(char[ ] data):一次讀多個字元,把讀到的數就放大data[0]....,返回的本次實際讀取的字元數,最多讀data.length
* (3)int read(char[ ] data, int offset, int len):一次讀多個字元節,把讀到的數就放大data[offset]....,返回的本次實際讀取的字元數,最多讀len個

public static void main(String[] args) throws IOException {
        FileReader fil = new FileReader("sourse/1.txt");
        
        char[] data = new char[10];
        int len;
        StringBuilder s = new StringBuilder();  //建立一個可變字串存資料
        while((len = fil.read(data)) != -1){  //fil.read( )--->>>返回的是int長度
            s.append(new String(data, 0, len)); //append( ) //形參是char型別,所以byte位元組不能用這個方法
       s.append(data, 0, len);
} System.out.println(s); fil.close(); }

如果1.txt檔案比較大,那麼用下面的程式碼讀取時,因為把所有的內容拼接為一個
//StringBuilder物件,它是一個可變字元序列的緩衝區,會一致擴容,以裝下所有內容,
//檔案太大的話,記憶體可能溢位

寫檔案(按位元組) FileOutputStream

 寫檔案的步驟:
(1)建立輸出流
 (2)寫資料
 (3)關閉IO流
 OutputStream:
* (1)write(int b):寫一個位元組
* (2)write(byte[] data):把整個位元組寫出去
* (3)write(byte[] data, int offset, int len):把data中的從[offset]開始,寫len個

public static void main(String[] args) throws IOException {
        
        Scanner input = new Scanner(System.in);
        System.out.print("請輸入:");
        String s = input.next();
        FileOutputStream file = new FileOutputStream("sourse/2.txt");//如果需要覆蓋模式,不用寫true,如果追加模式,寫,true
        file.write(s.getBytes());
        file.close();
        input.close();
    }

 

寫檔案按照(字元) FileWriter

* 寫檔案的步驟:
* (1)建立輸出流
* (2)寫資料
* (3)關閉IO流
*
* Writer:
* (1)write(int b):寫一個字元
* (2)write(char[] data):把整個字元陣列寫出去
* (3)write(char[] data, int offset, int len):把data中的從[offset]開始,寫len個
* (4)write(String str):把整個字串寫出去
* (5)write(String str,int offset ,int len):寫字串的一部分

public static void main(String[] args) throws IOException {
        Scanner input = new Scanner(System.in);
        System.out.print("請輸入:");
        String s = input.next();
        FileWriter fw = new FileWriter("sourse/2.txt"); 
        fw.write(s);  //輸入字元
        fw.close();  //必須把流關了,不然寫不進去喔!!
        input.close();
    
    }

把一個檔案複製一份(不一定是字串檔案,所以按位元組(可能是圖片、視訊等))

public static void copy(String srcFilePath, String destFilePath) throws IOException{
        FileInputStream file = new FileInputStream(srcFilePath); //建立輸入流
        FileOutputStream f = new FileOutputStream(destFilePath); //輸出流
        byte[] data = new byte[10]; //讀取位元組資料
        int len;
        while((len = file.read(data)) != -1){
            f.write(data, 0, len);
        }
    }

 緩衝流(為了提高讀寫效率)

BufferedReader按行讀取 

使用緩衝流的目的是為了提高讀寫效率

*  BufferedInputStream:包裝InputStream位元組輸入流
 *  BufferedReader:包裝Reader字元輸入流,按行讀取 
 *  BufferedOutputStream:包裝OutputStream位元組輸出流
 *  BufferedWriter:包裝Writer字元輸出流

複製

public static void copyBuffer(String srcFilePath, String destFilePath) throws IOException{
        FileInputStream fis = new FileInputStream(srcFilePath); //(1)建立IO流
        BufferedInputStream bis = new BufferedInputStream(fis);
        
        FileOutputStream fos = new FileOutputStream(destFilePath);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        //資料讀從srcFilePath-->fis --> bis --> data陣列;
        //資料寫從data陣列 --> bos --> fos --> destFilePath;
        //(2)一邊讀一邊寫
        byte[] data = new byte[10];
        int len;
        while((len = bis.read(data)) != -1){   //迴圈一次,從fis中把資料到data陣列,從[0]裝,一次len個
            bos.write(data, 0, len);//再從data陣列[0]取len寫到fos中
        }
        bis.close();
        fis.close();
        bos.close();
        fos.close();
        
    }
View Code

 

* BufferedReader:
   (1)String readLine()

JDK1.7之後,有一個try...with...resouces
語法格式:
* try(
 宣告需要關閉的資源
* ){
* 可能發生異常的程式碼
* }catch(異常型別 e){
* }
* 
* 無論是否發生異常,都會自動關閉

 

    public static void test1(String srcPath) throws IOException{
        try(
            FileReader fr = new FileReader(srcPath);
            BufferedReader br = new BufferedReader(fr); //位元組-->字元;
        ){
            while(true){
                String line = br.readLine();  
                if(line == null){
                    break;
                }
                System.out.println(line);//邊讀邊列印
            }
        }catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
        /*
        br.close();
        fr.close();*/
    }

 InputStreamReader(位元組-->>字元) |  OutputStreamReader(字元--->>位元組)

 InputStreamReader:把位元組流轉為字元流,並且可以指定“編碼方式”
  FileReader是按照平臺預設的字元編碼解碼

 OutputStreamWriter:把字元流按照指定的編碼轉為位元組流
//字元編碼方式GBK的檔案,在UTF-8的的控制檯上,讀取內容顯示
    @Test
    public void test1() throws IOException{
        FileInputStream fs = new FileInputStream("sourse/22.txt");//位元組輸入流
        InputStreamReader isr = new InputStreamReader(fs, "UTF-8");//位元組--字元;按位元組流讀取,然後再指定字元編碼方式,轉為字元流
        
        BufferedReader br = new BufferedReader(isr);
        //資料:resources/gbk.txt-->位元組的方式-->fs-->按照指定的編碼-->isr-->字元流-->緩衝流br
        String line;
        while((line = br.readLine()) != null){ //readLine()是按行讀取;
System.out.println(line); } br.close(); isr.close(); fs.close(); }
//嘗試用字元輸入流讀取 @Test public void test2() throws IOException{ FileInputStream fis = new FileInputStream("sourse/22.txt"); byte[] data = new byte[10]; int len; while((len = fis.read(data)) != -1){ //String的構造器,也可以指定字元編碼方式進行解碼 //String(byte[] bytes, int offset, int length, String charsetName) System.out.println(new String(data, 0, len, "GBK")); } //也可以轉成位元組 new String(data, 0, len).getBytes();//轉成位元組 fis.close(); }

 

  @Test
    public void test1() throws IOException{
        
        String str = "字串";
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("sourse/1.txt"), "UTF-8");
        osw.write(str);
        
        osw.close();
    }
    
    @Test
    public void test2() throws UnsupportedEncodingException, IOException{
        String str = "字串";
        FileOutputStream fos = new FileOutputStream("sourse/1.txt");
        fos.write(str.getBytes("UTF-8"));
        fos.close();
    }

 

操作Java各種資料型別的資料  DataOutputStream & DataInputStream

  資料輸出流允許應用程式以適當方式將基本 Java 資料型別寫入輸出流中。然後,應用程式可以使用資料輸入流將資料讀入。
  基本 Java 資料型別:包括Java基本資料型別和String

 資料輸入流允許應用程式以與機器無關方式從底層輸入流中讀取基本 Java 資料型別。
 應用程式可以使用資料輸出流寫入稍後由資料輸入流讀取的資料。

    @Test
    public void testIn() throws IOException{
        //因為DataInputStream無法與檔案直接相連;//需要檔案位元組輸入流
        DataInputStream dis = new DataInputStream(new FileInputStream("sourse/7.txt"));
        String readUTF = dis.readUTF();
        int readInt = dis.readInt();
        double readDouble = dis.readDouble();
        
        char readChar = dis.readChar();
        
        System.out.println(readUTF);
        System.out.println(readInt);
        System.out.println(readDouble);
        System.out.println(readChar);
        dis.close();
        
    }
    @Test
    public void test1() throws IOException{
         String name = "白骨精";
         int age = 500;
         double engry = 789.5;
         char gender = '女';
         
        //DataOutputStream無法直接與檔案相連,需要一個OutputStream
         DataOutputStream dos = new DataOutputStream(new FileOutputStream("sourse/7.txt"));
         dos.writeUTF(name); //開始寫資料
         dos.writeInt(age);
         dos.writeDouble(engry);
         dos.writeChar(gender);
         
         dos.close();
    }
View Code
/*要儲存如下一組資料,到game.dat檔案中,並在後面可以重寫讀取。
完成這個需求,可以使用DataOutputStream進行寫,隨後用DataInputStream進行讀取,而且順序要一致。*/
public static void saveData() throws IOException{
        String name = "巫師";
        int age = 300;
        char gender = '男';
        int energy = 5000;
        double price = 75.5;
        boolean relive = true;
        
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("resource/game.dat"));
        
        dos.writeUTF(name);
        dos.writeInt(age);
        dos.writeChar(gender);
        dos.writeDouble(price);
        dos.writeBoolean(relive);
    }
    public static void reload() throws IOException{
        
        DataInputStream dis = new DataInputStream(new FileInputStream("resource/game.dat"));
    
        String name = dis.readUTF();
        int age = dis.readInt();
        char gender = dis.readChar();
        double price = dis.readDouble();
        boolean relive = dis.readBoolean();
        
        System.out.println("姓名:" + name + ",年齡:" + age + "性別:" + gender + "價格:" + price + ",重新生活" + relive);
        
    }
    
View Code

儲存物件

如果你正在編寫遊戲,就得有儲存和恢復遊戲的功能。如果程式要儲存狀態,你可以選擇上面的方式(DataOutputStream),對每一個物件逐個地把每一項變數的值寫到特定格式的檔案中。但是其實,Java還提供了面向物件的處理方式——直接儲存物件。不過,這種方式儲存的資料,必須是由Java程式來讀取。如果在程式所儲存的檔案資料需要給某些非Java應用程式所讀取時,就不能選用這種方式了。

ObjectOutputStream:序列化(把物件轉成位元組序列輸出)

序列化與反序列化

想要輸出物件,必須藉助ObjectOutputStream,它有一個writeObject(obj)方法可以輸出物件。

如果想要反序列化,必須藉助ObjectInputStream,它有一個readObject()方法可以讀取物件。

注意:

不是所有物件都可以序列化,必須實現java.io.Serializable介面的類或其子類的物件,而且如果該物件的屬性也是引用資料型別,並且該屬性也要序列化,那麼該屬性的型別或其父類也要實現java.io.Serializable介面,否則會報java.io.NotSerializableException異常。

序列化的檔案是很難讓人閱讀的,但它比純文字檔案、或一項一項資料儲存的資料更容易讓程式恢復物件的狀態,也比較安全,因為一般人不會知道如何動手修改資料。

* 將 Java 物件的基本資料型別和圖形寫入 OutputStream。可以使用 ObjectInputStream 讀取(重構)物件。
 *     通過在流中使用檔案可以實現物件的持久儲存。如果流是網路套接字流,則可以在另一臺主機上或另一個程序中重構物件。
 * 
 * 凡是用ObjectOutputStream輸出的物件,它的類必須實現java.io.Serializable介面(序列化介面):
 * 只能將支援 java.io.Serializable 介面的物件寫入流中。每個 serializable 物件的類都被編碼,編碼內容包括類名和類簽名、物件的欄位值和陣列值,
以及從初始物件中引用的其他所有物件的閉包。
* * * ObjectInputStream:反序列化(把位元組序列重構成一個物件) * ObjectInputStream 對以前使用 ObjectOutputStream 寫入的基本資料和物件進行反序列化。 * * 寫物件: * void writeObject(obj) * 讀物件: * Object obj = readObject() *

 

 Account.setInterestRate(0.02); //靜態屬性
        Account account = new Account("101", "kris", "1234", 100000, 3.2);
        //把物件寫入資料
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("resource/account.dat"));
        oos.writeObject(account);
        
        //把物件從檔案中讀出來
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("resource/account.dat"));
        Object obj = ois.readObject();
        System.out.println(obj); 
    }


Account類--->>>
class Account implements Serializable{
    
    private static double interestRate;
    private String number;
    private String name;
    private String password;
    private double balance;
    transient private double interest;  //加上這個當初始化的時候就不會給它初始化賦值了,而是還是預設值; 不是3.2了,而是預設值0.0
    public Account(String number, String name, String password, double balance, double interest) {
        super();
        this.number = number;
        this.name = name;
        this.password = password;
        this.balance = balance;
        this.interest = interest;
    }
    public static double getInterestRate() {
        return interestRate;
    }
    public static void setInterestRate(double interestRate) {
        Account.interestRate = interestRate;
    }

....
View Code

 

 java.io.Serializable:標識型介面,沒有抽象方法的介面

  預設所有屬性都需要序列化,除了static和transient修改的屬性。

 

    public class Account implements Serializable{
    private static double interestRate;
    private String number;
    private String name;
    private String password;
    private double balance;
    transient private double interest;

 

所有對類的修改都導致原來的資料在反序列化時失敗 java.io.InvalidClassException。

解決這個問題的方法,就是在實現java.io.Serializable介面時,增加一個long型別的靜態常量serialVersionUID。如果類沒有顯示定義這個靜態變數,它的值是Java執行時環境根據類的內部細節自動生成的,若類的原始碼作了修改,serialVersionUID 就會發生變化,從而導致“舊”資料反序列化失敗。

如果對類做了會影響資料相容性的操作時,要麼修改serialVersionUID的值,使得原來的反序列化資料失敗,要麼你要對“舊”物件反序列化後引起的問題負責。

 

* Role類實現了Serializable介面後,每次編譯,會自動生成一個serialVersionUID
 * 如果當資料序列化之後,重新修改了類,就會導致  “stream中的serialVersionUID"與"local的serialVersionUID"不一致。
 * 
 * 如何解決?
 * 在序列化之前,不讓這個類每次自動生成一個serialVersionUID,而是固定一個serialVersionUID值(序列化版本ID)。

private static final long serialVersionUID = 1L; 這是預設的

* 假設engry屬性的值,不需要序列化。我們可以在這個屬性之前加一個關鍵字transient * 一般像計算的結果值等,不需要序列化,在反序列後重新計算,這樣的欄位,加transient修改 * * 如果屬性的型別是引用資料型別,那麼這個引用資料型別也要支援序列化,即實現java.io.Serializable介面, * 例如:String,Owner * * 靜態的變數是不會被序列化的。 * 因為序列化的是物件的狀態值,而靜態變數是類的值。 * 序列化的是物件獨有的內容,而靜態是所有物件共享的內容。

 


 java.io.Externalizable:


* Externalizable 例項類的唯一特性是可以被寫入序列化流中,該類負責儲存和恢復例項內容。
* 實現Externalizable介面,哪些屬性需要序列化和如何反序列化,都有你自己決定,在
* writeExternal方法和readExternal方法中決定。
* 請保留無參構造

  @Test
    public void testIn() throws FileNotFoundException, IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("sourse/66.txt"));
        Object obj = ois.readObject();
        System.out.println(obj);
        ois.close();
    }
    
    @Test
    public void testOut() throws IOException{
        
        FileOutputStream fos = new FileOutputStream("sourse/66.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        
        User user = new User(10, 20, 30);
    
        oos.writeObject(user);
        oos.close();
        fos.close();
    }
    
}

class User implements Externalizable{
    
    private static int a = 1;
    private transient int b = 2;
    private int c = 3;
    
    public User() {
        super();
    }
    
    
    public User(int b, int c) {
        super();
        this.b = b;
        this.c = c;
    }
    public User(int a, int b, int c) {
        super();
        User.a = a;
        this.b = b;
        this.c = c;
    }

    

    @Override
    public String toString() {
        return "a=" + a + ",b=" + b + ", c=" + c;
    }


    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(a);
        out.writeInt(b);
        out.writeInt(c);
        
        
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        a = in.readInt();
        b = in.readInt();
        c = in.readInt();
        
    }
    

按行輸出文字內容 

* System.out:PrintStream
* 如果沒有修改,往控制檯列印,可以修改為另個PrintStream物件
* System.err:PrintStream ;  System.in:InputStream

* PrintStream:
   1)沒有異常; 2)可以列印任意型別的資料(以字串格式輸出),如果是引用資料型別,那麼就把物件轉成字串,相當於自動呼叫toString() ; 3)自動flush()
    PrintStream 列印的所有字元都使用平臺的預設字元編碼轉換為位元組。
* PrintWriter:  web  --->> response.getWriter():這個物件伺服器是給客戶端傳送返回訊息用的。

這兩個類提供了一系列過載的print()和println()方法,用於多種資料型別的輸出。

在需要寫入字元而不是寫入位元組的情況下,應該使用 PrintWriter 類,PrintWriter類還可以自定義字元編碼。

與 PrintStream 類不同,如果啟用了自動重新整理,則只有在呼叫 println、printf 或 format 的其中一個方法時才可能完成此操作,而不是每當正好輸出換行符時才完成。這些方法使用平臺自有的行分隔符概念,而不是換行符。

  @Test
    public void test1() throws FileNotFoundException{
        PrintStream ps = new PrintStream("sourse/2.txt");
        Scanner input = new Scanner(System.in);
        
        while(true){
            System.out.print("輸入內容:");
            String next = input.next();  // input.nextLine();
            if("bye".equalsIgnoreCase(next)){
                break;
            }
            ps.print(next);
        }
        ps.close();
        input.close();
    }
    @Test
    public void test2() throws FileNotFoundException{
        //Scanner掃描器,從輸入流中掃描資料
        Scanner input = new Scanner(new FileInputStream("sourse/2.txt"));
        while(input.hasNext()){
            String nextLine = input.nextLine();
            System.out.println(nextLine);
        }
        input.close();
    }