Java學(xué)習總結(jié)之Java IO系統(tǒng)(一)

概述

java.io 包幾乎包含了所有操作輸入、輸出需要的類。所有這些流類代表了輸入源和輸出目標。java.io 包中的流支持很多種格式,比如:基本類型、對象、本地化字符集等等。一個流可以理解為一個數(shù)據(jù)的序列。輸入流表示從一個源讀取數(shù)據(jù),輸出流表示向一個目標寫數(shù)據(jù)。Java 為 I/O 提供了強大的而靈活的支持,使其更廣泛地應(yīng)用到文件傳輸和網(wǎng)絡(luò)編程中。

Java 的 I/O 大概可以分成以下幾類:

  • 磁盤操作:File
  • 字節(jié)操作:InputStream 和 OutputStream
  • 字符操作:Reader 和 Writer
  • 對象操作:Serializable
  • 網(wǎng)絡(luò)操作:Socket
  • 新的輸入/輸出:NIO

File

Java中IO操作有相應(yīng)步驟,以文件操作為例,主要操作流程如下:
1.使用File類打開一個文件
2.通過字節(jié)流或字符流的子類,指定輸出的位置
3.進行讀/寫操作
4.關(guān)閉輸入/輸出

那么我們先來介紹一下File類
Java文件類在java.io包中,它以抽象的方式代表文件名和目錄路徑名。該類主要用于獲取文件和目錄的屬性,文件和目錄的創(chuàng)建、查找、刪除、重命名等,但不能進行文件的讀寫操作
File對象代表磁盤中實際存在的文件和目錄。通過以下構(gòu)造方法創(chuàng)建一個File對象。

通過給定的父抽象路徑名子路徑名字符串創(chuàng)建一個新的File實例。
File(File parent, String child)

通過將給定路徑名字符串轉(zhuǎn)換成抽象路徑名來創(chuàng)建一個新 File 實例。
File(String pathname)

根據(jù) parent 路徑名字符串和 child 路徑名字符串創(chuàng)建一個新 File 實例。
File(String parent, String child)

通過將給定的 file: URI 轉(zhuǎn)換成一個抽象路徑名來創(chuàng)建一個新的 File 實例。
File(URI uri)

注意:
1.在各個操作系統(tǒng)中,路徑的分隔符是不一樣的,例如:Windows中使用反斜杠:"\",Linux|Unix中使用正斜杠:"/"。在使用反斜杠時要寫成"\\"的形式,因為反斜杠要進行轉(zhuǎn)義。如果要讓Java保持可移植性,應(yīng)該使用File類的靜態(tài)常量File.pathSeparator。
2.構(gòu)建一個File實例并不會在機器上創(chuàng)建一個文件。不管文件是否存在,都可以創(chuàng)建任意文件名的File實例。可以調(diào)用File實例上的exists()方法來判斷這個文件是否存在。通過后續(xù)的學(xué)習我們會知道,當把一個輸出流綁定到一個不存在的File實例上時,會自動在機器上創(chuàng)建該文件,如果文件已經(jīng)存在,把輸出流綁定到該文件上則會覆蓋該文件,但這些都不是在創(chuàng)建File實例時進行的。

創(chuàng)建File對象成功后,可以使用以下列表中的方法操作文件。

File1.png

File2.png
File3.png
File4.png

下面給出一個使用File類的實例:

import java.io.File;
public class DirList {
   public static void main(String args[]) {
      String dirname = "/java";
      File f1 = new File(dirname);
      if (f1.isDirectory()) {
         System.out.println( "Directory of " + dirname);
         String s[] = f1.list();
         for (int i=0; i < s.length; i++) {
            File f = new File(dirname + "/" + s[i]);
            if (f.isDirectory()) {
               System.out.println(s[i] + " is a directory");
            } else {
               System.out.println(s[i] + " is a file");
            }
         }
      } else {
         System.out.println(dirname + " is not a directory");
    }
  }
}

小貼士:lastModified()方法返回的是從時間戳(1970年1月1日0時0分0秒)到當前的毫秒數(shù),返回值類型是long,可以用Date類對它進行包裝使其更易讀。

Java中的目錄

創(chuàng)建目錄:
File類中有兩個方法可以用來創(chuàng)建文件夾:

  • mkdir( )方法創(chuàng)建一個文件夾,成功則返回true,失敗則返回false。失敗表明File對象指定的路徑已經(jīng)存在,或者由于整個路徑還不存在,該文件夾不能被創(chuàng)建。
  • mkdirs()方法創(chuàng)建一個文件夾和它的所有父文件夾。
    下面的例子創(chuàng)建 "/tmp/user/java/bin"文件夾:
import java.io.File;
 
public class CreateDir {
  public static void main(String args[]) {
    String dirname = "/tmp/user/java/bin";
    File d = new File(dirname);
    // 現(xiàn)在創(chuàng)建目錄
    d.mkdirs();
  }
}

mkdirs是遞歸創(chuàng)建文件夾,允許在創(chuàng)建某文件夾時其父文件夾不存在,從而一同創(chuàng)建;mkdir必須滿足路徑上的父文件夾全都存在
注意: Java 在 UNIX 和 Windows 自動按約定分辨文件路徑分隔符。如果你在 Windows 版本的 Java 中使用分隔符 (/) ,路徑依然能夠被正確解析。
讀取目錄:
一個目錄其實就是一個 File 對象,它包含其他文件和文件夾。
如果創(chuàng)建一個 File 對象并且它是一個目錄,那么調(diào)用 isDirectory() 方法會返回 true。
可以通過調(diào)用該對象上的 list() 方法,來提取它包含的文件和文件夾的列表。
下面展示的例子說明如何使用 list() 方法來檢查一個文件夾中包含的內(nèi)容:

import java.io.File;
 
public class DirList {
  public static void main(String args[]) {
    String dirname = "/tmp";
    File f1 = new File(dirname);
    if (f1.isDirectory()) {
      System.out.println( "目錄 " + dirname);
      String s[] = f1.list();
      for (int i=0; i < s.length; i++) {
        File f = new File(dirname + "/" + s[i]);
        if (f.isDirectory()) {
          System.out.println(s[i] + " 是一個目錄");
        } else {
          System.out.println(s[i] + " 是一個文件");
        }
      }
    } else {
      System.out.println(dirname + " 不是一個目錄");
    }
  }
}

刪除目錄或文件:
刪除文件可以使用 java.io.File.delete() 方法。
以下代碼會刪除目錄/tmp/java/,即便目錄不為空。
測試目錄結(jié)構(gòu):

/tmp/java/
|-- 1.log
|-- test

deleteFolder是一個遞歸函數(shù),類似于DFS思想

import java.io.File;
 
public class DeleteFileDemo {
  public static void main(String args[]) {
      // 這里修改為自己的測試目錄
    File folder = new File("/tmp/java/");
    deleteFolder(folder);
  }
 
  //刪除文件及目錄
  public static void deleteFolder(File folder) {
    File[] files = folder.listFiles();
        if(files!=null) { 
            for(File f: files) {
                if(f.isDirectory()) {
                    deleteFolder(f);
                } else {
                    f.delete();
                }
            }
        }
        folder.delete();
    }
}

RandomAccessFile

RandomAccessFile不同于File,它提供了對文件內(nèi)容的訪問,可以讀寫文件且支持隨機訪問文件的任意位置
RandomAccessFile讀寫用到文件指針,它的初始位置為0,可以用getFilePointer()方法獲取文件指針的位置。下面是RandomAccessFile常用的方法。

RandomAccessFile.png

public int read(int x) throws IOException 方法只讀取一個字節(jié),也就是x的低八位。


import java.io.File ;
import java.io.RandomAccessFile ;
public class RandomAccessFileDemo01{
    // 所有的異常直接拋出,程序中不再進行處理
    public static void main(String args[]) throws Exception{
        File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
        RandomAccessFile rdf = null ;       // 聲明RandomAccessFile類的對象
        rdf = new RandomAccessFile(f,"rw") ;// 讀寫模式,如果文件不存在,會自動創(chuàng)建
        String name = null ;
        int age = 0 ;
        name = "zhangsan" ;         // 字符串長度為8
        age = 30 ;                  // 數(shù)字的長度為4
        rdf.writeBytes(name) ;      // 將姓名寫入文件之中
        rdf.writeInt(age) ;         // 將年齡寫入文件之中
        name = "lisi    " ;         // 字符串長度為8
        age = 31 ;                  // 數(shù)字的長度為4
        rdf.writeBytes(name) ;      // 將姓名寫入文件之中
        rdf.writeInt(age) ;         // 將年齡寫入文件之中
        name = "wangwu  " ;         // 字符串長度為8
        age = 32 ;                  // 數(shù)字的長度為4
        rdf.writeBytes(name) ;      // 將姓名寫入文件之中
        rdf.writeInt(age) ;         // 將年齡寫入文件之中
        rdf.close() ;               // 關(guān)閉
    }
};

寫完之后,開始讀取數(shù)據(jù)。寫的時候可以將一個字符串寫入,讀的時候需要一個個的以字節(jié)的形式讀取出來。


import java.io.File ;
import java.io.RandomAccessFile ;
public class RandomAccessFileDemo02{
    // 所有的異常直接拋出,程序中不再進行處理
    public static void main(String args[]) throws Exception{
        File f = new File("d:" + File.separator + "test.txt") ; // 指定要操作的文件
        RandomAccessFile rdf = null ;       // 聲明RandomAccessFile類的對象
        rdf = new RandomAccessFile(f,"r") ;// 以只讀的方式打開文件
        String name = null ;
        int age = 0 ;
        byte b[] = new byte[8] ;    // 開辟byte數(shù)組
        // 讀取第二個人的信息,意味著要空出第一個人的信息
        rdf.skipBytes(12) ;     // 跳過第一個人的信息
        for(int i=0;i<b.length;i++){
            b[i] = rdf.readByte() ; // 讀取一個字節(jié)
        }
        name = new String(b) ;  // 將讀取出來的byte數(shù)組變?yōu)樽址?        age = rdf.readInt() ;   // 讀取數(shù)字
        System.out.println("第二個人的信息 --> 姓名:" + name + ";年齡:" + age) ;
        // 讀取第一個人的信息
        rdf.seek(0) ;   // 指針回到文件的開頭
        for(int i=0;i<b.length;i++){
            b[i] = rdf.readByte() ; // 讀取一個字節(jié)
        }
        name = new String(b) ;  // 將讀取出來的byte數(shù)組變?yōu)樽址?        age = rdf.readInt() ;   // 讀取數(shù)字
        System.out.println("第一個人的信息 --> 姓名:" + name + ";年齡:" + age) ;
        rdf.skipBytes(12) ; // 跳過第二個人的信息
        for(int i=0;i<b.length;i++){
            b[i] = rdf.readByte() ; // 讀取一個字節(jié)
        }
        name = new String(b) ;  // 將讀取出來的byte數(shù)組變?yōu)樽址?        age = rdf.readInt() ;   // 讀取數(shù)字
        System.out.println("第三個人的信息 --> 姓名:" + name + ";年齡:" + age) ;
        rdf.close() ;               // 關(guān)閉
    }
};

結(jié)果如下:

result.png

在Java程序中所有的數(shù)據(jù)都是以的方式進行傳輸或保存的,程序需要數(shù)據(jù)的時候要使用輸入流讀取數(shù)據(jù),而當程序需要將一些數(shù)據(jù)保存起來的時候,就要使用輸出流完成。程序中的輸入輸出都是以流的形式保存的,流中保存的實際上全都是字節(jié)文件。流涉及的領(lǐng)域很廣:標準輸入輸出,文件的操作,網(wǎng)絡(luò)上的數(shù)據(jù)流,字符串流,對象流,zip文件流等等。

Stream.png

流具有方向性,至于是輸入流還是輸出流則是一個相對的概念,一般以程序為參考,如果數(shù)據(jù)的流向是程序至設(shè)備,我們成為輸出流,反之我們稱為輸入流。
可以將流想象成一個“水流管道”,水流就在這管道中形成了,自然就出現(xiàn)了方向的概念。

Information.jpg

先上一個Java IO流類層次圖,如前所述,一個流被定義為一個數(shù)據(jù)序列。輸入流用于從源讀取數(shù)據(jù),輸出流用于向目標寫數(shù)據(jù):

JavaIO流類層次圖.png

是不是被嚇到了?沒關(guān)系,我們將通過一個個例子來學(xué)習這些功能。

IO流分類

1.按操作數(shù)據(jù)類型分:字符流和字節(jié)流

編碼與解碼:編碼就是把字符轉(zhuǎn)換為字節(jié),而解碼是把字節(jié)重新組合成字符。
如果編碼和解碼過程使用不同的編碼方式那么就出現(xiàn)了亂碼。

  • GBK 編碼中,中文字符占 2 個字節(jié),英文字符占 1 個字節(jié);
  • UTF-8 編碼中,中文字符占 3 個字節(jié),英文字符占 1 個字節(jié);
  • UTF-16be 編碼中,中文字符和英文字符都占 2 個字節(jié)。

UTF-16be 中的 be 指的是 Big Endian,也就是大端。相應(yīng)地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
Java 使用雙字節(jié)編碼 UTF-16be,這不是指 Java 只支持這一種編碼方式,而是說 char 這種類型使用 UTF-16be 進行編碼。char 類型占 16 位,也就是兩個字節(jié),Java 使用這種雙字節(jié)編碼是為了讓一個中文或者一個英文都能使用一個 char 來存儲。

字符流:Java中的字符流處理的最基本的單元是2字節(jié)的Unicode碼元(char),它通常用來處理文本數(shù)據(jù),如字符、字符數(shù)組或字符串等。所謂Unicode碼元,也就是一個Unicode代碼單元,范圍是0x0000~0xFFFF。在以上范圍內(nèi)的每個數(shù)字都與一個字符相對應(yīng),Java中的String類型默認就把字符以Unicode規(guī)則編碼而后存儲在內(nèi)存中。然而與存儲在內(nèi)存中不同,存儲在磁盤上的數(shù)據(jù)通常有著各種各樣的編碼方式。使用不同的編碼方式,相同的字符會有不同的二進制表示。實際上字符流是這樣工作的:

  • 輸出字符流:把要寫入文件的字符序列(實際上是Unicode碼元序列)轉(zhuǎn)為指定編碼方式下的字節(jié)序列,然后再寫入到文件中。
  • 輸入字符流:把要讀取的字節(jié)序列按指定編碼方式解碼為相應(yīng)字符序列(實際上是Unicode碼元序列從)從而可以存在內(nèi)存中。

也就是說,所有的文件在硬盤或在傳輸時都是以字節(jié)的方式進行的,包括圖片等都是按字節(jié)的方式存儲的,而字符是只有在內(nèi)存中才會形成。

字節(jié)流:Java中的字節(jié)流處理的最基本單位為單個字節(jié)(byte),它通常用來處理二進制數(shù)據(jù),如果要得到字節(jié)對應(yīng)的字符需要強制類型轉(zhuǎn)換。

兩者比較:
1.字符流是由Java虛擬機將字節(jié)轉(zhuǎn)化為2個字節(jié)的Unicode字符為單位的字符而成的,所以它對多國語言支持性較好,如果要操作中文數(shù)據(jù)等,用字符流。
2.字符流只用來處理文本數(shù)據(jù),字節(jié)流還可以用來處理媒體數(shù)據(jù),如視頻、音頻、圖片等。
3.字符流的兩個抽象基類為Reader和Writer,字節(jié)流的兩個抽象基類為InputStream和OutputStream。它們的具體子類名以基類名為后綴進行擴展。
4.字節(jié)流在操作的時候不會用到緩沖區(qū)(內(nèi)存),是直接對文件本身操作的,而字符流在操作的時候使用緩沖區(qū)。

Compare.jpg

以向一個文件輸出"Hello world!"為例,我們分別使用字節(jié)流和字符流進行輸出,且在使用完之后都不關(guān)閉流。

使用字節(jié)流不關(guān)閉執(zhí)行:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;


public class IOPractice {

    public static void main(String[] args) throws IOException {
        // 第1步:使用File類找到一個文件    
             File f = new File("/home/xiejunyu/"+
             "桌面/text.txt");   
        // 第2步:通過子類實例化父類對象     
             OutputStream out = new FileOutputStream(f); 
        // 通過對象多態(tài)性進行實例化    
        // 第3步:進行寫操作    
             String str = "Hello World!";      
        // 準備一個字符串    
             byte b[] = str.getBytes();          
        // 字符串轉(zhuǎn)byte數(shù)組    
             out.write(b);                      
        // 將內(nèi)容輸出    
         // 第4步:關(guān)閉輸出流    
            // out.close();                  
        // 此時沒有關(guān)閉    
                }    
}
1.png

此時沒有關(guān)閉字節(jié)流操作,但是文件中也依然存在了輸出的內(nèi)容,證明字節(jié)流是直接操作文件本身的。

使用字符流不關(guān)閉執(zhí)行:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;


public class IOPractice {

    public static void main(String[] args) throws IOException {
         // 第1步:使用File類找到一個文件    
        File f = new File("/home/xiejunyu/桌面/test.txt");
        // 第2步:通過子類實例化父類對象            
        Writer  out = new FileWriter(f);            
        // 第3步:進行寫操作    
        String str = "Hello World!";      
        // 準備一個字符串    
        out.write(str);                    
        // 將內(nèi)容輸出    
        // 第4步:關(guān)閉輸出流    
        // out.close();
        // 此時沒有關(guān)閉    
    }    
}
2.png

程序運行后會發(fā)現(xiàn)文件中沒有任何內(nèi)容,這是因為字符流操作時使用了緩沖區(qū),而在關(guān)閉字符流時會強制性地將緩沖區(qū)中的內(nèi)容進行輸出,但是如果程序沒有關(guān)閉字符流,緩沖區(qū)中的內(nèi)容是無法輸出的,所以得出結(jié)論:字符流使用了緩沖區(qū),而字節(jié)流沒有使用緩沖區(qū)。如果想讓緩沖區(qū)中的內(nèi)容輸出,要么關(guān)閉流強制刷新緩沖區(qū),要么調(diào)用flush方法沖刷緩沖區(qū)。可以簡單地把緩沖區(qū)理解為一段特殊的內(nèi)存。某些情況下,如果一個程序頻繁地操作一個資源(如文件或數(shù)據(jù)庫),則性能會很低,此時為了提升性能,就可以將一部分數(shù)據(jù)暫時讀入到內(nèi)存的一塊區(qū)域之中,以后直接從此區(qū)域中讀取數(shù)據(jù)即可,因為讀取內(nèi)存速度會比較快,這樣可以提升程序的性能。
在字符流的操作中,所有的字符都是在內(nèi)存中形成的,在輸出前會將所有的內(nèi)容暫時保存在內(nèi)存之中,所以使用了緩沖區(qū)暫存數(shù)據(jù)。

建議:
1.雖然不關(guān)閉字節(jié)流不影響數(shù)據(jù)的輸出,且后續(xù)JVM會自動回收這部分內(nèi)存,但還是建議在使用完任何流對象之后關(guān)閉流。
2.使用流對象都要聲明或拋出IOException
3.在創(chuàng)建一個文件時,如果目錄下有同名文件將被覆蓋
4.在寫文件時,如果文件不存在,會在創(chuàng)建輸出流對象并綁定文件時自動創(chuàng)建文件,不必使用File的exists方法提前檢測
4.在讀取文件時,必須使用File的exists方法提前檢測來保證該文件已存在,否則拋出FileNotFoundException

2.按流向分:輸入流和輸出流

輸入流:程序從輸入流讀取數(shù)據(jù)源。數(shù)據(jù)源包括外界(鍵盤、文件、網(wǎng)絡(luò)等),即是將數(shù)據(jù)源讀入到程序的通信通道。輸入流主要包括兩個抽象基類:InputStream(字節(jié)輸入流)和Reader(字符輸入流)及其擴展的具體子類。
輸出流:程序向輸出流寫入數(shù)據(jù)。將程序中的數(shù)據(jù)輸出到外界(顯示器、打印機、文件、網(wǎng)絡(luò)等)的通信通道。輸出流主要包括兩個抽象基類:OutputStream(字節(jié)輸出流)和Writer(字符輸出流)及其擴展的具體子類。

3.按功能分:節(jié)點流和處理流

按照流是否直接與特定的地方(如磁盤、內(nèi)存、設(shè)備等)相連,分為節(jié)點流和處理流兩類。
節(jié)點流:程序用于直接操作目標設(shè)備所對應(yīng)的類叫節(jié)點流。(低級流)
處理流:程序通過一個間接流類去調(diào)用節(jié)點流類,以達到更加靈活方便地讀寫各種類型的數(shù)據(jù),這個間接流類就是處理流。處理流可以看成是對已存在的流進行連接和封裝的流。(高級流)

注意:在使用到處理流對流進行連接和封裝時,讀寫完畢只需關(guān)閉處理流,不用關(guān)閉節(jié)點流。處理流關(guān)閉的時候,會調(diào)用其處理的節(jié)點流的關(guān)閉方法。如果將節(jié)點流關(guān)閉以后再關(guān)閉處理流,會拋出IO異常。

(1) 節(jié)點流
節(jié)點流.png
  • File 文件流。對文件進行讀、寫操作:FileReader、FileWriter、FileInputStream、FileOutputStream。
  • Memory 流。
    向內(nèi)存數(shù)組讀寫數(shù)據(jù): CharArrayReader與 CharArrayWriter、ByteArrayInputStream與ByteArrayOutputStream。
    向內(nèi)存字符串讀寫數(shù)據(jù):StringReader、StringWriter、StringBufferInputStream。
  • Pipe管道流:實現(xiàn)管道的輸入和輸出(進程間通信): PipedReader與PipedWriter、PipedInputStream與PipedOutputStream。
節(jié)點流示意圖.png
(1) 處理流
處理流.png
  • Buffering緩沖流:在讀入或?qū)懗鰰r,對數(shù)據(jù)進行緩存,以減少I/O的次數(shù):BufferedReader與BufferedWriter、BufferedInputStream與BufferedOutputStream。
  • Filtering 濾流:在數(shù)據(jù)進行讀或?qū)憰r進行過濾:FilterReader與FilterWriter、FilterInputStream與FilterOutputStream。
  • Converting between Bytes and Characters 轉(zhuǎn)換流:按照一定的編碼/解碼標準將字節(jié)流轉(zhuǎn)換為字符流,或進行反向轉(zhuǎn)換(Stream到Reader):InputStreamReader、OutputStreamWriter。
  • Object Serialization 對象流 :ObjectInputStream、ObjectOutputStream。
  • DataConversion數(shù)據(jù)流:按基本數(shù)據(jù)類型讀、寫(處理的數(shù)據(jù)是Java的基本類型):DataInputStream、DataOutputStream 。
  • Counting計數(shù)流:在讀入數(shù)據(jù)時對行記數(shù) :LineNumberReader、LineNumberInputStream。
  • Peeking Ahead預(yù)讀流: 通過緩存機制,進行預(yù)讀 :PushbackReader、PushbackInputStream。
  • Printing打印流: 包含方便的打印方法 :PrintWriter、PrintStream。
處理流示意圖.png

讀取控制臺輸入

在Java中,從控制臺輸入有三種方法:

1.使用標準輸入流對象System.in

System.in是System中內(nèi)置的InputStream類對象,它的read方法一次只讀入一個字節(jié)數(shù)據(jù),返回0 ~ 255的一個byte值,一般用來讀取一個字符,需要強制類型轉(zhuǎn)換為char類型,而我們通常要取得一個字符串或一組數(shù)字,故這種方法不常用。下面給出這種方法的一個例子:

public class CharTest{
public static void main(String[] args) {
         try{   
         System.out.print("Enter a Char:");   
         char i = (char)System.in.read();   
         System.out.println("Yout Enter Char is:" + i);           }   
         catch(IOException e){   
            e.printStackTrace();   
         }   
    }
}

使用這種方法必須提供try-catch塊或者在main方法首部聲明IOException異常,因為System.in是一個流對象

2.使用Scanner類

Scanner類功能十分強大,可以讀入字符串、整數(shù)、浮點數(shù)、布爾類型值等等。下面是例子:

public class ScannerTest{
public static void main(String[] args){
    Scanner sc = new Scanner(System.in);   
    System.out.println("ScannerTest, Please Enter Name:");   
    String name = sc.nextLine();  //讀取字符串型輸入   
    System.out.println("ScannerTest, Please Enter Age:");   
    int age = sc.nextInt();    //讀取整型輸入   
    System.out.println("ScannerTest, Please Enter Salary:");   
    float salary = sc.nextFloat(); //讀取float型輸入   
    System.out.println("Your Information is as below:");   
    System.out.println("Name:" + name +"\n" + "Age:"+age 
    + "\n"+"Salary:"+salary);   
    }
 }   

注意:
1.用nextXXX()讀入XXX類型的數(shù)據(jù),XXX可以是除了char外的所有基本數(shù)據(jù)類型,還可以是BigInteger或BigDecimal,其中凡是整型類型的數(shù)據(jù)還可以指定radix(進制),可以用next()和nextLine()讀取一個字符串或一行字符
2.next()和nextLine()的區(qū)別:
next()

  • 一定要讀取到有效字符后才可以結(jié)束輸入。
  • 對輸入有效字符之前遇到的空白,next() 方法會自動將其去掉。
  • 只有輸入有效字符后才將其后面輸入的空白作為分隔符或者結(jié)束符。
  • next() 不能得到帶有空格的字符串,除非用useDelimeter方法修改分隔符。

nextLine()

  • 以Enter為結(jié)束符,也就是說 nextLine()方法返回的是輸入回車之前的所有字符。
  • 可以獲得空白。

3.可以用循環(huán)配合hasNextXXX方法判斷輸入是否繼續(xù)
4.Scanner類沒有直接提供讀取一個字符的方法,如果要讀取一個字符,有三種方法,一是讀入一個字符串后取字符串的第一個字符,二是使用System.in的read方法,三是使用字符流讀入

更多Scanner的用法之前已經(jīng)在Java學(xué)習總結(jié)之Java基本程序設(shè)計結(jié)構(gòu)中總結(jié)過了,不再贅述。

3.使用BufferedReader對象

可以把 System.in 包裝在一個 BufferedReader 對象中來創(chuàng)建一個字符流
下面是創(chuàng)建 BufferedReader 的基本語法:

BufferedReader br = new BufferedReader(new 
                      InputStreamReader(System.in));

其中,System.in是一個InputStream對象(字節(jié)流),使用InputStreamReader作為橋梁,將字節(jié)流轉(zhuǎn)換為字符流,然后再使用BufferedReader進行進一步包裝。
BufferedReader 對象創(chuàng)建后,我們便可以使用 read() 方法從控制臺讀取一個字符(讀入一個用0~65535之間的整數(shù)表示的字符,需要強制類型轉(zhuǎn)換為char類型,如果已到達流末尾,則返回 -1),或者用 readLine() 方法讀取一個字符串。下面是例子:

public static void main(String[] args){
//必須要處理java.io.IOException異常
  BufferedReader br = new BufferedReader(new InputStreamReader
  (System.in ));
  //java.io.InputStreamReader繼承了Reader類
  String read = null;
  System.out.print("輸入數(shù)據(jù):");
  try {
   read = br.readLine();
  } catch (IOException e) {
   e.printStackTrace();
  }
  System.out.println("輸入數(shù)據(jù):"+read);
 }

下面的程序示范了用 read() 方法從控制臺不斷讀取字符直到用戶輸入 "q"。

// 使用 BufferedReader 在控制臺讀取字符
 
import java.io.*;
 
public class BRRead {
  public static void main(String args[]) throws IOException
  {
    char c;
    // 使用 System.in 創(chuàng)建 BufferedReader 
    BufferedReader br = new BufferedReader(new 
                       InputStreamReader(System.in));
    System.out.println("輸入字符, 按下 'q' 鍵退出。");
    // 讀取字符
    do {
       c = (char) br.read();
       System.out.println(c);
    } while(c != 'q');
  }
}

下面的程序讀取和顯示字符行直到你輸入了單詞"end"。

// 使用 BufferedReader 在控制臺讀取字符
import java.io.*;
public class BRReadLines {
  public static void main(String args[]) throws IOException
  {
    // 使用 System.in 創(chuàng)建 BufferedReader 
    BufferedReader br = new BufferedReader(new
                            InputStreamReader(System.in));
    String str;
    System.out.println("Enter lines of text.");
    System.out.println("Enter 'end' to quit.");
    do {
       str = br.readLine();
       System.out.println(str);
    } while(!str.equals("end"));
  }
}

在ACM等算法競賽中,我們常常也會使用Java,在輸入數(shù)據(jù)時有以下幾點注意:
1.hasXXX等價于C++中讀到文件末尾(EOF)
2.使用BufferedReader輸入會比Scanner輸入快十倍左右!

控制臺輸出

控制臺的輸出由 print() 和 println() 完成。這些方法都由類 PrintStream 定義,System.out 是該類的一個對象。
PrintStream 繼承了 OutputStream類,并且實現(xiàn)了方法 write()。這樣,write() 也可以用來往控制臺寫操作。
PrintStream 定義 write() 的最簡單格式如下所示:
void write(int byteval)該方法將 byteval 的低八位字節(jié)寫到流中,即System.out的write方法一次只能寫一個字節(jié)(類比System.in的read方法一次只能讀取一個字節(jié))。
下面的例子用 write() 把字符 "A" 和緊跟著的換行符輸出到屏幕:

import java.io.*;
 
// 演示 System.out.write().
public class WriteDemo {
   public static void main(String args[]) {
      int b; 
      b = 'A';//向上類型轉(zhuǎn)換
      System.out.write(b);
      System.out.write('\n');
   }
}

注意:write() 方法不經(jīng)常使用,因為 print() 和 println() 方法用起來更為方便。

字節(jié)流(OutputStream、InputStream)

字節(jié)流主要是操作byte類型的數(shù)據(jù),以byte數(shù)組為準,主要操作類是OutputStream、InputStream。
由于文件讀寫最為常見,我們先討論兩個重要的字節(jié)流 FileInputStream(文件輸入流) 和 FileOutputStream(文件輸出流),分別是抽象類InputStream和OutputStream的具體子類:

FileInputStream
該流用于從文件讀取數(shù)據(jù),它的對象可以用關(guān)鍵字 new 來創(chuàng)建。
有多種構(gòu)造方法可用來創(chuàng)建對象。
可以使用字符串類型的文件名來創(chuàng)建一個輸入流對象來讀取文件:

InputStream f = new FileInputStream("C:/java/hello");

也可以使用一個文件對象來創(chuàng)建一個輸入流對象來讀取文件。我們首先得使用 File() 方法來創(chuàng)建一個文件對象:

File f = new File("C:/java/hello");
InputStream in = new FileInputStream(f);

創(chuàng)建了InputStream對象,就可以使用下面的方法來讀取流或者進行其他的流操作。


InputStream.png

下面是一個例子:

public static void main(String[] args) throws IOException{
    InputStream f  = new FileInputStream
    ("/home/xiejunyu/桌面/test.txt");
    int c = 0;
    while((c =  f.read()) != -1) 
    //這里也可以先用available方法得到可讀的字節(jié)數(shù)
    System.out.println((char)c);
}

當我們需要創(chuàng)建一個byte[]來保存讀取的字節(jié)時,如果數(shù)組太小,無法完整讀入數(shù)據(jù),如果太大又會造成內(nèi)存浪費。可以使用File類的length方法得到文件的數(shù)據(jù)字節(jié)數(shù),從而有效確定byte數(shù)組的大小。

public static void main(String[] args) {
        // 創(chuàng)建一個FileInputStream對象
        try {
            FileInputStream fis = new FileInputStream("/home/xiejunyu/桌面/test.txt");
            byte[] b=new byte[100];
            fis.read(b,0,5); 
            /*把字節(jié)從文件讀入b數(shù)組,從b數(shù)組的0位置開始存放,
            讀取5個字節(jié)*/
            System.out.println(new String(b));
            fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

注意: 每調(diào)用一次read方法,當前讀取在文件中的位置就會向后移動一個字節(jié)或者移動byte[]的長度(read的兩個重載方法),已經(jīng)到文件末尾會返回-1,可以通過read方法返回-1判斷是否讀到文件末尾,也可以使用available方法返回下一次可以不受阻塞讀取的字節(jié)數(shù)來讀取。FileInputStream不支持mark和reset方法進行重復(fù)讀取。BufferedInputStream支持此操作。

FileOutputStream
該類用來創(chuàng)建一個文件并向文件中寫數(shù)據(jù)。
如果該流在打開文件進行輸出前,目標文件不存在,那么該流會創(chuàng)建該文件。
有兩個構(gòu)造方法可以用來創(chuàng)建 FileOutputStream 對象。
使用字符串類型的文件名來創(chuàng)建一個輸出流對象:

OutputStream f = new FileOutputStream("C:/java/hello")

也可以使用一個文件對象來創(chuàng)建一個輸出流來寫文件。我們首先得使用File()方法來創(chuàng)建一個文件對象:

File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f);

之前的所有操作中,如果重新執(zhí)行程序,則肯定會覆蓋文件中的已有內(nèi)容,那么此時就可以通過FileOutputStream向文件中追加內(nèi)容,F(xiàn)ileOutputStream的另外一個構(gòu)造方法:

public FileOutputStream(File file,boolean append) 

在構(gòu)造方法中,如果將append的值設(shè)置為true,則表示在文件的末尾追加內(nèi)容。程序代碼如下:

File f = new File("C:/java/hello");
OutputStream f = new FileOutputStream(f,true);

創(chuàng)建OutputStream 對象完成后,就可以使用下面的方法來寫入流或者進行其他的流操作。

FileOutputStream.png

當有一個字符串時,可以用getBytes方法轉(zhuǎn)為byte數(shù)組用于字節(jié)流的輸出。

下面是一個演示 InputStream 和 OutputStream 用法的例子:

import java.io.*;
 
public class FileStreamTest{
  public static void main(String args[]){
    try{
      byte bWrite[] = "ABC".getBytes();
      OutputStream os = new FileOutputStream("/home/xiejunyu/桌面/test.txt");
      for(int x=0; x < bWrite.length ; x++){
      os.write(bWrite[x] ); // writes the bytes
    }
    os.close();
 
    InputStream is = new FileInputStream("/home/xiejunyu/桌面/test.txt");
    int size = is.available();
 
    for(int i=0; i< size; i++){
      System.out.print((char)is.read() + "  ");
    }
      is.close();
    }catch(IOException e){
      System.out.print("Exception");
    }  
  }
}

上面的程序首先創(chuàng)建文件test.txt,并把給定的數(shù)字以二進制形式寫進該文件,同時輸出到控制臺上。
以上代碼由于是二進制寫入,可能存在亂碼,你可以使用以下代碼實例來解決亂碼問題:

import java.io.*;
 
public class fileStreamTest2{
  public static void main(String[] args) throws IOException {
    
    File f = new File("a.txt");
    FileOutputStream fop = new FileOutputStream(f);
    // 構(gòu)建FileOutputStream對象,文件不存在會自動新建;如果存在會覆蓋原文件
    
    OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
    // 構(gòu)建OutputStreamWriter對象,參數(shù)可以指定編碼,默認為操作系統(tǒng)默認編碼,windows上是gbk
    
    writer.append("中文輸入");
    // 寫入到緩沖區(qū)
    
    writer.append("\r\n");
    //換行
    
    writer.append("English");
    // 刷新緩沖區(qū),寫入到文件,如果下面已經(jīng)沒有寫入的內(nèi)容了,直接close也會寫入
    
    writer.close();
    //關(guān)閉寫入流,同時會把緩沖區(qū)內(nèi)容寫入文件,所以上面的注釋掉
    
    fop.close();
    // 關(guān)閉輸出流,釋放系統(tǒng)資源
 
    FileInputStream fip = new FileInputStream(f);
    // 構(gòu)建FileInputStream對象
    
    InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
    // 構(gòu)建InputStreamReader對象,編碼與寫入相同
 
    StringBuffer sb = new StringBuffer();
    while (reader.ready()) {
      sb.append((char) reader.read());
      // 轉(zhuǎn)成char加到StringBuffer對象中
    }
    System.out.println(sb.toString());
    reader.close();
    // 關(guān)閉讀取流
    
    fip.close();
    // 關(guān)閉輸入流,釋放系統(tǒng)資源
 
  }
}

以上例子證明:在對多國語言的支持上,字符流表現(xiàn)更優(yōu),此時應(yīng)使用字符流而不是字節(jié)流。

還可以用InputStream和OutputStream配合進行文件的復(fù)制,即讀取原件數(shù)據(jù),寫入副本文件。
復(fù)制有兩種實現(xiàn)方式:
實現(xiàn)一:將源文件中的內(nèi)容全部讀取進來,之后一次性的寫入到目標文件
實現(xiàn)二:邊讀邊寫

在實際開發(fā)中建議使用邊讀邊寫的方式,代碼如下:

public static void main(String[] args) {
        // 文件拷貝
        try {
            FileInputStream fis=new FileInputStream("happy.gif");
            FileOutputStream fos=new FileOutputStream("happycopy.gif");
            int n=0;
            byte[] b=new byte[1024];
            while((n=fis.read(b))!=-1){ 
            /*循環(huán)讀取,每次1024個字節(jié),最后一次可能不滿1024。
            后面的字節(jié)覆蓋前面的字節(jié),不必擔心數(shù)組溢出。*/
                fos.write(b,0,n); //n是實際讀取到的字節(jié)數(shù),如果寫fos.write(b),會造成最后一次數(shù)組未滿的情況也寫1024個字節(jié),從而造成副本比原件略大
            }
            fis.close();
            fos.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }
    }

實際上邊讀邊寫也分為三種方式:
1.批量拷貝(循環(huán)讀取,每次讀入一個byte數(shù)組)
2.緩沖拷貝(使用緩沖流)
3.批量+緩沖拷貝(循環(huán)批量讀取到字節(jié)數(shù)組中,然后使用緩沖輸出流寫入到文件)

第三種方式是最快的。

注意:InputStream的int read()方法讀取一個字節(jié),并用這個字節(jié)填充整型的低八位并返回,OutputStream的void write(int x)寫入x的低八位,如果要寫入一個int,需要移位并寫4次。讀寫基本數(shù)據(jù)類型建議使用DataInputStream和DataOutputStream。

后續(xù)內(nèi)容見 Java學(xué)習總結(jié)之Java IO系統(tǒng)(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內(nèi)容