一、概述
- File類用於將文件或文件夾封裝成對象,方便對文件和文件夾的屬性信息進行操作。該類可以作爲參數傳遞給IO流的構造函數,彌補流對象在操作文件和文件夾上的缺陷。
二、File類的使用
- 1.構造方法
- 1)File(String FileName)
- 示例:
File f1 = new File("C:\\abc\\a.txt");
- 2)File(Strng, parent, String FileName)
- 示例:
File f2 = new File("C:\\abc", "b.txt");
該構造方法的好處在於對文件的操作更加靈活,出現文件目錄固定,文件名需要改變的時候,這個構造方法更好
- 3)File(File parent, String FileName)
- 示例:
File d = new File("C:\\abc");
File f3 = new File(d, "c.txt");
示例代碼:
package com.heisejiuhuche.io;
import java.io.File;
public class FileDemo {
public static void main(String[] args) {
/* 使用File類的不同構造方法封裝文件對象 */
File f1 = new File("Demo1.txt");
File f2 = new File("C:/Users/jeremy/Documents/javaTmp/", "Demo2.txt");
File d = new File("C:/Users/jeremy/Documents/javaTmp/");
File f3 = new File(d, "Demo3.txt");
/* 打印各個文件對象 */
System.out.println("f1:" + f1);
System.out.println("f2:" + f2);
System.out.println("f3:" + f3);
}
}
程序輸出結果:
f1:Demo1.txt
f2:C:\Users\jeremy\Documents\javaTmp\Demo2.txt
f3:C:\Users\jeremy\Documents\javaTmp\Demo3.txt
打印封裝文件對象時的絕對路徑或相對路徑。
- 2.成員方法
- 1)創建操作
- -boolean createNewFile():文件名不存在的情況下創建新文件
-boolean mkdir():創建一級目錄
-boolean mkdirs():創建多級目錄
- 2)刪除操作
- -boolean delete():刪除指定文件
-void deleteOnExit():虛擬機退出的時候刪除指定文件
- 3)判斷操作
- -boolean canExecute():判斷文件對象是否可執行
-boolean canRead():判斷文件對象是否可讀
-boolean canWrite():判斷文件對象是否可寫
-boolean exists():判斷文件對象是否存在
-boolean isDirectory():判斷文件對象是否是文件夾,判斷之前,必須先判斷該文件對象是否存在
-boolean isFile():判斷文件對象是否是文件,判斷之前,必須先判斷該文件對象是否存在
-boolean isHidden():判斷文件對象是否是隱藏文件
-boolean isAbsolute():判斷文件對象路徑是否是絕對路徑
-int compareTo(File pathname):比較兩個文件對象,以自然順序排序
- 4)獲取操作
- -String getName():獲取文件對象名
-String getParent():獲取文件對象的父母路,必須明確指定文件路徑
-String getPath():獲取文件對象相對路徑
-String getAbsolutePath():獲取文件對象絕對路徑,文件可以存在也可以不存在
-long lastModified():獲取文件對象最近一次被修改的時間
-long length():獲取文件對象大小
- 5)修改操作
- -boolean renameTo(File dest):將文件修改爲指定文件名並存入指定目錄
- 6)其他重要方法
- -static File[] listRoots():獲取有效盤符
-String[] list():獲取指定目錄中的文件和文件夾,包括隱藏文件;必須是存在目錄調用,文件調用會空指針
-String[] list(FilenameFileter filter):獲取指定格式的文件
-File[] listFiles()
-File[] listFiles(FileFilter filter)
-File[] listFiles(FilenameFilter filter)
- 3.遞歸
- 用遞歸遍歷文件夾裏的所有內容並以層級結構打印。
示例代碼:
package com.heisejiuhuche.io;
import java.io.File;
public class RecursionDemo {
public static void main(String[] args) {
File dir = new File("C:/Users/jeremy/Documents/javaTmp/testfile");
recur(dir, 0);
}
/* 目錄層級結構 */
private static String getLevel(int level) {
StringBuilder sb = new StringBuilder();
/* 樹形結構圖形 */
sb.append("|--");
for(int x= 0; x < level; x++) {
/* 根據層級的不同在樹形結構圖形前補上製表符 */
sb.insert(0, "\t");
}
return sb.toString();
}
/* 遞歸遍歷文件夾,並打印所有文件和文件夾 */
private static void recur(File dir, int level) {
/* 列出所有文件到文件數組 */
File[] files = dir.listFiles();
/* 打印文件夾的名稱 */
System.out.println(getLevel(level) + dir.getName());
/* 打印完文件夾的名稱,層級自增1 */
level++;
/* 遍歷所有文件,如果是文件夾,則遞歸遍歷裏面的文件和文件夾 */
for(int x = 0; x < files.length; x++) {
if(files[x].isDirectory()) {
recur(files[x], level);
}
else
System.out.println(getLevel(level) + files[x].getName()); //如果不是文件夾,打印文件
}
}
}
程序輸出結果:
|--testfile
|--aaa
|--ccc
|--ddd
|--h.txt
|--ddd - Copy (2).txt
|--ddd - Copy - Copy.txt
|--ddd - Copy.txt
|--ddd.txt
|--ccc - Copy (2).txt
|--ccc - Copy - Copy.txt
|--ccc - Copy.txt
|--ccc.txt
|--aaa - Copy (2).txt
|--aaa - Copy - Copy.txt
|--aaa - Copy.txt
|--aaa.txt
|--bbb
|--eee
|--f.txt
|--eee - Copy (2).txt
|--eee - Copy - Copy.txt
|--eee - Copy.txt
|--eee.txt
注意:
遞歸的使用必須要明確控制條件,便面無限循環;遞歸要慎重使用,調用次數過多,會造成內存溢出。
- 4.刪除帶內容目錄
示例代碼:
package com.heisejiuhuche.io;
import java.io.File;
public class DelDirWithContentDemo {
public static void main(String[] args) {
File dir = new File("C:/Users/jeremy/Documents/javaTmp/testfile");
recur(dir);
}
/* 遍歷所有文件夾,刪除文件,並刪除文件夾 */
private static void recur(File dir) {
File[] files = dir.listFiles();
for(int x = 0; x < files.length; x++) {
if(files[x].isDirectory())
recur(files[x]);
else
/* 打印文件名及文件刪除結果 */
System.out.println(files[x].getName() + "--files--" + files[x].delete());
}
/* 打印文件夾名及文件夾刪除結果 */
System.out.println(dir.getName() + "**dir**" + dir.delete());
}
}
程序輸出結果:
demo - Copy (2) - Copy.txt--files--true
ddd**dir**true
demo - Copy (2) - Copy - Copy - Copy.txt--files--true
demo - Copy (2) - Copy - Copy.txt--files--true
ccc**dir**true
demo - Copy (2) - Copy - Copy.txt--files--true
demo - Copy (2) - Copy.txt--files--true
demo - Copy (2).txt--files--true
demo - Copy (3) - Copy.txt--files--true
aaa**dir**true
demo - Copy - Copy - Copy.txt--files--true
demo - Copy - Copy.txt--files--true
demo - Copy - Copy - Copy.txt--files--true
eee**dir**true
bbb**dir**true
demo - Copy (2) - Copy.txt--files--true
demo - Copy (2).txt--files--true
demo - Copy - Copy (2).txt--files--true
demo - Copy - Copy (3).txt--files--true
demo - Copy - Copy - Copy.txt--files--true
demo - Copy - Copy.txt--files--true
demo - Copy.txt--files--true
demo.txt--files--true
testfile**dir**true
注意:
Java無法訪問windows中的隱藏目錄,所以,最好在for循環中判斷的時候加上:
if(!files[x].isHidden() && files[x].isDirectory())
忽略隱藏的文件夾
- 5.練習
FileWriter
接收File
的構造方法。示例代碼:
package com.heisejiuhuche.io;
/**
* 接收鍵盤錄入,然後寫入指定目錄下的文件中
* 爲了測試FileWriter接收File類型數據作爲構造方法參數
*/
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
public class FileWriterTest {
public static void main(String[] args) {
try {
writeToFile(createFile("C:\\Users\\jeremy\\Documents\\FileWriter.txt"));
} catch(IOException e) {
e.printStackTrace();
}
}
private static File createFile(String filepath) throws IOException {
File file = new File(filepath);
if(!file.exists())
file.createNewFile();
return file;
}
private static void writeToFile(File file) {
BufferedReader bufr = null;
BufferedWriter bufw = null;
try {
bufr = new BufferedReader(new InputStreamReader(System.in));
bufw = new BufferedWriter(new FileWriter(file));
String line = null;
while((line = bufr.readLine()) != null) {
if(line.equals("over"))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(bufr != null)
bufr.close();
} catch(IOException e) {
e.printStackTrace();
}
try {
if(bufw != null)
bufw.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
一、Properties類
- 1.概述
Properties
類是HashTable
的子類。該類具備Map
集合的特點,儲存字符串作爲鍵值對。Properties
類是集合中和IO技術相結合的容器。該類可以用於鍵值對形式的配置文件。配置文件用於保存軟件運行時的各項參數。配置完成之後將會被持久化存儲,每次運行軟件都會加載該配置文件。- 2.應用
- 1)設置並打印鍵值對
示例代碼:
package com.heisejiuhuche.io;
import java.util.Properties;
import java.util.Set;
public class PropertiesDemo2 {
public static void main(String[] args) {
/* 創建Property對象 */
Properties prop = new Properties();
/* 設置鍵值對 */
prop.setProperty("zhangsan", "18");
prop.setProperty("wangwu", "20");
/* 通過鍵獲取值,並打印 */
System.out.println(prop.getProperty("zhangsan"));
/* 將鍵都返回並裝進Set */
Set<String> value = prop.stringPropertyNames();
/* 遍歷集合並打印鍵值 */
for(String str : value) {
System.out.println(str + "::" + prop.getProperty(str));
}
}
}
程序輸出結果:
18
zhangsan::18
wangwu::20
- 2)從文件加載配置並修改
示例代碼:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Properties;
public class PropertiesLoadDemo {
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
// loadProp();
/* 創建緩衝輸入流對象並關聯配置文件 */
BufferedReader bufr = new BufferedReader(new FileReader(
"C:/Users/jeremy/Documents/javaTmp/info.txt"));
/* 調用prop獨享的load方法加載配置文件 */
prop.load(bufr);
/* 在控制檯列出所有鍵值對 */
prop.list(System.out);
/* 修改鍵值對 */
prop.setProperty("李四", "50");
/* 調用store方法修改配置文件並保存 */
prop.store(new OutputStreamWriter(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/info.txt"), "GBK"), "Test");
prop.list(System.out);
}
/* 方法一:讀取文件,將每一行按=號分割,將鍵值對存入Properties對象 */
private static void loadProp() {
BufferedReader bufr = null;
Properties prop = null;
try {
bufr = new BufferedReader(new FileReader("C:/Users/jeremy/Documents/javaTmp/info.txt"));
prop = new Properties();
String line = null;
while((line = bufr.readLine()) != null) {
String[] kv = line.split("=");
prop.setProperty(kv[0], kv[1]);
}
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(bufr != null)
bufr.close();
} catch(IOException e) {
e.printStackTrace();
}
}
System.out.println(prop);
}
}
程序輸出結果:
-- listing properties --
王五=19
張三=20
李四=90
-- listing properties --
王五=19
張三=20
李四=50
注意:
-#都是註釋,不會被Properties加載
-Properties加載的配置文件必須有固定格式;通常爲:鍵=值
- 3)記錄程序運行次數
- 創建配置文件記錄程序運行次數,到達
5
此,提示用戶註冊。示例代碼:
package com.heisejiuhuche.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class PropertiesTest {
public static void main(String[] args) throws IOException {
try {
checkAuth();
} catch(IOException e) {
throw new RuntimeException("配置文件加載異常...");
}
}
private static void checkAuth() throws IOException {
/* 創建配置文件對象 */
File file = new File("C:/Users/jeremy/Documents/javaTmp/auth.txt");
/* 如果文件不存在,創建文件,避免異常 */
if(!file.exists())
file.createNewFile();
/* 創建輸出流對象 */
FileInputStream fis = new FileInputStream(file);
/* 創建Properties對象 */
Properties prop = new Properties();
/* 加載配置文件 */
prop.load(fis);
String val = null;
int count = 0;
/* 如果讀取鍵值爲空,說名是第一次運行,那麼將計數器count+1,然後和鍵time一起存入prop對象
* 並寫入配置文件
*/
if((val = prop.getProperty("time")) != null) {
/* 如果取到了值,說明不是第一次運行,那麼讓計數器count等於time鍵所對應的值
* 再+1,然後和time鍵一起再次存入配置文件
*/
count = Integer.parseInt(val);
/* 判斷,如果count = 5,說明使用次數已到,結束程序,提示註冊 */
if(count >= 5) {
System.out.println("請註冊...");
return;
}
}
count++;
prop.setProperty("time", count + "");
/* 輸出流不能在load語句前聲明,否則將會覆蓋配置文件,導致配置信息清空 */
FileOutputStream fos = new FileOutputStream(file);
/* 將配置文件更新 */
prop.store(fos, "CheckAuth");
fis.close();
fos.close();
}
}
二、打印流
- 1.PrintStream類
- 1)概述
PrintStream
類是字節打印流,其爲其他流添加了功能,使它們能夠方便打印各種數據值形式。該類不會拋出IOException
,同時內部有刷新機制,無須手動刷新緩衝區。PrintStream
可以直接操作文件對象。- 2)常用方法
- -PrintStream(File file):接收File對象
-PrintStream(OutputStream out):接收字節輸出流
-PrintStream(String filename):接收字符串路徑
-println():打印所有基本數據類型,保持數據原樣
- 3)PrintStream示例
示例代碼:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
public class PrintStreamDemo {
public static void main(String[] args) throws IOException {
/* 創建輸入流和PrintStream對象並關聯文件 */
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintStream ps = new PrintStream("C:/Users/jeremy/Documents/javaTmp/PrintStream.txt");
String len = null;
/* 接收鍵盤輸入並寫入文件 */
while((len = bufr.readLine()) != null) {
if(len.equals("over"))
break;
ps.println(len);
ps.flush();
}
bufr.close();
ps.close();
}
}
該類的使用方法和下面介紹的PrintWriter
類基本相同。
- 2.PrintWriter類
- 1)概述
字符打印流可以打印基本舒蕾型。其最強大的功能是
println
方法,可以實現自動換行並直接操作基本數據類型。- 2)常用方法
- -PrintWriter(File file):接收File對象
-PrintWriter(File file, String csn):接收File對象,並制定字符集
-PrintWriter(OutputStream out):接收字節輸出流
-PrintWriter(OutputStream out, boolean autoflush):接收字節輸出流,設置自動刷新
-PrintStream(Writer out):接收字符輸出流
-PrintWriter(String filename):接收字符串路徑
- 3)PrintWriter示例
示例代碼:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
public class PrintWriterDemo {
public static void main(String[] args) throws IOException {
/* 創建輸入流對象和打印流對象 */
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(System.out, true);
String len = null;
while((len = bufr.readLine()) != null) {
/* 如果len等於over,就停止程序 */
if(len.equals("over"))
break;
/* 調用println方法,在打印的時候自動換行 */
pw.println(len.toUpperCase());
}
bufr.close();
pw.close();
}
}
注意:
在PrintWriter構造方法中設置true,就無須調用flush()刷新緩衝。
三、序列流
- 1.SequenceInputStream類
- 1)概述
SequenceInputStream
類標識其他輸入流的邏輯串聯。它從輸入流的有序集合開始,並從第一個輸入流開始讀取,直到達到文件末尾;接着從第二個輸入流讀取,以此類推,直到到達包含的最後一個輸入流的文件末尾爲止。- 2)應用
- 將三個文件的內容使用
SequenceInputStream
寫入到一個文件中。示例代碼:
package com.heisejiuhuche.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
public class SequenceInputStreamDemo {
public static void main(String[] args) {
write();
}
private static void write() {
SequenceInputStream sis = null;
ArrayList<FileInputStream> list = null;
FileOutputStream fos = null;
try {
/* 創建集合對象 */
list = new ArrayList<FileInputStream>();
/* 將輸入流對象存入集合 */
for(int x = 1; x <= 3; x++) {
list.add(new FileInputStream(
"C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + x + ".txt"));
}
Iterator<FileInputStream> it = list.iterator();
/* 得到裝有輸入流對象的Enumeration */
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
public boolean hasMoreElements() {
return it.hasNext();
}
public FileInputStream nextElement() {
return it.next();
}
};
/* 創建序列流對象和輸出流對象 */
sis = new SequenceInputStream(en);
fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\4.txt");
byte[] buf = new byte[1024];
int len = 0;
/* 合併文件 */
while((len = sis.read(buf)) != -1) {
fos.write(buf, 0, len);
fos.flush();
}
} catch(IOException e) {
e.printStackTrace();
} finally {
try {
if(sis != null)
sis.close();
} catch(IOException e) {
e.printStackTrace();
}
try {
if(fos != null)
fos.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
- 3)切割文件後再合併
- 將一個
MP3
文件按1M
切割,最後合併,要求保證合併後的文件可以播放。示例代碼:
package com.heisejiuhuche.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
public class SplitMp3Test {
public static void main(String[] args) {
split();
merge();
}
private static void merge() {
/* 創建集合,用於存放輸入流對象 */
ArrayList<FileInputStream> list = new ArrayList<FileInputStream>();
SequenceInputStream sis = null;
FileOutputStream fos = null;
try {
/* 將四個文件輸入流對象存入集合 */
for(int x = 1; x <= 4; x++) {
list.add(new FileInputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + x + ".part"));
}
Iterator<FileInputStream> it = list.iterator();
/* 拿到有輸入流對象的Enumeration */
Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
public boolean hasMoreElements() {
return it.hasNext();
}
public FileInputStream nextElement() {
return it.next();
}
};
/* 創建序列流對象 */
sis = new SequenceInputStream(en);
/* 創建輸出流對象,並關聯文件 */
fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\back.mp3");
byte[] buf = new byte[1024];
int len = 0;
/* 合併文件 */
while((len = sis.read(buf)) != -1) {
fos.write(buf, 0, len);
fos.flush();
}
} catch(FileNotFoundException e) {
throw new RuntimeException("文件不存在...");
} catch(IOException e) {
throw new RuntimeException("合併失敗...");
} finally {
try {
if(sis != null)
sis.close();
} catch(IOException e) {
throw new RuntimeException("資源關閉失敗...");
}
try {
if(fos != null)
fos.close();
} catch(IOException e) {
throw new RuntimeException("資源關閉失敗...");
}
}
}
private static void split() {
File file = null;
FileInputStream fis = null;
FileOutputStream fos = null;
try {
/* 創建輸入流對象 */
file = new File("C:\\Users\\jeremy\\Documents\\javaTmp\\Backseat.mp3");
fis = new FileInputStream(file);
byte[] buf = new byte[1024 * 1024];
int count = 1;
int len = 0;
/* 將文件按1M大小分割,分爲count個文件 */
while((len = fis.read(buf)) != -1) {
fos = new FileOutputStream("C:\\Users\\jeremy\\Documents\\javaTmp\\mp\\" + count++ + ".part");
fos.write(buf, 0, len);
fos.flush();
}
} catch(IOException e) {
throw new RuntimeException("分割失敗...");
} finally {
try {
if(fis != null)
fis.close();
} catch(IOException e) {
throw new RuntimeException("輸入資源關閉失敗");
}
try {
if(fos != null)
fos.close();
} catch(IOException e) {
throw new RuntimeException("輸出資源關閉失敗");
}
}
}
}
四、對象流
- 1.ObjectOutputStream和ObjectInputStream類
- 1)概述
ObjectOutputStream
類可以將Java對象的基本數據類型和圖形寫入OutputStream
,再利用ObjectInputStream
讀取(重構)該對象。該類用於將對象持久化存儲在硬盤上,以便程序下次啓動的時候能再次加載該對象。- 2)常用方法
ObjectOutputStream
具備直接操作基本數據類型的一些列write()
方法及操作對象的writeObject()
方法。以一個示例來演示該類的使用方法及注意事項。示例代碼:
package com.heisejiuhuche.io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectOutputInputStreamDemo {
static final File file = new File("C:/Users/jeremy/Documents/javaTmp/person.txt");
public static void main(String[] args) throws Exception {
writeObj();
}
private static void readObj() throws Exception {
/* 創建對象輸入流對象,傳入一個字節輸入流,並關聯文件 */
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
/* 讀取Person對象,向下轉型爲Person類型 */
Person p = (Person)ois.readObject();
/* 打印Person對象 */
System.out.println(p);
/* 關閉資源 */
ois.close();
}
private static void writeObj() throws Exception {
/* 創建對象輸出流對象,傳入一個字節輸出流,並關聯文件 */
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
/* 調用writeObject方法將Person對象寫入文件 */
oos.writeObject(new Person("zhangsan", 28));
/* 寫入文件後讀取並打印在控制檯 */
readObj();
/* 關閉資源 */
oos.close();
}
}
class Person {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + "::" + this.age;
}
}
注意:
上面的代碼報出異常:
Exception in thread “main” java.io.NotSerializableException: com.heisejiuhuche.io.Person
在使用ObjectOutputStream寫入對象的時候,被寫入的對象必須實現Serializable接口
- 3)Serializable接口
Serializable
接口是一個標記接口,它沒有任何方法。該接口給對象定義一個固定的數字標識,將該對象類序列化。使用該標識作爲對象存儲和讀取是否一致的判斷標準。這個數字標識叫做SerialVersionUID
,是根據每個對象的成員,由Java計算出來的一個固定值。如果讀取的時候,該值發生了變化,會拋出異常。下面會用代碼演示這一現象及序列化的其他特點。修改上述代碼,按要求使Person
類實現Serializable
接口。
示例代碼:
class Person implements Serializable {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + "::" + this.age;
}
}
程序運行結果:
zhangsan::28
此時,如果將Person
類中name
成員的private
關鍵字去掉,在寫入和讀取時,就會發生UID
不匹配的情況,拋出異常。
示例代碼:
class Person implements Serializable {
/* 去掉了private關鍵字 */
String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + "::" + this.age;
}
}
程序運行結果:
Exception in thread "main" java.io.InvalidClassException
這證明了UID是根據成員的數字標識算出來的一個固定值。
如果要實現更改成員之後還能讀取該類,只需手動指定UID
。下面的代碼仍然去掉了private
關鍵字,但是讀取無誤。
示例代碼:
package com.heisejiuhuche.io;
import java.io.Serializable;
class Person implements Serializable {
/* 手動指定UID */
private static final long serialVersionUID = 37L;
String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return this.name + "::" + this.age;
}
}
程序運行結果:zhangsan::28
注意:
-已被賦值的靜態成員,在序列化後,值不能修改;如,static String country = “cn”;之後再修改country的值,讀取的時候,還是”cn”;
-不想序列化某非靜態成員,可以加上transient關鍵字:transient private int age;
五、管道流
- 1.概述
- 管道流能通過結合線程技術,實現輸入流和輸出流的直接連接。管道輸入和輸出流必須連接在一起使用。某個線程從
PipedInputStream
讀取數據,並由另一個線程寫入到PipedOutputStream
。這是涉及多線程技術的IO流
。- 2.PipedInputStream和PipedOutputStream類
- 管道流可以通過構造方法連接,也可以通過調用
connect()
方法連接。示例代碼:
package com.heisejiuhuche.io;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class PipedStreamDemo {
public static void main(String[] args) throws IOException {
/* 創建管道流對象並連接 */
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
pis.connect(pos);
/* 啓動線程 */
new Thread(new Read(pis)).start();
new Thread(new Write(pos)).start();
}
}
class Read implements Runnable {
private PipedInputStream pis;
Read(PipedInputStream pis) {
this.pis = pis;
}
public void run() {
/* 等待管道輸出流寫入數據,讀到輸出流數據前,處於阻塞狀態 */
try {
byte[] buf = new byte[1024];
System.out.println("等待讀取...");
int len = pis.read(buf);
System.out.println("讀取結束...");
System.out.println(new String(buf, 0, len));
} catch(IOException e) {
throw new RuntimeException("讀取失敗");
}
}
}
class Write implements Runnable {
private PipedOutputStream pos;
Write(PipedOutputStream pos) {
this.pos = pos;
}
public void run() {
/* 往管道輸入流中寫入數據,寫入先等待6秒,模擬寫入過程 */
try {
System.out.println("數據寫入中...");
Thread.sleep(6000);
pos.write("數據來啦!!!!!".getBytes());
pos.close();
} catch(Exception e) {
throw new RuntimeException("寫入失敗");
}
}
}
程序運行結果:
等待讀取...
數據寫入中...
讀取結束...
數據來啦!!!!!
六、RandomAccessFile類
- 1.概述
RandomAccessFile
類不是IO體系中的子類,而是直接繼承自Object
。但是它仍然是IO包
中的成員,因爲它具備讀寫功能。該類支持對隨機訪問文件的讀取和寫入。該類的內部封裝了數組,通過文件指針對數據進行操作,可以通過getFilePointer()
方法獲取指針位置;通過seek()
方法改變指針的位置。RandomAccessFile
類只能操作文件,並必須設定操作模式。- 2.應用
- 演示
RandomAccessFile
的基本方法及特點。示例代碼:
package com.heisejiuhuche.io;
import java.io.IOException;
import java.io.RandomAccessFile;
public class RandomAccessFileDemo {
public static void main(String[] args) throws IOException {
write();
read();
}
private static void write() throws IOException {
/* 創建RnadomAccessFile對象 */
RandomAccessFile raf = new RandomAccessFile("C:/Users/jeremy/Documents/javaTmp/random.txt", "rw");
raf.write("李四".getBytes());
/* write()方法的特點是,只寫出數據的最低8位,會造成數據丟失,出現亂碼 */
// raf.write(258);
/* 保證數據不丟失,要調用writeInt()方法,寫入4個8位 */
raf.writeInt(97);
raf.write("王五".getBytes());
raf.writeInt(99);
/* 讓指針回到文件起始位置 */
raf.seek(0);
/* 寫入數據,那麼李四的數據將被修改 */
raf.write("趙六".getBytes());
raf.writeInt(103);
raf.close();
}
private static void read() throws IOException {
RandomAccessFile raf = new RandomAccessFile("C:/Users/jeremy/Documents/javaTmp/random.txt", "rw");
/* 創建4個字節的數組 */
byte[] buf = new byte[4];
/* 如果想要直接取王五的數據,調用seek方法讓指針指向第二個8位即可 */
raf.seek(8);
/* 如果想要直接取王五的數據,調用skepBytes方法跳過前8位 */
raf.skipBytes(8);
/* 讀取4個字節到數組中 */
raf.read(buf);
/* 用readInt讀取下4個字節 */
int age = raf.readInt();
System.out.println(new String(buf) + age);
}
}
注意:
-write()方法只寫數據的最低8位,會造成數據丟失;在寫入int類型數據時,調用writeInt()方法;
-skipBytes()方法只能往前跳過指定字節數,不能往回跳轉;
-RandomAccessFile對象不僅能寫如數據,還能修改該數據;
-RandomAccessFile對象在創建時,如果模式爲”r”,就不會創建文件;如果該被讀取文件不存在,會拋出異常;模式爲”rw”,會自動創建該文件;如果文件已存在,則不會覆蓋,繼續添加內容;
-RandomAccessFile類可以結合多線程,實現數據的分段寫入,提高寫入效率
七、其他流對象
- 1.DataInputStream和DataOutputStream類
- 用於操作基本數據類型的流對象。
示例代碼:
package com.heisejiuhuche.io;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
write();
read();
writeUTF();
readUTF();
}
private static void readUTF() throws IOException {
DataInputStream dos = new DataInputStream(new FileInputStream(
"C:/Users/jeremy/Documents/javaTmp/utf-8.txt"));
System.out.println(dos.readUTF());
}
private static void writeUTF() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/utf-8.txt"));
/* 使用UTF-8修改版字符集寫入數據 */
dos.writeUTF("你好");
dos.close();
}
private static void read() throws IOException {
/* 創建DataInputStream對象並關聯文件 */
DataInputStream dos = new DataInputStream(new FileInputStream(
"C:/Users/jeremy/Documents/javaTmp/data.txt"));
/* 讀取基本數據類型 */
int num = dos.readInt();
boolean b = dos.readBoolean();
double d = dos.readDouble();
System.out.println("num = " + num);
System.out.println("b = " + b);
System.out.println("d = " + d);
}
private static void write() throws IOException {
/* 創建DataOutputStream對象並關聯文件 */
DataOutputStream dos = new DataOutputStream(new FileOutputStream(
"C:/Users/jeremy/Documents/javaTmp/data.txt"));
/* 寫入基本數據類型 */
dos.writeInt(234);
dos.writeBoolean(false);
dos.writeDouble(124.135252);
dos.close();
}
}
程序運行結果:
num = 234
b = false
d = 124.135252
你好
- 2.ByteArrayInputStream和ByteArrayOutputStream類
- 用於操作字節數組的流對象。該類的對象無須關閉,因爲沒有調用任何系統底層資源;如果關閉,仍可以調用其方法,同時沒有任何
IO異常
。ByteArrayInputStream
在初始化的時候需要接收一個字節數組;ByteArrayOutputStream
內部的緩衝區,會隨着數據的不斷寫入而自動增長。這兩個類用流的讀寫思想來操作數組,用於往內存中暫時寫入數據。示例代碼:
package com.heisejiuhuche.io;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
public class ByteArrayStreamDemo {
public static void main(String[] args) {
readWrite();
}
private static void readWrite() {
/* 創建字節數組流對象並關聯文件 */
ByteArrayInputStream bis = new ByteArrayInputStream("abcdefg".getBytes());
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int ch = 0;
/* 循環讀取所有字節並寫去字節數組輸出流對象 */
while((ch = bis.read()) != -1) {
bos.write(ch);
}
/* 打印數組大小,並打印內容 */
System.out.println(bos.size());
System.out.println(new String(bos.toByteArray()));
System.out.println(bos.toString());
}
}
程序運行結果:
7
abcdefg
abcdefg
注意:
和字節數組輸入輸出流類似的流對象還有:
分類 | 輸入 | 輸出 |
---|---|---|
字符數組流 | CharArrayReader | CharArrayWriter |
字符串流 | StringReader | StringWriter |
這些類的使用方式和字節數組流相似。
一、概述
- 字符編碼表是由計算機二進制數
1010
的排列組合和文字的對應關係形成的。它的出現可以讓計算機識別各個國家不同的文字和符號。二、常見字符編碼表
- 1.ASCII碼錶
- 美國標準信息交換碼,用一個字節的
7位
標識一個字符,最高位爲0
。- 2.ISO8859-1
- 拉丁碼錶(歐洲碼錶),用一個字節的
8位
標識一個字符。- 3.GB2312和GBK
GB2312
和GBK
都是中文字符編碼表。後者是前者的升級版,囊括更多的中文文字和符號。- 4.Unicode
- 國際標準碼,融合了全世界多種文字和符號。
- 5.UTF-8
Unicode
編碼表的優化版,最多用3個
字節標識一個字符。三、亂碼問題
- 亂碼問題的產生,原因在於解碼的時候沒有使用編碼時的編碼表。比如,在寫入中文數據的時候,指定了GBK編碼表,但是在讀取數據的時候卻用了UTF-8編碼表。GBK是兩個字節表示一個字符,UTF-8是三個字節表示一個字符。如果輸入的字符是“你好”,對應的GBK編碼表上的數字是【-60,-29,-70,-61】;讀取的時候誤用了UTF-8編碼表,那麼會讀取【-60,-29,-70】這三個字節,到UTF-8碼錶裏面尋找有沒有對應的字符,如果沒有,返回?;接着拿【-61】去找,如果沒有匹配的字符,返回?。反過來的過程相同。“你好”對應的UTF-8的數字是【-28,-67,-96,-27,-91,-67】,如果在讀取時誤用了GBK碼錶,那麼就拿每兩個數字去GBK表中查找對應字符,因此出現亂碼。
四、編碼解碼
- 1.定義
- 編碼就是將字符串轉換爲字節數組的過程;解碼就是將字節數組轉換爲字符串的過程。
- 2.解決亂碼問題
- 實際開發過程中,
web服務器
端通常默認使用ISO8859-1
的編碼表。如果出現使用GBK
編碼,而數據傳到服務器端之後出現亂碼的問題,只需要將亂碼的字符串再次用ISO8859-1
進行編碼,在用GBK
解碼即可。示例代碼:
package com.heisejiuhuche.io;
public class EncodeDemo {
public static void main(String[] args) throws Exception {
String s1 = "你好";
/* 使用gbk編碼 */
byte[] b1 = s1.getBytes("GBK");
/* 使用iso8859-1解碼 */
String s2 = new String(b1, "ISO8859-1");
/* 出現亂碼???? */
System.out.println(s2);
/* 是同iso8859-1再次編碼 */
byte[] b2 = s2.getBytes("ISO8859-1");
/* 使用gbk解碼 */
String s3 = new String(b2, "GBK");
/* 還原字符串成功 */
System.out.println(s3);
}
}
程序運行結果:
????
你好
五、聯通的問題
- 在文本文件中輸入“聯通”二字,保存退出;第二次打開時會出現亂碼。
圖中可見“聯通”二字的二進制儲存形式,符合UTF-8
兩個字節存儲的格式要求;最高位分別爲110
和10
。所以當“聯通”二字以GBK
編碼表形式編碼存儲後,記事本在讀取的時候誤認爲是UTF-8
編碼表編碼,會到UTF-8
碼錶中查找字符,形成亂碼。
六、練習
- 有
5
個學生,每個學生有3
們課程的成績。從鍵盤錄入以上數據,格式爲:姓名,數學成績,語文成績,英語成績;並計算出總成績。將最後的學生信息和計算出的總分按從高到低的順序存入文件“stud.txt”
中。示例代碼:
package com.heisejiuhuche.io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.Comparator;
import java.util.TreeSet;
public class StudentInfoToFileTest {
public static void main(String[] args) {
/* 反轉比較器 */
Comparator<Student> student = Collections.reverseOrder();
/* 獲得學生對象並寫入文件 */
StudentTools.infoToFile(StudentTools.getStudentInfo(student));
}
}
class StudentTools {
public static TreeSet<Student> getStudentInfo(Comparator<Student> comp) {
BufferedReader bufr = null;
try {
/* 創建TreeSet對象,用於儲存學生對象並排序 */
TreeSet<Student> stuSet = null;
/* 接收鍵盤錄入 */
bufr = new BufferedReader(new InputStreamReader(System.in));
/* 判斷有無比較器參數,分別創建有比較器和沒有比較器的兩個TreeSet對象 */
if(comp == null)
stuSet = new TreeSet<Student>();
else
stuSet = new TreeSet<Student>(comp);
String tmp = null;
/* 不斷接收鍵盤按固定格式的錄入,如果收到over,結束程序 */
while((tmp = bufr.readLine()) != null) {
if(tmp.equals("over"))
break;
/* 將字符串按指定格式切割 */
String[] info = tmp.split(",");
/* 將每段信息傳入學生對象封裝 */
Student stu = new Student(info[0], Integer.parseInt(info[1]),
Integer.parseInt(info[2]),
Integer.parseInt(info[3]));
/* 將封裝號的學生對象存入集合以便排序 */
stuSet.add(stu);
}
return stuSet;
} catch(IOException e) {
throw new RuntimeException("文件讀入失敗...");
} finally {
try {
if(bufr != null)
bufr.close();
} catch(IOException e) {
throw new RuntimeException("輸入流關閉失敗...");
}
}
}
public static void infoToFile(TreeSet<Student> stuSet) {
File file = new File("C:/Users/jeremy/Documents/javaTmp/StudentInfo.txt");
BufferedWriter bufw = null;
try {
bufw = new BufferedWriter(new FileWriter(file));
/* 將集合中的數據寫入文件 */
for(Student stus : stuSet) {
bufw.write(stus.toString() + "\t");
bufw.write(stus.getSum() + "");
bufw.newLine();
bufw.flush();
}
} catch(IOException e) {
throw new RuntimeException("文件寫入失敗...");
} finally {
try {
if(bufw != null)
bufw.close();
} catch(IOException e) {
throw new RuntimeException("輸入流關閉失敗...");
}
}
}
}
/* 創建學生類 */
class Student implements Comparable<Student> {
private String name;
private int math, cn, en, sum;
Student(String name, int math, int cn, int en) {
this.name = name;
this.math = math;
this.cn = cn;
this.en = en;
sum = math + cn + en;
}
public void setName(String name) { this.name = name; }
public void setMath(int math) { this.math = math; }
public void setCn(int cn) { this.cn = cn; }
public void setEn(int en) { this.en = en; }
public String getName() { return name; }
public int getMath() { return math; }
public int getCn() { return cn; }
public int getEn() { return en; }
public int getSum() { return sum; }
/* 複寫hashCode方法 */
public int hashCode() {
return this.name.hashCode() + this.sum * 37;
}
/* 複寫equals方法 */
public boolean equals(Object obj) {
if(!(obj instanceof Student))
throw new ClassCastException("類型不匹配");
Student stu = (Student)obj;
return this.name.equals(stu.name) && this.sum == stu.sum;
}
/* 複寫compareTo方法 */
public int compareTo(Student stu) {
int flag = new Integer(this.sum).compareTo(new Integer(stu.sum));
if(flag == 0)
return this.name.compareTo(stu.name);
return flag;
}
/* 複寫toString方法 */
public String toString() {
return "Student[" + this.name + "," + math + "," + cn + "," + en + "]";
}
}
程序運行結果:
Student[zhouqi,70,70,70] 210
Student[zhaoliu,60,60,60] 180
Student[wangwu,50,50,50] 150
Student[lisi,40,40,40] 120
Student[zhangsan,30,30,30] 90