1. 程式人生 > >file類 和IO流(一)

file類 和IO流(一)

今日任務:
1.file類
2.IO流
3.轉換流

1.file類

1.1檔案和資料夾的“增”

file類是Java中表示檔案路徑(資料夾)的型別,我們可以使用該類進行對檔案的增刪移等操作。

File file = new File("e://aaa.txt");
        if (!file.exists()) {
            System.out.println("CreateFile: "+file.createNewFile());
        }

如程式碼所示,我們new了一個file物件,然後使用exist方法檢測在file的路徑中有沒有該檔案,(該方法在有檔案時返回true,沒有時返回false。)如果沒有就使用createNewFile方法進行建立。注意這裡是要有兩個“/”的,由於轉移字元的原因。當然,createNewFile的返回值是一個boolean型,如果資料夾中已經存在了對應的檔案,也會直接返回false。

在這裡,我們建立了檔案物件,如果是一個資料夾,則我麼你需要使用的就不是createNewFile了,而是mkdirs方法

File dir=new File("d:\\aaa\\bbb\\ccc");
if(!dir.exists()) {
    System.out.println("建立:"+dir.mkdirs());
}   
1.2檔案和資料夾的“刪”

在刪除時,檔案(資料夾)有兩種刪除方法,delete方法是立刻刪除,執行完這個方法之後,我們將失去這個檔案,而deleteOnExit方法則是在jvm退出時才會刪除,在你的程式執行期間仍然存在,一般我們可以這個方法來儲存一些在程式執行期間的快取檔案而避開復雜的傳參。

//直接刪除
System.out.println("刪除結果:"+file.delete());
//jvm退出時刪除
file.deleteOnExit();

需要提出的是,在刪除資料夾時,我們只能刪除空的資料夾,裡面有檔案的資料夾將不能被被這個方法刪除,非空資料夾的刪除方法,我將在“查”中寫出。(這個檔案的原來的名稱是在new的時候命名的)

1.3檔案和資料夾的“改”

檔案和資料夾有一個相同的方法來重新命名(並不是修改內容,修改內容需要用到“流”,我們將在下文講)
這個方法在改變兩者名稱的同時,還可以改變他們所在的路徑,也就是剪下的功能。

//重新命名
System.out
.println("重新命名:"+file.renameTo(new File("d:\\123.txt"))); //剪下功能 System.out.println("重新命名:"+file.renameTo(new File("d:\\mywork\\hahaha.txt")));

可以看出,我們把在D盤下的file檔案改成了123.txt檔案,之後又把它改成了hahaha.txt檔案放在了D盤mywork資料夾中。

1.4檔案和資料夾的“查”

事實上,file類對於檔案和資料夾的方法在獲取路徑部分是一樣的,畢竟無論是檔案還是資料夾他們在目錄的層面是一樣的一個路徑,並且無論是檔案還是資料夾,都是可以隱藏、可以修改的,因此他們有著這些相同的方法。

System.out.println("獲取絕對路徑:"+file.getAbsolutePath());
System.out.println("獲取規範路徑:"+file.getCanonicalPath());
System.out.println("檔名稱:"+file.getName());
System.out.println("獲取父目錄:"+file.getParent());
System.out.println("獲取路徑:"+file.getPath());
System.out.println("是否是隱藏:"+file.isHidden());
System.out.println("最後一次修改時間:"+new Date(file.lastModified()));

而對於一個檔案來說,它還有其他的屬性,例如可讀、可寫、可執行等。他們的程式碼是

System.out.println("是否可執行:"+file.canExecute());
System.out.println("是否可讀:"+file.canRead());
System.out.println("是否可寫:"+file.canWrite());

另外,檔案還有資料夾所沒有的一個特殊的屬性,檔案長度。

System.out.println("獲取檔案長度:"+file.length());

而資料夾則要複雜很多,它不想檔案那樣有用長度,它的大小是由它當中含有著其他的檔案和資料夾決定的。所以我們要能夠知道資料夾中的情況。
最基礎的,我們首先要能得到當前資料夾中的檔案。

//獲得資料夾下的檔案和檔案的名稱
{
String[] files=dir.list();
for (String string : files) {
    System.out.println(string);
}
//得到資料夾下的檔案(及資料夾)
public void printAll(File file,int level) {
    if (!file.exists()) {
        return;
    }
    System.out.println(addblock(level)+file.getAbsolutePath());
    File[] list = file.listFiles();
    for (File file2 : list) {
        if (file2.isDirectory()) printAll(file2,level+1);
        else {
            System.out.println(addblock(level+1)+file2.getAbsolutePath());;
        }
    }
}
//為了列印好看而新增的輔助方法
private String addblock(int level) {
        StringBuffer sBuffer =new StringBuffer();
        for (int i = 0; i < level; i++) {
            sBuffer.append("\t");
        }
        return sBuffer.toString();
    }

這兩個方法都是獲得資料夾下檔案的資訊之後列印到螢幕上。
可以看到,這兩個方法呼叫的的核心的方法分別是返回String陣列的dir.list()和返回file類陣列的file.listFiles()。之後我們就只用迴圈輸出即可。

看上去dir.list()方法似乎沒有什麼大用,然而它可以傳入一個變數,對於檢索出的東西進行匹配查詢。

System.out.println("-------------獲取資料夾中.txt檔案--------------");
    String[] files2=dir.list(new FilenameFilter() {     
        @Override
        public boolean accept(File dir, String name) {
            if(name.endsWith(".txt")) {
                return true;
            }
            return false;
        }
    });
        for (String string : files2) {
            System.out.println(string);
        }

在這裡,使用了FilenameFilter作為內部類作為過濾器,過濾掉了不符合條件的檔案.

在Java程式設計思想這本書裡面對第二個方法描述得可高端了:DirFilter這個類存在的唯一原因是accept()方法,建立這個類的目的在於把accept()方法提供給list()使用,使list()可以回撥accept(),進而決定哪些檔案包含在列表中。因此,這種結構也稱為回撥。更具體的說,這是一個策略模式的例子,因為list()實現了基本的功能,且按照FilenameFilter的形式提供了這個策略,以便完善list()在提供服務時所需的演算法。因為list()接受FilenameFilter物件作為引數,這意味著我們可以傳遞任何實現了FilenameFilter介面的類的物件,用以選擇list()方法的行為模式。
(↑這一段不是我寫的,雖然裡面說的設計模式和程式設計思想我都看過,然而我並沒有聯想到這裡,即使聯想到了我怕是也說不了這麼明白,看來對於策略模式我理解的還不到位。我還需要學習,最後像這個大佬一樣。原文連線)

file.listFiles()的方法既然可以獲得資料夾下的所有的檔案,那麼只要將這個方法稍微改一改,我們就可以得到一個刪除一個非空資料夾的方法。

    public void deleteAll(File file) {
        //讓我們先檢測一下傳進來的file是否存在。省的報錯。
        //當然,如果想檢測錯誤就不要加這個if
        if (!file.exists()) {
            return;
        }
        //獲得資料夾下所有的檔案
        File[] list = file.listFiles();
        if (list != null&&list.length>0) {
            for (File file2 : list) {
                //判斷這個file是否是一個資料夾,如果是,
                //則遞迴呼叫這個方法,將其下的檔案也全部刪除
                if (file2.isDirectory()) { 
                    deleteAll(file2);
                }
                else {
                    //如果不是個資料夾,那就是個檔案了,直接刪除
                    file2.delete();
                }
            }
        }
        //刪除傳進來的,經過迴圈現在已經空了的資料夾 
        file.delete();
    }

在這個方法中使用了一個新的方法,是我們在這篇文章中還沒有提到的。isDirectory(),這個方法是判斷一個file類中儲存的到底是檔案還是資料夾的方法,如果是資料夾就會返回true。
與之對應的是file.isFile(),判斷file中是否是一個檔案的方法。

一圖流:

file類一圖流

2.IO流

IO流按方向可以分為輸入流輸出流,按資料單位分可以分為位元組流、位元組流。

輸入流即從外存讀取到記憶體中使用的流
輸出流即從記憶體寫入到外存中使用的流

外存可以是任何儲存裝置,硬碟、U盤,行動硬碟等。

位元組流即按照位元組向外讀出的流,一次只讀出設定大小的量。
字元流即按照字元向外讀出的流,一個字元可能是一個位元組,兩個位元組或者更多。

在Java中,這兩種區別方式,分別組成了四個介面,以及介面下的六個實現類。
(這裡只討論用的最多的6個,剩下的請查API)

2.1輸入流+位元組流

位元組流和字元流組合,形成了抽象類InputStream類

方法名 描述
void close() 關閉此輸入流並釋放與該流關聯的所有系統資源。
int read() 從輸入流中讀取資料的下一個位元組。
int read(byte[] b) 從輸入流中讀取一定數量的位元組,並將其儲存在緩衝區陣列b中。
int read(byte[] b,int off, int len) 將輸入流中off的位置最多len個數據位元組讀入 byte 陣列。

而他的實現類中用的最多的則是FileInputStream類。

FileInputStream fis =new FileInputStream("你自己要讀取的檔案的路徑");
    //這個陣列用於儲存數值,他的大小就是我們一次讀取的大小。大小是B。
    byte[] bytes = new byte[1024];
    //len用於記錄輸入流實際讀取的大小。
    //之所以不把它直接放到陣列中是因為最後一組可能讀不滿一個數組的長度。
    int len = 0;
    while ((len=fis.read(bytes))!=-1) {
        System.out.println(len);
        for (int i = 0; i < len; i++) {
            //bytes陣列中存放的當然都是bytes型別的,
            //要列印在螢幕上我們需要轉化
            System.out.print((char)(bytes[i]));
        }
        System.out.println();
    }
    //由於檔案並不出於jvm中,所以垃圾回收機不能回收,我們需要手動回收。
    fis.close();
2.2輸出流+位元組流

位元組流和字元流組合,形成了抽象類OutputStream類

方法名 描述
void close() 關閉此輸出流並釋放與此流有關的所有系統資源。
void flush() 重新整理此輸出流並強制寫出所有緩衝的輸出位元組。
void write(byte[] b) 將 b.length 個位元組從指定的 byte 陣列寫入此輸出流。
void write(byte[] b,int off, int len) 將指定 byte 陣列中從偏移量 off 開始的 len 個位元組寫入此輸出流。
void write(int b) 將指定的位元組寫入此輸出流。

而他的實現類中用的最多的則是FileInputStream類。

FileOutputStream fos = new FileOutputStream("你的檔案的路徑",true);
//設定byte陣列,當然,在這裡是空的,只是順便提醒一下這個類是用byte陣列的
byte[] b = new byte[1024];
//我們可以直接使用getBytes方法將想要寫入的字串轉化為Byte陣列
fos.write("aaa".getBytes());
//flush方法強制將快取區中的資料寫入到外存中
fos.flush();
//其實close方法也帶有將緩區中的資料寫入外存的工程,但誰知道你會不會忘寫。
//為了防止忘寫close方法造成緩衝區的資料丟失,我們最好加上flush方法
fos.close();
2.3字元流+輸入流

在我們使用位元組流進行輸入輸出時,我們會發現,我們的漢字都變成了亂碼。這是因為位元組流的byte陣列雖然很大,但它最底層仍然是一個位元組一個位元組拆開讀寫的。是的。拆開。而我們都知道,我們的漢字在不同的編碼中可能佔據不同的位元組,但常用的編碼方式中沒有所有漢字都只佔一個位元組的。所以當我們把一個漢字拆開以後,我們所得到的就只能是一個位元組一個位元組的碎片。

為了應對類似這樣的情況,我們就需要使用字元流一個字元一個字元的進行輸入輸出來保證漢字的完整性。

Reader是所有字元輸入流的父類,為一個抽象類,不能例項化物件,使用它的子類FileReader類。

FileReader fr =new FileReader("你的路徑");
int data = 0;
while ((data=fr.read())!=-1) {
    System.out.println((char)data);
}
fr.close();

在程式碼上,和位元組流不同,我們不需要在使用byte陣列來接收資料,而直接使用int行來接收,這裡的int就是每個字元的ASCII碼。

2.4字元流+輸出流

Writer類就比較簡單,不多說直接上程式碼。

File file = new File("file/output1.txt");
Writer writer = new FileWriter(file);
//寫入
//注意:區別於位元組流,可以直接傳字串
writer.write("天邊最美的雲彩");
writer.flush();//立即寫入硬碟
writer.close();

需要提醒的一點是,要注意我們的編譯器和檔案的編碼方式的區別哦。一般我們新建立的txt檔案的編碼方式都是GBK,而我們在自己用eclipse時會把eclipse的編碼換成UTF-8方便以後的網路程式設計。這樣就會導致我們從檔案中讀寫的漢字和我們在eclipse的控制檯列印的漢字編碼方式不一致,造成亂碼。記得在使用時更改編碼方式。
既然我們在讀寫的時候編碼方式對於我們造成了這麼大的影響,那麼我們有沒有什麼可以直接指定編碼方式呢?有。

3.自帶編碼方式的字元輸入輸出流(轉換流)

作用:

  • a.實現位元組流到字元流的轉換
  • b.解決中文亂碼的問題
    • 中文編碼
      GB2312
      GBK
      GB18030
      (英文、數字都是一個位元組,中文是兩個位元組)
    • Unicode字符集(包含每個國家的所有字元)國際通用
      unicode編碼 使用兩個位元組—65536個字元
      為了節省空間
      utf-8 使用 1 、2、3個位元組
      utf-16 使用兩個位元組—65536個字元
      utf-32 使用4個位元組
      • 臺灣 big5
      • ANSI:在簡體中文Windows作業系統中, ANSI 編碼代表 GBK 編碼
        c.只有轉換流才能指定讀取和寫入的字符集
3.1 InputStreamReader類

InputStreamReader:位元組字元轉換輸入流,將位元組輸入流轉換為字元輸入流
案例:

public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        InputStreamReader reader = new InputStreamReader(new FileInputStream(new File("file/input1.txt")),"UTF-8");

        //3.讀取
        char[] arr = new char[16];
        int len = 0;

        while((len = reader.read(arr)) != -1) {
            String string = new String(arr, 0, len);
            System.out.println(string);
        }

        reader.close();
    }
}
3.2 OutputStreamWriter類

OutputStreamWriter:字元轉換輸出流,將記憶體中的字元轉成位元組儲存到硬碟中。
程式碼實現:

public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
        //需求:將一段文字以utf-8的格式寫入到檔案中
        //【注,檔案格式為預設格式】
        //1.例項化FIle物件
        //注意:對於所有的輸出流而言,檔案可以不存在,
        //在進行寫入的過程中可以自動進行建立
        //但是,對於所有的輸入流而言,檔案必須先存在,然後才能操作,
        //否則,會丟擲FileNotFounedException
        File file = new File("file/output1.txt");

        //2.例項化轉換輸出流
        //如果不想覆蓋原始檔中的內容時,則在傳參的時候,
        //設定一個引數為true
        OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file,true), "utf-8");

        //3.寫入
        writer.write("家客戶放假啊剛回家");

        //4.重新整理
        writer.flush();

        //5.關閉
        writer.close();
    }
}

4.每日10題錯題記

1.Math.round(11.5) 等於:()
A、11
B、11.5
C、12
D、12.5
正確答案: C 你的答案: A (錯誤)
解析:round方法,它表示“四捨五入”,演算法為Math.floor(x+0.5),即將原來的數字加上0.5後再向下取整,所以,Math.round(11.5)的結果為12,Math.round(-11.5)的結果為-11。 ceil是天花板,向上取整。 floor是地板,向下去整。

2.下列哪一項的實現不可以新增為 TextField 物件的監聽器()
A、MouseMotionListener
B、FocusListener
C、WindowsListener
D、ActionListener
正確答案: C 你的答案: A (錯誤)
解析:Swing的內容已淘汰。
在單行文字輸入區(Textfield)構件上可能發生的事件包括FocusEvent焦點事件,所對應的事件監聽器是FocusListener;ActionEvent動作事件,所對應的事件監聽器是ActionListener;MouseEvent滑鼠事件,所對應的事件監聽器是MouseMotionListener;

3.下面有關java類載入器,說法正確的是?
A、引導類載入器(bootstrap class loader):它用來載入 Java 的核心庫,是用原生程式碼來實現的
B、擴充套件類載入器(extensions class loader):它用來載入 Java 的擴充套件庫。
C、系統類載入器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來載入 Java 類
D、tomcat為每個App建立一個Loader,裡面儲存著此WebApp的ClassLoader。需要載入WebApp下的類時,就取出ClassLoader來使用
正確答案: A B C D 你的答案: B D (錯誤)
解析:參考第二天的讀書筆記

4.下列哪個選項是錯誤的。()
A、一個檔案中只能有一個public class。
B、一個檔案中可以有多個類。
C、一個類中可以有兩個main方法。
D、若類中只含一個main方法,則必須是public的。
正確答案: A D 你的答案: A B D (錯誤)
解析:疑似題目出錯?(內部類和過載)