輸入和輸出是所有應用中所必需的組成部分,通過IO可以讀取輸入資料以及存儲資料到外部裝置上。Java中的輸入和輸出是通過java.io來支援的。下面是本人在學習中的歸納和體會。
1. File類和檔案過濾器
顧名思義,File類中是有關檔案的操作。這裡必須明确,檔案還包括目錄。通過檔案或目錄路徑的字元串作為參數,根據傳入的是目錄路徑還是檔案名路徑可以分别初始化不同的File對象。在File對象中封裝了對檔案的操作,例如建立檔案,删除檔案和重命名等方法。但是它并不能通路檔案本身的内容,這需要輸入輸出流來完成。下面是有關File類的代碼。
- public class FileTest
- {
- public static void main(String[] args) throws IOException
- {
- //以目前路徑來建立一個File對象
- File file = new File(".");
- //直接擷取檔案名,輸出一點
- System.out.println(file.getName());
- //擷取相對路徑的父路徑可能出錯,下面代碼輸出null
- System.out.println(file.getParent());
- //擷取絕對路徑
- System.out.println(file.getAbsoluteFile());
- //擷取上一級路徑
- System.out.println(file.getAbsoluteFile().getParent());
- //在目前路徑下建立一個臨時檔案
- File tmpFile = File.createTempFile("aaa", ".txt", file);
- //指定當JVM退出時删除該檔案
- tmpFile.deleteOnExit();
- //以系統目前時間作為新檔案名來建立新檔案
- File newFile = new File(System.currentTimeMillis() + "");
- System.out.println("newFile對象是否存在:" + newFile.exists());
- //以指定newFile對象來建立一個檔案
- newFile.createNewFile();
- //以newFile對象來建立一個目錄,因為newFile已經存在,
- //是以下面方法傳回false,即無法建立該目錄
- newFile.mkdir();
- //使用list方法來列出目前路徑下的所有檔案和路徑
- String[] fileList = file.list();
- System.out.println("======目前路徑下所有檔案和路徑如下=====");
- for (String fileName : fileList)
- {
- System.out.println(fileName);
- }
- //listRoots靜态方法列出所有的磁盤根路徑。
- File[] roots = File.listRoots();
- System.out.println("======系統所有根路徑如下=====");
- for (File root : roots)
- {
- System.out.println(root);
- }
- }
- }
|
在上面的代碼中,使用list()方法可以傳回目錄下所有檔案的路徑和名稱。使用檔案過濾器,可以根據自己的需要選擇傳回的檔案類型。通過一個實作了FilenameFilter接口的類對象作為list()方法的參數,就可以實作檔案過濾的作用。過濾的規則在重寫FilenameFilter中的accept()方法中定義。代碼如下:
- public class FilenameFilterTest
- {
- public static void main(String[] args)
- {
- File file = new File(".");
- String[] nameList = file.list(new MyFilenameFilter());
- for (String name : nameList)
- {
- System.out.println(name);
- }
- }
- }
- //實作自己的FilenameFilter實作類
- class MyFilenameFilter implements FilenameFilter
- {
- public boolean accept(File dir, String name)
- {
- //如果檔案名以.java結尾,或者檔案對應一個路徑,傳回true
- return name.endsWith(".java")
- || new File(name).isDirectory();
- }
- }
|
2. 輸入輸出流
在Java中,從不同角度定義了輸入輸出流的分類。以記憶體作為流的方向基點,可将其分為輸入流和輸出流,流向記憶體的成為輸入流,流出記憶體的成為輸出流。從資料操作的最小單元來分類,可将其分為位元組流和字元流。根據抽象程度分類,可将其分為節點流和處理流,處理流是連接配接到實際實體節點的節點流的封裝,這樣既可以不必去關注節點的來源(檔案或者數組),用統一的方法去操作,又可以使用更加方面的方法來實作操作。
在學習中,本人對于輸入輸出流的總結歸納:
A. 位元組流和字元流其實并沒有本質的差別,無非是它們二者的操作單元不同。位元組流的基類為InputStream和OutputStream,字元流的基類為Reader和Writer。它們所提供的讀寫方法一樣,差別在于參數,需要對應流類型。
B. 輸入流中的read系列方法是從輸入流中讀取資料的操作。空參數将傳回所讀取的位元組(字元)。read方法中可以傳入數組參數,數組的類型必須和流的資料類型相比對,相當于一個竹筒,一次可讀取最多等于數組容量的資料,并将所讀資料裝進數組中。
C. 輸出流中的write系列方法是向輸出流中寫入資料的操作。方法中的參數可以是整型或字元型變量,也可以傳入數組參數,數組的類型必須和流的資料類型相比對,相當于一個竹筒,一次可寫入最多等于數組容量的資料。特别地,字元型輸出流的write方法可以傳入字元串。
D. InputStream和OutputStream,Reader和Writer這四個類作為Java中輸入輸出流的抽象基類,并不能直接被初始化使用。Java中提供了一些繼承了它們的類,用于實作具體的某種流操作。如:
a.File字首系列的類(如FileInputStream),用于對檔案類進行流操作,使用檔案名作為構造函數的參數;示例代碼如下:
輸出類:
- public class FileOutputStreamTest
- {
- public static void main(String[] args) throws IOException
- {
- FileInputStream fis = null;
- FileOutputStream fos = null;
- try
- {
- //建立位元組輸入流
- fis = new FileInputStream("FileOutputStreamTest.java");
- //建立位元組輸入流
- fos = new FileOutputStream("newFile.txt");
- byte[] bbuf = new byte[32];
- int hasRead = 0;
- //循環從輸入流中取出資料
- while ((hasRead = fis.read(bbuf)) > 0 )
- {
- //每讀取一次,即寫入檔案輸出流,讀了多少,就寫多少。
- fos.write(bbuf , 0 , hasRead);
- }
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- finally
- {
- //使用finally塊來關閉檔案輸入流
- if (fis != null)
- {
- fis.close();
- }
- //使用finally塊來關閉檔案輸出流
- if (fos != null)
- {
- fos.close();
- }
- }
- }
- }
|
輸入類:
- public class FileInputStreamTest
- {
- public static void main(String[] args) throws IOException
- {
- //建立位元組輸入流
- FileInputStream fis = new FileInputStream("FileInputStreamTest.java");
- //建立一個長度為1024的“竹筒”
- byte[] bbuf = new byte[1024];
- //用于儲存實際讀取的位元組數
- int hasRead = 0;
- //使用循環來重複“取水”過程
- while ((hasRead = fis.read(bbuf)) > 0 )
- {
- //取出“竹筒”中水滴(位元組),将位元組數組轉換成字元串輸入!
- System.out.print(new String(bbuf , 0 , hasRead ));
- }
- fis.close();
- }
- }
|
b. 數組字首系列類(如ByteArrayInputSteam),用于對數組進行流操作,使用數組對象作為構造函數的參數;
c. 轉換流InputStreamReader将位元組輸入流轉換為字元輸入流;InputStreamReader将位元組輸出流轉換為字元輸出流,代碼如下:
- public class KeyinTest
- {
- public static void main(String[] args)
- {
- BufferedReader br = null;
- try
- {
- //将Sytem.in對象轉換成Reader對象
- InputStreamReader reader = new InputStreamReader(System.in);
- //将普通Reader包裝成BufferedReader
- br = new BufferedReader(reader);
- String buffer = null;
- //采用循環方式來一行一行的讀取
- while ((buffer = br.readLine()) != null)
- {
- //如果讀取的字元串為"exit",程式退出
- if (buffer.equals("exit"))
- {
- System.exit(1);
- }
- //列印讀取的内容
- System.out.println("輸入内容為:" + buffer);
- }
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- //關閉輸入流
- finally
- {
- try
- {
- br.close();
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- }
- }
- }
|
d. 字元流的子類有String字首(StringReader和StringWriter)系列類可以對字元串進行操作,代碼如下:
- public class StringNodeTest
- {
- public static void main(String[] args)
- {
- String src = "從明天起,做一個幸福的人\n"
- + "喂馬,劈柴,周遊世界\n"
- + "從明天起,關心糧食和蔬菜\n"
- + "我有一所房子,面朝大海,春暖花開\n"
- + "從明天起,和每一個親人通信\n"
- + "告訴他們我的幸福\n";
- StringReader sr = new StringReader(src);
- char[] buffer = new char[32];
- int hasRead = 0;
- try
- {
- //采用循環讀取的通路讀取字元串
- while((hasRead = sr.read(buffer)) > 0)
- {
- System.out.print(new String(buffer ,0 , hasRead));
- }
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- finally
- {
- sr.close();
- }
- //建立StringWriter時,實際上以一個StringBuffer作為輸出節點
- //下面指定的20就是StringBuffer的初始長度
- StringWriter sw = new StringWriter(20);
- //調用StringWriter的方法執行輸出
- sw.write("我遠離了大海,\n");
- sw.write("看不到春暖花開,\n");
- sw.write("我隻有一隻小龜,\n");
- sw.write("一樣可以聞到馥郁花香\n");
- System.out.println("------下面是sw的字元串節點裡的内容------:");
- //使用toString方法傳回StringWriter的字元串節點的内容
- System.out.println(sw.toString());
- }
- }
|
d.推回輸入流PushbackInputStream和PushbackReader帶有一個推回緩沖區,使用unread(數組)方法可以将一個位元組/字元數組推回到推回緩沖區中。推回輸入流也有read方法,使用read方法讀取輸入流時,首先從輸入緩沖區中讀取,讀完之後才從輸入流中讀。代碼如下:
- public class PushbackTest
- {
- public static void main(String[] args)
- {
- PushbackReader pr = null;
- try
- {
- //建立一個PushbackReader對象,指定推回緩沖區的長度為64
- pr = new PushbackReader(new FileReader("PushbackTest.java") , 64);
- char[] buf = new char[32];
- //用以儲存上次讀取的字元串内容
- String lastContent = "";
- int hasRead = 0;
- //循環讀取檔案内容
- while ((hasRead = pr.read(buf)) > 0)
- {
- //将讀取的内容轉換成字元串
- String content = new String(buf , 0 , hasRead);
- int targetIndex = 0;
- //将上次讀取的字元串和本次讀取的字元串拼起來,檢視是否包含目标字元串
- //如果包含目标字元串
- if ((targetIndex = (lastContent + content).indexOf("new PushbackReader")) > 0)
- {
- //将本次内容和上次内容一起推回緩沖區
- pr.unread((lastContent + content).toCharArray());
- //再次讀取指定長度的内容(就是目标字元串之前的内容)
- pr.read(buf , 0 , targetIndex);
- //列印讀取的内容
- System.out.print(new String(buf , 0 ,targetIndex));
- System.exit(0);
- }
- else
- {
- //列印上次讀取的内容
- System.out.print(lastContent);
- //将本次内容設為上次讀取的内容
- lastContent = content;
- }
- }
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- finally
- {
- try
- {
- if (pr != null)
- pr.close();
- }
- catch (IOException ioe)
- {
- ioe.printStackTrace();
- }
- }
- }
- }
|
E. 在Java虛拟機中可以通過exec方法執行其他的應用程式,并傳回Proess對象。利用該對象的getErrorStream、getInputStream和getOutputStream方法可以分别獲得子程序的錯誤流,輸出流和輸入流(方向是以程式角度為基點)。
F. RandomAccessFile類提供對檔案的随機通路,程式可以直接跳轉到檔案的任何地方來讀取資料。它内部提供了與位元組流同法相同的讀寫方法,另外又加入了自由定位檔案記錄指針的方法seek。在讀寫操作時以位元組為機關。在構造函數中還需加入一個mode參數由于制定讀寫權限。
G. 需要注意Scanner對象的使用。Scanner對象用于捕獲輸入,并将輸入内容轉換為字元串。
- //使用System.in建立Scanner對象,用于擷取标準輸入
- Scanner sc = new Scanner(System.in);
- PrintStream ps = new PrintStream(
- new FileOutputStream("out.txt"));
- //增加下面一行将隻把回車作為分隔符
- sc.useDelimiter("\n");
- //判斷是否還有下一個輸入項
- while(sc.hasNext())
- {
- //輸出輸入項
- ps.println("鍵盤輸入的内容是:" + sc.next());
- }
- ps.close();
|
對于初學者來說一個容易忽略的地方是,需要差別節點流和處理流構造方法中參數的意義。節點流中,構造方法參數是要讀取或者輸出地實體節點,是“目的地”或者“始發地”,而處理流的構造函數參數是所要包裝的處理流對象,包裝之後的操作實際上是間接操作節點流,并未對被包裝的節點流本身屬性做修改。采用處理流包裝後,可以不用管節點流的資料類型,而根據處理流的性質傳遞在節點流中不能傳遞的内容,例如Obj;同時還可以增加一些便于操作的方法,比如緩沖區。