java IO流(下)

上一篇文章中總結了java IO流基本流對象操作方法及典型應用示例,本文學習並總結IO包中的其它常用重要對象,這些對象對完整及更加靈活地使用和操作IO流是必不可少的。這些流對象種類較多,不像上一篇中只有字符流、字節流及各自的緩衝區流對象等,學習時可以根據其名稱記住這些流對象各自的特點和主要方法,實際項目使用時可以再查閱API手冊。

File類
將文件或文件夾封裝成對象,方便對文件或文件夾的屬性進行操作。流只能操作數據,File對象可以作爲參數傳遞給流的構造函數。

File類構造函數

可以將已有的和未出現的文件(夾)封裝成File對象,File類構造函數有:
File(File parent, String child), 將路徑名和文件名參數分開傳入,方便單獨操作文件名參數。
File(String parent, String child), 等同於上一種方式。
File(String pathname), pathname可以是相對路徑也可以是絕對路徑。
FIle(URI uri), 將uri轉換成一個抽象路徑名來創建File對象。
上述構造函數中,Windows下路徑分隔符要使用"\\", linux中路徑分隔符是"/", File類提供了跨平臺的文件路徑分隔符File.separator, 是File類的靜態String成員。
File類重寫了toString()方法,打印的是文件對象的路徑名(相對路徑或絕對路徑,看創建的時候是什麼)。

File常用方法

1. 創建
boolean createNewFile(), 在指定位置創建文件,如果該文件已經存在,則不創建,返回false;調用了系統底層方法,會拋出IOException.
static File createTempFile(String perfix, String suffix), 使用指定的前綴和後綴在系統默認臨時文件目錄創建臨時文件。可使用echo %temp%(windows系統)或echo $temp(linux系統)查看默認臨時文件目錄。臨時文件要程序運行時產生,程序結束後可能銷燬,也可能留存(垃圾文件)。
static File createTempFile(String perfix, String suffix, File directory), 在指定目錄下創建臨時文件。
boolean mkdir(), 創建單級文件夾。
boolean mkdirs(), 創建多級文件夾。

2. 刪除
boolean delete(), 刪除失敗時返回false,不拋異常.
void deleteOnExit(), 在程序退出時刪除指定文件,不拋異常。
java刪除文件不進回收站。

3. 判斷
boolean canExecute(), 文件是否可執行,返回true時,可加入Runtime類執行該文件。
boolean compareTo(String pathname), 按字母順序比較文件路徑名。還可能自定義比較器按其它方式比較路徑名。
boolean exists(), 文件是否存在,在使用下面2個方法判斷File對象是文件還是文件夾時必須先判斷文件是否存在,文件不存在時,下面2個方法都返回false.
boolean isFile(), 判斷是不是文件。
boolean isDirectory(), 判斷是不是目錄。
boolean isHidden(), 是否隱藏,系統中有些文件,如windows下system開頭的系統卷標目錄,java無法訪問,需要事先用此方法判斷,不讀取隱藏的文件。
boolean isAbsolute(), 是否是絕對路徑,文件可以不存在,只要封裝時帶盤符,即返回true.

4. 獲取
String getName(), 只返回文件名,沒有路徑名
String getPath(), 返回File對象封裝時指定的路徑名,相對或絕對路徑。
String getParent(), 返回絕對路徑中的父目錄,如果封裝時使用的是相對路徑,且相對路徑中有上一級目錄,則返回該上級目錄,否則返回null.
String getAbsolutePath(), 返回絕對路徑。
File getAbsoluteFile(), 返回絕對路徑封裝成的對象。
long lastModified(), 最後修改時間,毫秒值。
long length(), 返回文件(夾)大小。文件夾大小是0;FileInputStream類中的int available()方法可以使用該方法實現, 不過available()函數只能返回int類型的值, 不能超出int取值範圍。
boolean renameTo(File f), 重命名,可以實現文件移動(剪切).

5. 文件列表
static File[] listRoots(), 列出系統中有效盤符。
String[] list(), 列出一個目錄下所有的文件名和文件夾名稱,包含隱藏文件;如果調用此方法的File對象應該是一個已存在的目錄,如果是不一個文件或不存在的目錄,則返回null,操作返回值時會報空指針異常。
String[] list(FilenameFilter filter), 只返回滿足過濾條件的文件和文件夾。

FilenameFilter是一個接口,接口中只有一個方法,具體應用時可以用內部類方式複寫該方法:
boolean accept(File dir, String name), dir是要過濾的目錄,name代表dir目錄下的文件或文件夾名稱。
list方法根據accept函數的返回值確定是否列出文件(夾)名,只有滿足accept條件的文件(夾)名才被返回。

File[] listFile(), 返回指定目錄下的文件對象和文件夾對象,可以使用前面介紹的方法操作返回的文件(夾)對象,更方便實用。

下面用上面學到的方法實現目錄的複製、目錄層次打印和目錄的刪除:

import java.util.*;
import java.io.*;
public class FileDemo {
        public static void main(String[] args) throws Exception 
        {
            //複製文件/文件夾
            File f=new File("FileZilla Server");//要複製和打印目錄結構的文件
            File desf=new File("copy_FileZilla Server");//目標位置
            copy(f,desf);
            //打印目錄結構
            List<String> list=new ArrayList<String>();
            String listFile="FileZilla Server_dir.txt";
            showDir(f,0,list);
            writeToFile(list,listFile);
            //刪除一個目錄
            removeDir(f);            
        }
        //將一個文件或文件夾拷貝到另一個位置
        public static void copy(File src,File des) throws Exception
        {
            //首先判斷要拷貝的是文件還是文件夾,是文件直接調用copyFile,是文件夾時調用遞歸函數copyDirectory.            
            if(src.exists()&&!src.isHidden()){
                if(src.isDirectory()){
                    des.mkdir();
                    copyDirectory(src,des);
                }else{
                    copyFile(src,des);
                }
            }               
        }
        //使用字節緩衝流完成文件的複製。
        public static void copyFile(File src,File des) throws Exception
        {

            BufferedInputStream bufis=new BufferedInputStream(new FileInputStream(src.getAbsolutePath()));
            BufferedOutputStream bufos=new BufferedOutputStream(new FileOutputStream(des.getAbsolutePath()));
            int by=0;
            while((by=bufis.read())!=-1){
                bufos.write(by);
                bufos.flush();
            }                
        }
        //遞歸複製文件夾
        public static void copyDirectory(File src,File des) throws Exception{
            File[] files=src.listFiles();
            for(File f:files){
                if(f.isFile()){
                    File df=new File(des.getAbsolutePath()+File.separator+f.getName());
                    copyFile(f,df);
                }
                else{                                
                    File newDes=new File(des.getAbsolutePath()+File.separator+f.getName());
                    File newSrc=new File(src.getAbsolutePath()+File.separator+f.getName());
                    newDes.mkdir();
                    copyDirectory(newSrc,newDes);
                }
            }               
        }
        //遞歸刪除一個目錄下的所有文件
        public static void removeDir(File dir){
            if(dir.exists()){
                File[] files=dir.listFiles();
                for(int x=0;x<files.length;x++){
                    if(!files[x].isHidden()&&files[x].isDirectory())
                        removeDir(files[x]);
                    else
                        System.out.println(files[x].toString()+":-file-: "+files[x].delete());
                    System.out.println(dir+":-dir-:"+dir.delete());                       
                }
            }
        }
        //列出一個目錄結構中所有文件和文件夾,帶層次,並保存到一個List集合中
        public static void showDir(File dir, int level,List<String> list) throws IOException
        {
            list.add(getLevel(level)+dir.getName());
            level++;
            File[] files=dir.listFiles();
            for(int x=0;x<files.length;x++){
                if(files[x].isDirectory())
                    showDir(files[x],level,list);
                else
                    list.add(getLevel(level)+files[x].getName());
            }
        }
        //下面這個函數是爲了打印出層次結構。        
        public static String getLevel(int level){
            StringBuilder sb=new StringBuilder();
            sb.append("|--");
            for(int x=0;x<level;x++)
                sb.insert(0,"|  ");
            return sb.toString();
        } 
        //將List集合中的數據保存到硬盤中。
        public static void writeToFile(List<String> list, String listFile) throws IOException
        {
            BufferedWriter bufw=null;
            bufw=new BufferedWriter(new FileWriter(listFile));
            for(String s:list){
                bufw.write(s);
                bufw.newLine();
                bufw.flush();
            }
        }
}

Properties集合
Properties是HashTable的子類,具體Map集合的特點,裏面存儲的鍵值對都是字符串。
Properties是集合中與IO技術相結合的容器,該集合的特點是可用於鍵值對形式的配置文件。軟件配置文件常見的有2種形式,xml文件和鍵值對文件(ini文件、properties文件都是鍵值對)。

Properties構造函數
Properties(), 無參數構造函數,創建一個空屬性集合。
Properties(Properties default), 創建一個帶指定默認值的屬性集合。

Properties常用方法

1. 設置和獲取元素
Object setProperty(String key, String value), 調用的是HashTable的put方法。
String getProperty(String key), 根據鍵獲取值。
set<String> stringPropertyNames(), 返回鍵集合,可遍歷返回的鍵集,調用上一個方法獲取所有鍵的值。

2. Properties存取配置文件
void load(InputStream in), 從輸入流中讀取屬性集合。從1.2版本開始就有了。
void load(Reader r), 從1.6版本開始有的。該方法可以用之前學過的IO流和String字符串知識來自己實現,將是與該方法一致。
void list(PrintStream out), 將屬性集合輸出到指定輸出流上。從1.2版本開始就有了。
void list(PrintWriter out), 從1.6版本開始有的。
void store(PrintStream out, String comments), void store(PrintWriter out, String comments), 將內存中修改的屬性鍵值保存到輸出流中.

Properties基本用法示例:

import java.io.*;
import java.util.Properties;
public class PropertiesDemo {
	public static void main(String[] args) throws IOException
	{
		loadDemo(); 
		myLoad();
	}
	public static void loadDemo() throws IOException
	{
		Properties prop=new Properties();
		FileInputStream fis=new FileInputStream("info.txt");
		prop.load(fis);
		prop.setProperty("lisi", "90");//setProperty()改變的是內存中的值
		FileOutputStream fos=new FileOutputStream("info.txt");
		prop.store(fos, "haha");//將內存中的修改值保存到輸出流中,並自動加上註釋和當前時間。
		prop.list(System.out);
		fis.close();
		fos.close();
	}
	//實現與load()方法相似的功能
	public static void myLoad() throws IOException
	{
		//用一個流與info.txt文件關聯
		BufferedReader bufr=new BufferedReader(new FileReader("info.txt"));
		String line=null;
		Properties prop=new Properties();
		//遍歷讀取一行數據,將該行數據用"="進行分割
		while((line=bufr.readLine())!=null){
			String[] arr=line.split("=");
			//等號左邊作爲鍵,右邊作爲值,存入到Properties集合即可。
			prop.setProperty(arr[0], arr[1]);
		}
		bufr.close();
		prop.list(System.out);
	}
}

下面是Properties操作配置文件的典型例子:

/*
 * 統計程序運行次數。
 */
import java.io.*;
import java.util.Properties;
public class RunCount{
	public static void main(String[] args) throws IOException
	{
		Properties prop=new Properties();
		File file=new File("count.ini");
		if(!file.exists())//判斷文件是否存在,只創建一次
			file.createNewFile();
		FileInputStream fis=new FileInputStream(file);
		prop.load(fis);
		int count=0;
		String value=prop.getProperty("time");
		if(value!=null){
			count=Integer.parseInt(value);
			if(count>=5){
				System.out.println("使用次數已到!");
				return;
			}
			count++;
			prop.setProperty("time", count+"");
			FileOutputStream fos=new FileOutputStream(file);
			prop.store(fos, "");
			fis.close();
			fos.close();			
		}
	}
}
序列流-SequenceInputStream
SequenceInputStream, 是InputStream的子類,沒有對應的序列輸出流。
SequenceInputStream表示其他輸入流的邏輯串聯,可以將多個輸入流順序串連成一個流,只有讀到最後一個流的結束標識時,序列流才結束。
SequenceInputStream可用來實現多個源文件到一個目的文件的複製。

SequenceInputStream構造函數
SequenceInputStream(InputStream s1, InputStream s2), 將2個流拼成一個序列流,先讀取s1, 再讀取s2.
SequenceInputStream(Enumeration<? extends InputStream> e), 傳到多個輸入流的枚舉,可將多個流拼成一個序列流。

下面是一個序列流使用示例:

import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
public class SequenceDemo {
	public static void main(String[] args) throws IOException 
	{
		Vector<FileInputStream> v=new Vector<FileInputStream>();
		v.add(new FileInputStream("1.txt"));
		v.add(new FileInputStream("2.txt"));
		v.add(new FileInputStream("3.txt"));
		Enumeration<FileInputStream> en=v.elements();
		SequenceInputStream sis=new SequenceInputStream(en);
		FileOutputStream fos=new FileOutputStream("4.txt");
		byte[] buf=new byte[1024];
		int len=0;
		while((len=sis.read(buf))!=-1){
			fos.write(buf, 0, len);
		}
		fos.close();
		sis.close();//關閉所有源輸入流。		
	}

}
切割文件
將一個文件切割成體積較小的多個碎片文件。多個碎片文件可使用合併流合成一個文件。
import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
public class SplitFile {
	static int count;
	public static void main(String[] args) throws IOException 
	{
		splitFile();
		merge();
	}
	public static void merge() throws IOException
	{
		ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();
		for(int x=0;x<count;x++)
			al.add(new FileInputStream("C:\\splitfiles\\"+x+".part"));
		//迭代器的匿名內部類中使用,需要用filna修飾。
		final Iterator<FileInputStream> it=al.iterator();
		//使用匿名內部類創建枚舉對象
		Enumeration<FileInputStream> en=new Enumeration<FileInputStream>(){
			public boolean hasMoreElements(){
				return it.hasNext();
			}
			public FileInputStream nextElement(){
				return it.next();
			}
		};
		//創建合併流,將多個碎片文件合成一個文件
		SequenceInputStream sis=new SequenceInputStream(en);
		FileOutputStream fos=new FileOutputStream("C:\\splitfiles\\2.bmp");
		byte[] buf=new byte[1024];
		int len=0;
		while((len=sis.read(buf))!=-1){
			fos.write(buf,0,len);
		}
		fos.close();
		sis.close();		
	}
	public static void splitFile() throws IOException
	{
		FileInputStream fis=new FileInputStream("C:\\1.bmp");
		FileOutputStream fos=null;
		byte[] buf=new byte[1024*1024];
		count=0;
		int len=0;
		//切割文件,每個文件大小不超過1M; 如果要切割電影文件等較大文件,每上碎片文件100M,那需要每存入1M時獲取時輸出流文件的大小 ,在fos累計達到64M時就新創建另一個輸出流
		while((len=fis.read(buf))!=-1){
			fos=new FileOutputStream("C:\\splitfiles\\"+(count++)+".part");
			fos.write(buf, 0, len);
			fos.close();
		}
		fis.close();		
	}
}
對象的序列化-ObjectInputStream和ObjectOutputStream
ObjectInputStream和ObjectOutputStream, 分別是InputStream和OutputStream的子類,可直接操作對象,實現對象的反序列化和序列化(持久化)。
被操作的對象必須實現Serializable接口,否則會報NotSerializableException異常;Serializable接口在IO包中,類實現該接口後才允許序列化和反序列化;Serializable接口中裏面沒有方法,這類接口稱爲標識接口。
實現Serializable接口的類,編譯時會生成UID,此UID標識通常是給編譯器使用的;類被序列化後可以被修改,再編譯時生成的UID會不一樣;類中的每個成員(變量和函數)都有一個數字標識(數字標籤),UID就是根據成員的數字標籤算出來的。

ObjectInputStream和ObjectOutputStream構造方法
ObjectInputStream(), 爲完全重新實現 ObjectInputStream 的子類提供一種方式。
ObjectInputStream(InputStream in), 創建從指定字節流讀取的 ObjectInputStream。
ObjectOutputStream的構造函數形式與ObjectInputStream類似。

讀取和寫入方法
void write(int val), 寫入int數據的低8位。
void writeInt(int val), 寫入32位int數據。類似還有寫入其它7種基本數據類型的write方法。
void writeObject(Object obj), 寫入對象。
ObjectInputStream類中read方法形式與write方法基本相對應。

對象序列化和反序列化的一個簡單示例如下,代碼中也標註了一些注意事項。

class Person implements Serializable
{
    //手動將UID寫死,不用編譯器算出來的值,這樣即使類改變了,之前已經序列化的對象也還能讀取出來。
    public static final long serializableVersionUID=42l;
    int age;
    String name;
    static String country="cn";//靜態成員不能序列化,靜態成員是類成員,是不屬於對象的,
    transient double weight;//被transient修飾的成員也不能序列化。
    Person(String name, int age){
        this.name=name;
        this.age=age;
    }
    public String toString(){
        return this.name+"...."+this.age;
    }
}
public class ObjectStreamdDemo{
    public static void main(String[] args){
        writeObj();
        readObj();//反序列化對象時,對象所屬類的UID必須與該對象序列化時類的UID一致才行,否則會報ClassNotFoundException異常。
    }
    public static void readObj() throws IOException
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("obj.txt"));
        Person p=(Person)ois.readObject();
        System.out.println(p);
        ois.close();
    }
    public static void writeObj() throws IOException
    {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("obj.txt"));//對象一般不會存在txt文件中,而.object文件中
        oos.writeObject(new Person("lisi",30));
        oos.close();
    }
}

管道流-PipedInputStream和PipedOutputStream
PipedInputStream和PipedOutputStream,是字節流基類的子類,這2個流可以直接連接進行寫入和讀取,不需要再通過字節數組這個中轉站。

PipedInputStream和PipedOutputStream連接的2種方式
1. 創建對象時連接,使用帶參數的構造函數
PipedInputStream(PipedOutputStream src), 創建連接到指定管道輸出流的管道輸入流。
PipedOutputStream(PipedInputStream snk), 創建連接到指定管道輸入流的管道輸出流。
2. 使用空參數構造函數創建管道流,並調用connect方法連接
管道輸出流中有void connect(PipedInputStream snk)方法, 管道輸入流中有void connect(PipedOutputStream src)方法。

管道流使用時通常使用多線程, 一個線程寫數據,另一個讀數據,不建議使用單線程;PipedInputStream中的read方法是個阻塞式方法,沒有數據可讀時會自動阻塞,有數據時自動恢復讀取數據。

隨機訪問文件流-RandomAccessFile
直接繼承自Object類,該類的實例對象支持對隨機訪問文件的讀取和寫入,因此是java IO包中的成員。

實際該類對象可以完成讀寫的原理就是內部封裝了字節輸入流和字節輸出流。
對象內部實際封裝了一個大型byte數組,存在指向該隱含數組的光標或索引,稱爲文件指針,該文件指針可以通過getFilePointer方法讀取,並通過seek 方法設置。
輸入操作從文件指針開始讀取字節,並隨着對字節的讀取而前移此文件指針;輸出操作從文件指針開始寫入字節,並隨着對字節的寫入而前移此文件指針。

該類構造函數形式:
RandomAccessFile(File file, String mode)
RandomAccessFile(String name, String mode)
其中mode可取值"r"、“rw"、"rws" 和"rwd", 具體含義如下:
"r" : 以只讀方式打開。調用結果對象的任何 write 方法都將導致拋出IOException.
"rw" : 打開以便讀取和寫入。如果該文件尚不存在,則嘗試創建該文件。
"rws":打開以便讀取和寫入,對於 "rw",還要求對文件的內容或元數據的每個更新都同步寫入到底層存儲設備。
"rwd" : 打開以便讀取和寫入,對於 "rw",還要求對文件內容的每個更新都同步寫入到底層存儲設備.
如果模式爲"r",不會創建文件,只會去讀取一個已存在的文件,如果該文件不存在,則會出現IOException。
如果模式爲"rw",操作的文件不存在時,會自動創建,如果存在則不會覆蓋。

RandomAccessFile中的讀寫方法
int read(),讀取一個字節數據,類型提升爲int返回。
int read(byte[] buf), 將數據讀到一個字節數組中。
int read(byte[] buf , int off, int len), 將數據讀到字節數組的指定位置。
int readInt(), 讀取一個32位int整數,類似地還有讀取其它7種基本數據類型的read方法。
寫入數據的write方法形式與上面read方法形式基本是對應的。

調整文件指針的方法
void seek(long pos), 設置到此文件開頭測量到的文件指針偏移量,在該位置發生下一個讀取或寫入操作。
int skipBytes(int n), 嘗試跳過輸入的 n 個字節以丟棄跳過的字節,只能往下跳,不能往上跳。
這2個方法都會拋IOException.
存入RanddomAcces的數據最好是有規律的,這樣數據比較容易定位和讀取。

RandomAccessFiles對象可以實現數據的分段寫,進而可用多個線程負責各個段的讀寫(多線程下載的原理)。

操作基本數據類型的流對象
DataInputStream和DataOutputStream, 分別是FileInputStream和FileOutputStream的子類,可以直接讀寫8種基本數據類型。

構造函數可以直接接收任何字節流對象:
DataInputStream(InputStream in), DataOutputStream(OutputStream).

讀寫方法:int readInt(), void writeInt(int v)等可以直接讀寫8種基本數據類型,讀數據時必須按寫入數據的順序讀,否則讀出的數據不正確。
void writeUTF(String str),以與機器無關方式使用UTF-8修改版編碼將一個字符串寫入基礎輸出流,以該方式寫入的數據只能用readUTF()讀出,通過使用UTF-8字符集的轉換流也無法讀取。
static String readUTF(), 以UTF-8修改版編碼格式讀取字符串。UTF-8中一個漢字佔3個字節,UTF-8修改版中一個漢字4個字節。

操作數組的流對象
ByteArrayInputStream、ByteArrayOutputStream,用流的思想來操作數組,分別是InputStream和OutputStream的子類。

構造函數形式
ByteArrayInputStream(byte[] buf)
ByteArrayInputStream(byte[] buf, int offset, int length)
字節數組讀取流構造時,需要接收數據源,數據源是一個字節數組。
ByteArrayOutputStream()
ByteArrayOutputStream(int size)
字節數組寫入流構造時,不用定義數據目的,因爲該對象內部已經封裝了可變長度(可自動增長)的字節數組,這就是目的地。

因爲這2個對象都操作數組(讀寫方法可接收的參數與讀取流一致),不調用任何底層方法,不使用系統資源,所以,不用進行close()關閉,即可關閉後流仍可使用

ByteArrayOutputStream類中有一個寫入方法可直接將數據寫到輸出流
void writeTo(OutputStream out), 該方法會拋出IOException,這2個對象中只有該方法會拋異常。

類似地,還有操作字符數組和字符串的流對象分別是:
CharArrayReader和CharArrayWriter, StringReader和StringWriter,原理和使用方法類似字節數組流對象,都是內存中的流。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章