1. 程式人生 > >Java學習之檔案傳輸基礎---Java IO流

Java學習之檔案傳輸基礎---Java IO流

一、檔案的編碼
中文機器上建立的文字檔案只能識別ansi編碼
如果是由其他地方建立的文字檔案 再拷貝出來的則可以識別任意的編碼
UTF-8編碼 漢字佔3個位元組 英文佔一個
gbk編碼 漢字佔2個位元組 英文佔1個
UTF-16be編碼是java中的編碼 漢字和英文都是佔兩個位元組
.getBytes();將字串變成byte型別
integer.toHexString();將位元組流變成16進位制的int型別
用什麼編碼將字串變成位元組流 就要用同樣的編碼才能將其變回去
new String(“dd”,”UTF-8”);可以自己選擇編碼方式 預設則是預設工程屬性中預設的編碼
二、File類的使用
ava.IO.File類表示檔案或目錄,只用於表示檔案或目錄得資訊,不能用於檔案的訪問。
常用的API:
1.建立File物件:File file=new File(String path);注意:File.seperater();獲取系統分隔符,如:”\“.
2.boolean file.exists();是否存在.
3.file.mkdir();或者file.mkdirs();建立目錄或多級目錄。
4.file.isDirectory()或者file.isFile()判斷是否是目錄或者是否是檔案。
5.file.delete();刪除檔案或目錄。
6.file.createNewFile();建立新檔案。
7.file.getName()獲取檔名稱或目錄絕對路徑。
8.file.getAbsolutePath()獲取絕對路徑。
9.file.getParent();獲取父級絕對路徑。
10.file.getSize();獲取檔案大小。
11.file.getFormat();獲取檔案格式名。

public String[] list()返回一個字串陣列,這些字串指定此抽象路徑名錶示的目錄中的檔案和目錄。
exists()方法用於判斷檔案或目錄是否存在
file.list() 返回的是 字串陣列 直接子的名稱,不包含子目錄下的內容
file.listFiles() 返回當前目錄下的所有子目錄和檔案的檔案陣列名稱
如果要遍歷子目錄下的內容就需要構造成File物件做遞迴操作
//throws IOException 迴避了IO的丟擲異常
File[] files = dir.listFiles();//返回的是直接子目錄(檔案)的抽象
if (file!=null && files.length > 0){
for(File file:files){
if(file.isDrectory()){
listDirectory(file);
}else{
System.out.println(file);
}
}
}
三、RandomAccessFile類的使用
RandomAccessFile java提供的對檔案內容的訪問,既可以讀檔案,也可以寫檔案。支援隨機訪問檔案,可以訪問檔案的任意位置。
(1)java檔案模型 :
在硬碟上的檔案是byte byte byte 儲存的 是資料的集合
(2)開啟檔案
有兩種模式 rw 讀寫 r只讀
RandomAccessFile raf=new RandomeAccessFile(file,”rw”);
檔案指標, pointer=0;
(3)寫方法
raf.write(int) —-> 只寫一個位元組 (後8位) 同時指標指向下一個位置 準備再次寫入
(4)讀方法
int b=raf.read(); 讀一個位元組
(5)檔案讀寫完成後一定要關閉 (oracle官方說明)

raf.getFilePointer()獲取當前指標位置
raf.length()獲取檔案長度
raf.seek()把指標指定到某一位置
注意write方法每次只能寫入一個位元組:
raf.write(‘A’);//此時指標後移
System.out.println(raf.getFilePointer());此時輸出為1
這時只寫入了一個位元組而不是完整的char,只是因為後八位剛好能夠表示A
raf.write(‘B’);

若要寫入一個整數i則需要寫四次
int i=0x7fffffff;
raf.write(i>>>24);//高八位
raf.write(i>>>16);
raf.write(i>>>8);
raf.write(i);//寫入最低的八位
System.out.println(raf.getFilePointer());
此時列印輸出6

可以直接寫入一個int
raf.writeInt(i);

String s=”中”;
byte[] gbk=s.getBytes(“gbk”);
raf.write(gbk);
System.out.println(raf.length(0);
此時列印輸出12(中文佔倆位元組)

讀檔案,必須把指標移到頭部
raf.seek();
//一次性讀取:
byte[] buf= new byte[(int)raf.length()];
raf.read(buf);
System.out.println(Arrays.toString(buf));
此時列印輸出
[65,66,127,-1,-1,-1,127,-1,-1,-1,-42,-48]
開頭的65,66是正確的AB,因為後八位已經能表示AB了

也可按字串輸出
String s1=new String(buf);
System.out.println(s1,”gbk”);
列印輸出AB?????
因為“中”的前後都有位元組,只有定位到中的兩個位元組,才能讀出他

最後要加上raf.close();
四、位元組流
4-1 位元組流之檔案輸入流FileInputStream-1
IO流分為輸入流、輸出流
還有位元組流、字元流
1、位元組流:
(1)InputStream:抽象了應用程式讀取資料的方式
(2)OutputStream:抽象了應用程式寫 出資料的方式
2)EOF = End 讀到-1就讀到結尾
3)輸入流基本方法
int b = in.read();讀取一個位元組無符號填充到int低八位.-1是EOF
in.read(byte[] buf) 讀取資料填充到位元組陣列buf
in.read(byte[] buf,int start, int size)讀取資料到位元組陣列buf從buf的start位置開始存放size長度分資料
4)輸出流基本方法
out.write(int b)寫出一個byte到流,b的低8位
out.write(byte[] buf)將buf位元組陣列都寫到流
out.write(byte[] buf, int start,int size) 位元組陣列buf從start位置開始寫size長度的位元組到流
1、byte 型別 8 位,int 型別 32 位,為了避免資料轉換錯誤,通過 & 0xff 將高 24 位清零
2、long time = System.currentTimeMillis()
當前時間與協調世界時 1970 年 1 月 1 日午夜之間的時間差(以毫秒為單位測量)
3、is.read() 單位元組適合讀取 小 檔案
is.read(byte[] bytes,int star,int size) 位元組陣列適合讀取 大 檔案
讀取檔案最常用的是批量讀取int bytes = fis.read(buf, 0 , buf.length);
FileInputStream檔案輸入
單位元組輸入即不用陣列。
4-2 位元組流之檔案輸入流FileInputStream-2
/**
* 批量讀取,對大檔案而言效率高,也是我們最常用的讀檔案的方式
* @Inparam fileName
* @throws IOException
*/
public static void printHexByByteArray(String fileName)throws IOException{
FileInputStream in = new FileInputStream(fileName);
byte[] buf = new byte[8 * 1024];
/*從in中批量讀取位元組,放入到buf這個位元組陣列中,
* 從第0個位置開始放,最多放buf.length個
* 返回的是讀到的位元組的個數
*/
int bytes = in.read(buf,0,buf.length);//一次性讀完,說明位元組陣列足夠大
int j = 1;
for(int i = 0; i < bytes;i++){
System.out.print(Integer.toHexString(buf[i] & 0xff)+” “);
if(j++%10==0){
System.out.println();
}
}
4-3 位元組流之檔案輸出流FileOutputStream (13:24)
FileOutputStream fos = new FileOutputStream(file,true)
檔案不存在,則建立,否則在後面追加內容
FileOutputStream fos = new FileOutputStream(file)
檔案不存在,則建立,否則,刪除後再建立
java中throw和throws的區別
仔細一看就知道了: public Test() throws RepletException { try { System.out.println(“Test this Project!”) } catch (Exception e) { throw new Exception(e.toString()); } }throws是用來宣告一個方法可能丟擲的所有異常資訊throw則是指丟擲的一個具體的異常型別搜尋。通常在一個方法(類)的宣告處通過throws宣告方法(類)可能丟擲的異常資訊,而在方法(類)內部通過throw宣告一個具體的異常資訊。throws通常不用顯示的捕獲異常,可由系統自動將所有捕獲的異常資訊拋給上級方法;throw則需要使用者自己捕獲相關的異常,而後在對其進行相關包裝,最後在將包裝後的異常資訊丟擲
public static void copyFile(File srcFile,File destFile)throws IOException{
if(!srcFile.exists()){
throw new IllegalArgumentException(“檔案:”+srcFile+”不存在”);
}
if(!srcFile.isFile()){
throw new IllegalArgumentException(srcFile+”不是檔案”);
}
FileInputStream in = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(destFile);
byte[] buf = new byte[8*1024];
int b ;
while((b = in.read(buf,0,buf.length))!=-1){
//講buf陣列裡的內容讀入到該輸出流中(out)
out.write(buf,0,b);
out.flush();//最好加上
}
in.close();
out.close();
}
4-4 位元組流之資料輸入輸出流
readInt readLong 方法都是對FileInputStream方法的包裝
DataOutputStream/DataInputStream
對“流”功能的擴充套件,可以更加方便的讀取 int,long, 字元等型別資料
DataOutputStream:使用FileOutputStream構造出來,通過包裝FileOutput,可以呼叫FileOutput類的write方法來構造新的更方便的寫方法:
new DataOutputStream(new FileOutptStream(file))

wrieteUTF()採用utf-8編碼寫出字串
用utf-16be寫出字串,或字串陣列
寫完之後一定要關閉流

資料輸入輸出流:
DataInputStream、DataOutputStream 是對“流”功能的擴充套件,方便讀寫
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
dos.writeInt(10);
dos.writeLong(10l);字母l
dos.writeDouble(10.5);
//採用utf-8編碼寫出
dos.writeUTF(“中國”);
//採用utf-16be編碼寫出
dos.writeChars(“中國”);
4-5 位元組緩衝流
BufferedInputStream&BufferedOutputStream
提供了帶快取區的操作,一般開啟檔案進行寫入或讀取操作時,都會加上緩衝,這種流模式提高了IO的效能
從應用程式中把輸入放入檔案,相當於將一罐水倒入到另外一個罐中:
FileOutputStream–>write()方法相當於一滴一滴地把水“轉移”過去
DataOutputStream–>writeXxx()方法會方便一些,相當於一瓢一瓢把水“轉移”
BuffereOutputStream–>write()方法更方便,相當於一瓢一瓢先放入 桶中,在從桶中倒入到另外一個罐中
批量檔案拷貝&利用帶緩衝的位元組流,實現檔案拷貝&單位元組,不帶緩衝進行檔案拷貝
效率:批量檔案拷貝最快,其次帶緩衝拷貝,最後單位元組拷貝
五、字元流
5-1 位元組字元轉換流
1)編碼問題 前已述及 起碼有一點要非常清楚的是,一切進入計算機的都會變成位元組碼
2)認識文字和文字檔案
java中的文字(其實就是char)16位的無符號整數,是字元的unicode編碼(這是一種雙自己編碼)
檔案是byte byte byte…的資料集合,可以縮句來理解 檔案輸資料集合。位元組流的編碼方式和序列化規則不一樣就形成了不同的檔案:文字檔案,音訊檔案,視訊檔案等
文字檔案是文字(char的編碼)序列按照某種編碼方案(utf-8,utf-16be,gbk)序列化為byte的資料儲存集合
(3)字元流
抽象類 Reader Writer 二者實現了資料的兩種相互轉換,儲存時我們用位元組碼的形式儲存,讀入計算機記憶體處理(包括顯示,運算等)是用字元(ABC…)的形式。
字元的處理,一次處理一個字元。其底層仍然是基本的位元組序列
字元流的基本實現
InputStreamReader 完成byte流解析為char流,按照編碼接卸
OutputStreamWriter 提供char流到byte流,按照編碼處理。
為什麼上面特別說明了一下文字檔案,因為字元流大部分操作的都是文字檔案。畢竟文字文編,以編碼的方式不容易認讀,我們才把字元編碼解析為字元。如果Reader一個MP3之類的音訊檔案,根本就沒有什麼意義,因為,聲音本來就不是用來看的,所以說字元流主要是用於處理文字檔案的
4)InputStreamreader 完成byte流解析成char流 按照編碼解析
OutputStreamWrite 提供char流到byte流 按照編碼處理
FileInputStream in=new FileInputStream(“e:\javaio\imooc.txt”);
InputSreamStreamReader isr=new InputStreamReader(in);//預設專案編碼 gbk 可改成 utf-8
int c;
while((c=isr.read())!=-1){
syso -> char(c)
}
//一次讀一個字元陣列
char[] buffer=new char[8*1024];
int c;
while ((c=isr.read(buffer,0,buffer.length))!=-1){
批量讀取放入buffer這個字元陣列 從第0個位置開始防止,最多放置buffer.length個
String s=new String(buffer,0,c);
syso -> s;
}
5-2 字元流之檔案讀寫流
.字元流:字元流分為輸出流(Reader)和輸出流(Writer)。操作的是文字檔案。
字元處理,一次處理一個字元
字元處理底層還是基本的位元組序列
InputStreamReader:完成byte流解析為char流,按照編碼解析
FileInputStream in = new FileInputStream(“e:\javaio\imoocutf8.txt”);
//獲取字元輸入流
InputStreamReader isr = new InputStreamReader(in,”utf-8”);//預設專案的編碼,操作的時候,要寫檔案本身的編碼格式
OutputStreamWriter:提供char流到byte流,按照編碼處理
FileOutputStream out = new FileOutputStream(“e:\javaio\imoocutf81.txt”);
//獲取字元輸出流
OutputStreamWriter osw = new OutputStreamWriter(out,”utf-8”);

FileReader/FileWriter:可以直接寫檔名的路徑。與InputStreamReader相比壞處:無法指定讀取和寫出的編碼,容易出現亂碼。
FileReader fr = new FileReader(“e:\javaio\imooc.txt”); //輸入流
FileWriter fw = new FileWriter(“e:\javaio\imooc2.txt”);//輸出流
5-3 字元流的過濾器
字元流 之字元流的過濾器 除了具有基本的讀寫單個字外,更具有加強功能,可以一次性的讀寫一行。可以參見幫助文件,文件中的介紹更加詳細,介紹到字元流的顧慮器,優化了一般的Reader的巨大開銷。
Bufferedreader–>readLine()
BufferedWriter/PrintWriter 可以實現一次寫一行
FileReader和FileWriter不能增加編碼引數,所以當專案和讀取檔案編碼不同時,就會產生亂碼。
這種情況下,只能迴歸InputStreamReader和OutputStreamWriter。
BufferedReader – > readLine -> 讀一行 不識別換行符,不會自動換行
BufferedWriter/PrintWriter – > writeLine/println -> 寫一行,不會自動換行/自動換行
在檔案中換行,可以用newLine();實現
/**其構造需要雙層的巢狀
* 看看下面是一個多麼噁心的巢狀,FileReader 是對一個 Reader 進行過濾 所以,構造式需要傳進來一個Reader,(我們知道Reader是一個抽象類,我們只能使用起實現子類)
* 我們有知道,用的是Reader的實現子類 InputStreamReader,而InputStreamReader它有需要一個InputStream,我們最常用的
* InputStream的實現子類是FileInputStream,因為我們這裡也是對檔案進行操作
*/
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream(“e:\Jworkspace\code.txt”)));

BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(“e:\Jworkspace\code2.txt”)));

String line;
while((line = br.readline())!=null) //一次讀一行
{
System.out.println(line); //一次讀一行不支援換行, 必須使用println
bw.write(line);
//必須要單獨寫出黃航
bw.newLine();
bw.flush();
}
br.close();
bw.close
六、物件的序列化和反序列化
6-1 序列化基本操作
IO——物件的序列化和反序列化
一、概念
1、物件序列化,就是將Object轉換成byte序列,反之叫物件的反序列化
2、序列化流(ObjectOutputStream),位元組的過濾流 —— writeObject()方法
反序列化流(ObjectInputStream)—— readObject()方法
3、序列化介面(Serializable)
物件必須實現序列化介面,才能進行序列化,否則將出現異常。
這個藉口,沒有任何方法,只是一個【標準】
二、transient關鍵字
1、transient修飾的元素,不會進行JVM預設的序列化:如int transient age = 10;在序列化和反序列化後,age的值為預設分配的值0
2、可以自己通過重寫序列化操作方式,來對transient修飾的元素進行想要的序列化。
*方法:通過從ArrayList中拿到writeObject()和readObject()方法,進行自寫完成。
· 先執行s.defaultWriteObject(); 和 s.defaultReadObject()方法
· 再對於無法預設序列化的成員,可以進行.writeObject(obj)和this.obj = s.readObject()完成序列化
3、這樣做的目的是提高效率。如ArrayList裡,對陣列的有效物件進行序列化
、、、被序列化的物件要實現序列化介面:
public class Student implements Serializable{
、、、物件的序列化:
public static void main(String[] args) throws Exception{
//序列化後存到這個檔案裡
String file = “demo/obj.dat”;
//1.物件的序列化
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file));
Student stu = new Student(“10001”, “張三”, 20);
oos.writeObject(stu);
oos.flush();
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file));
Student stu = (Student)ois.readObject();
System.out.println(stu);
ois.close()
6-2 transient及ArrayList原始碼分析
序列化:
transient 關鍵字:被transient修飾的元素,該元素不會進行jvm預設的序列化,但可以自己完成這個元素的序列化
注意:
(1)在以後的網路程式設計中,如果有某些元素不需要傳輸,那就可以用transient修飾,來節省流量;對有效元素序列化,提高效能。
(2)可以使用writeObject自己完成這個元素的序列化。ArrayList就是用了此方法進行了優化操作。ArrayList最核心的容器Object[] elementData使用了transient修飾,但是在writeObject自己實現對elementData陣列的序列化。只對陣列中有效元素進行序列化。readObject與之類似。
(3)java.io.ObjectOutputStream.defaultWriteObject();
// 把jvm能預設序列化的元素進行序列化操作
java.io.ObjectOutputStream.writeInt(age);// 自己完成序列化
(4)
java.io.ObjectOutputStream.defaultReadObject();// 把jvm能預設反序列化的元素進行反序列化
this.age = java.io.ObjectOutputStream.readInt(); // 自己完成age的反序列化操作
ArrayList原始碼中序列化和反序列化的方法,可以拿來直接用:
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException

private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
s.defaultWriteObject();//把jvm能預設序列化的元素進行序列化操作
s.writeInt(stuage);//自己完成stuage的序列化
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException{
s.defaultReadObject();//把jvm能預設反序列化的元素進行反序列化操作
this.stuage = s.readInt();//自己完成stuage的反序列化操作

6-3 序列化中子父類建構函式問題
序列化過程中子父類建構函式問題
一、父類實現了serializable介面,子類繼承就可序列化。
1、子類在反序列化時,父類實現了序列化介面,則不會遞迴呼叫其建構函式。
二、父類未實現serializable介面,子類自行實現可序列化
2、子類在反序列化時,父類沒有實現序列化介面,則會遞迴呼叫其建構函式。

* 結論:【反序列化時】,向上遞迴呼叫建構函式會從【可序列化的一級父類結束】。即誰實現了可序列化(包括繼承實現的),誰的建構函式就不會呼叫。
總結
當兩個程序在進行遠端通訊時,彼此可以傳送各種型別的資料。無論是何種型別的資料,都會以二進位制序列的形式在網路上傳送。傳送方需要把這個Java物件轉換為位元組序列,才能在網路上傳送;接收方則需要把位元組序列再恢復為Java物件。
把Java物件轉換為位元組序列的過程稱為物件的序列化。
把位元組序列恢復為Java物件的過程稱為物件的反序列化。
物件的序列化主要有兩種用途:
1) 把物件的位元組序列永久地儲存到硬碟上,通常存放在一個檔案中;
2) 在網路上傳送物件的位元組序列。
JDK類庫中的序列化API
java.io.ObjectOutputStream代表物件輸出流,它的writeObject(Object obj)方法可對引數指定的obj物件進行序列化,把得到的位元組序列寫到一個目標輸出流中。
java.io.ObjectInputStream代表物件輸入流,它的readObject()方法從一個源輸入流中讀取位元組序列,再把它們反序列化為一個物件,並將其返回。
只有實現了Serializable和Externalizable介面的類的物件才能被序列化。Externalizable介面繼承自Serializable介面,實現Externalizable介面的類完全由自身來控制序列化的行為,而僅實現Serializable介面的類可以採用預設的序列化方式 。
物件序列化包括如下步驟:
1) 建立一個物件輸出流,它可以包裝一個其他型別的目標輸出流,如檔案輸出流;
2) 通過物件輸出流的writeObject()方法寫物件。
物件反序列化的步驟如下:
1) 建立一個物件輸入流,它可以包裝一個其他型別的源輸入流,如檔案輸入流;
2) 通過物件輸入流的readObject()方法讀取物件。