一、FileInputStream、FileoutputStream
(一)FileInputStream
(1)read()方法
(a)不加改造的read()方法
FileInputStream fis = null;
try {
// fis = new FileInputStream("C:\\Users\\趙曉東\\Desktop\\javaIO\\FileInputStream.txt");
// /*相對路徑一定是從當前所在的位置作爲起點開始找*/
/*IDEA默認的當前路徑是哪裏?*/
fis = new FileInputStream("abc.txt");
int readDate= fis.read();
System.out.println(readDate);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis !=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
我的abc文件中寫的是abc,讀取的結果爲:
97
可見它只能讀取一個,並且最後一定要在finally裏面寫一個關閉的方法。避免浪費資源。fis.close();l
(b)加上while循環的read()方法
如果我們想讓它一直讀取怎麼辦呢?這時候就要用到了循環。
FileInputStream fis = null;
try {
fis = new FileInputStream("abc.txt");
int readCount = 0;
while (readCount !=-1){
readCount =fis.read();
if(readCount ==-1){
break;
}
System.out.println(readCount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis!=null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
解釋:當它讀到最後會返回一個-1代表讀完了。
(c)讀取多個的read(byte[] b)
- int
- read(byte[] b)
- 從該輸入流讀取最多 b.length個字節的數據爲字節數組。
這樣可以一次最多讀取b.length個字節,減少了硬盤和內存的交互,提高程序的執行效率,往byte[]數組當中讀。
首先,我在zxd文件中寫了abcdef,int readCount表示返回的是讀取的數量。
FileInputStream fis = null;
try {
fis = new FileInputStream("zxd");
/*第一次讀*/
byte[] bytes = new byte[4];
int readCount = fis.read(bytes);
System.out.println(readCount);
/*第二次讀*/
readCount = fis.read(bytes);
System.out.println(readCount);
/*第三次讀*/
readCount = fis.read(bytes);
System.out.println(readCount);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fis!=null){
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
讀取的結果
4
2
-1
並且在第二次讀的時候,會把前面ab會覆蓋了。下面我們用圖來模擬一下。
第一次讀的時候,會把abcdef中的abcd讀入到數組裏面。
第二次讀的時候,ef會把前面ab覆蓋了。
(2)available()
這個表示返回流當中剩餘的沒有讀到的字節數量
int
available()
返回從此輸入流中可以讀取(或跳過)的剩餘字節數的估計值,而不會被下一次調用此輸入流的方法阻塞。
FileInputStream fis = null;
fis = new FileInputStream("zxd");
int allNumber=fis.available();
System.out.println(allNumber);
6
可見available返回的是文件的大小。正好可以通過它,傳入到數組中然後這樣就可以直接讀取了。
FileInputStream fis = null;
fis = new FileInputStream("zxd");
int allNumber=fis.available();
System.out.println(allNumber);
byte [] bytes = new byte[allNumber];
int number =fis.read(bytes);
System.out.println(new String(bytes));
6
abcdef
這樣就可以通過avaliable方法獲得文件大小,然後再用數組進行聲明,直接用read方法進行讀取,就不會浪費,也不會因爲空間太小了。
(3)skip跳過幾個字節不讀取
- long
- skip(long n)
- 跳過並從輸入流中丟棄 n字節的數據。
FileInputStream fis = null;
fis = new FileInputStream("zxd");
fis.skip(3);
System.out.println(fis.read());
跳過3個正好讀取的是d,也就是100了。
100
(二)FileoutputStream
(1)Wirte()方法進行寫
void
write(byte[] b)
將
b.length
個字節從指定的字節數組寫入此文件輸出流。
void
write(byte[] b, int off, int len)
將
len
字節從位於偏移量off
的指定字節數組寫入此文件輸出流。
也可以全寫進去,也可以只寫一部分。
FileOutputStream fot = null;
try {
fot = new FileOutputStream("myfile");
byte [] bytes = {97,98,99,100};
fot.write(bytes);
/*一定要進行刷新*/
fot.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fot.close();
} catch (IOException e) {
e.printStackTrace();
}
}
這時候,我們會看見idea裏面多了一個myfile文件,裏面還有abcd
如果沒有myfile文件會新建,如果有則直接輸入。輸入的時候會把源文件情況,再輸入。所以謹慎使用。
下面這種方式是以追加的方式在末尾寫入,不會情況源文件內容。
FileOutputStream(String name, boolean append)
創建文件輸出流以指定的名稱寫入文件。
這時候我們會發現myfile文件中追加了abcd
將字符串轉換成數組的形式進行追加
三、FileReader、FileWriter
文件字符輸入流,只能讀取普通文本,讀取文本內容時,比較方便,快捷。這次就變成了char[]數組了。他們兩個的方法和FileInputStream和FinleoutputStream一樣。
四、帶有緩衝區的字符流(這時候就不需要char byte數組了,自帶有緩衝區)
(一)BufferedReader
帶有緩衝區的字符輸入流。使用這個流的時候,不需要自定義char數組,或者說不需要自定義byte數組。自帶緩衝。
從構造方法可以看出,如果要實例一個BufferedReader,裏面需要傳入一個流。
FileReader reader = new FileReader("zxd");
/**/
BufferedReader br = new BufferedReader(reader);
/*關閉流*/
br.close();
當一個流的構造方法中,需要一個流的時候,被傳進來的流叫做節點流。外部負責包裝的流,叫做:包裝流。還有一個名字叫做:處理流。
從源代碼可以看出,當我們關閉流的時候,只需要關閉最後一個即可。
bufferedReader.readLine()能讀一行。但是不帶換行符。
(a)如果字節流想傳進入怎麼辦?
這時候需要通過InputStreamReader
這時候報錯,的原因是字節流不能作爲BufferedReader的參數,所以這時候需要InputStreamReader
(1) readline()
FileReader reader = new FileReader("zxd");
/**/
BufferedReader br = new BufferedReader(reader);
String s1 =br.readLine();
System.out.println(s1);
/*關閉流*/
br.close();
abcdef
readline()讀取直接讀取一行。
接下來,我們可以對它進行循環。
String s = null;
while ((s=br.readLine())!=null){
System.out.println(s);
}
但是readline()不帶換行符。
(2)當我們想往BufferedReader中傳入字節流可以嗎?(FileInputStream)
可以看出,是不可以的。
這個時候需要使用轉換流了。(將字節流轉換成字符流)
java.io.InputStreamReader
java.io.outputStreamWriter
InputStreamReader(InputStream in)
創建一個使用默認字符集的InputStreamReader。
通過 構造方法可以看出InputStreamReader裏面可以傳入InputStream的。
這個時候就不報錯了。
(二)BufferedWriter
FileWriter f1 = new FileWriter("zjy");
BufferedWriter br = new BufferedWriter(f1);
/*進行文件的寫工作*/
br.write("hahah");
/*刷新*/
br.flush();
/*關閉包裝流/
*/
br.close();
當然,你想追加,可以構造方法哪裏寫true。同樣,如果想用字節流(Inputoutstream)也是需要轉換流的。(outputStreamReader)
五、PrintStream標準的字節輸出流(默認輸出到控制檯)
我們平時輸出的:
System.out.println("Hello World");
其實,就是這個字節輸出流
PrintStream ps = System.out
ps.println("Hello World");
當然它不需要關閉close().
那麼可以輸出改變方向嗎?
/*指向一個日誌文件*/
PrintStream out = null;
try {
out = new PrintStream(new FileOutputStream("log.txt",true));
/*改變輸出方向*/
System.setOut(out);
/*日期當前時間*/
Date nowTime = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(nowTime);
System.out.println(strTime +":"+msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
public class LogTest {
public static void main(String[] args) {
/*測試工具類*/
Logger.log("調用gc,啓動垃圾回收");
Logger.log("用戶嘗試進行登錄");
}
}
可以,以上就是日誌工具的實現原理。
六、對象專屬的流ObjectInputStream、ObjectOutputStream
(1)序列化ObjectOutputStream
對象的序列化(Serialize)和反序列化(DeSerialize)
將內存中的Java對象放到硬盤文件上叫序列化,是因爲內存中的Java對象切割成一塊一塊的並且編號,放到硬盤文件上。
如果從硬盤文件恢復到內存中,那麼叫反序列化。
一個是拆分一個是組裝。
如果需要序列化就是需要ObjectOutputStream了。如果反序列化就是需要ObjectInputStream了。
下面用個例子說明一下:
首先寫一個Student類
public class Student {
private int no;
private String name;
public Student(int no, String name) {
this.no = no;
this.name = name;
}
public Student(int no) {
this.no = no;
}
public Student(String name) {
this.name = name;
}
public Student() {
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) throws Exception{
/*進行序列化例子*/
Student s1 = new Student(1,"趙曉東");
/*通過流去給一個文件students爲文件的名字*/
FileOutputStream f1 = new FileOutputStream("Students");
/*調用ObjectOutputStream*/
ObjectOutputStream o1 = new ObjectOutputStream(f1);
/*調用方法進行序列化*/
o1.writeObject(s1);
/*刷新*/
o1.flush();
/*關閉流*/
o1.close();
}
}
Exception in thread "main" java.io.NotSerializableException: com.javase.IO.Student
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.javase.IO.ObjectOutputStreamTest.main(ObjectOutputStreamTest.java:16)
這時候我們看見會報錯。這個異常的意思是Student不支持序列化。然後我們讓student去實現Serializable
這時候會有Students文件,並且是亂碼的形式
所以參與序列化和反序列化的對象必須實現Serializable接口
這個接口當中什麼都沒有,那麼它起到什麼作用呢?起到了標識的作用,JVM看見了它會特殊待遇。JAVA中有兩種接口,一種是普通接口,另一種是標誌接口,標識接口是給JVM虛擬機看的。
JVM看到Serializable會自動生成一個序列化版本號。
(2)反序列化ObjectInputStream
public static void main(String[] args) throws Exception{
/*創建流讀取文件*/
FileInputStream f1 = new FileInputStream("Students");
/*進行反序列化*/
ObjectInputStream o1 = new ObjectInputStream(f1);
/*調用方法*/
Object ob =o1.readObject();
System.out.println(ob);
o1.close();
}
}
(3)能不能一次序列化多個對象
可以,可以將序列化放到集合當中,序列化集合。
User類
public class User implements Serializable {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
序列化
public static void main(String[] args) throws Exception{
/*創建集合*/
List l1 = new ArrayList<>();
l1.add(new User(1,"哈哈哈"));
l1.add(new User(2,"呵呵呵"));
l1.add(new User(3,"嘿嘿嘿"));
// 通過流去讀取
FileOutputStream f1 = new FileOutputStream("Users");
// 序列化
ObjectOutputStream o1 = new ObjectOutputStream(f1);
// 調用序列化方法
o1.writeObject(l1);
o1.flush();
o1.close();
}
結果
(4)transient表示不參加序列化操作
private transient String name;
(5)關於序列化版本號
Java虛擬機看到Serializable接口之後,會自動生成了一個序列化版本號,這裏沒有手動手寫處理,java虛擬機會默認提供這個序列化版本號。
java語言中採用什麼機制來區分類的?
第一:首先通過類名進行區分,如果類名不一樣,肯定不是一個類。
第二:如果類名一樣,再怎麼進行類的區別?靠序列化版本號進行區分。
這種自動生成序列化版本號有什麼缺陷?
一旦自動生成的序列化版本號缺點是:一旦代碼確定之後,不能進行後續的修改,因爲只要修改,必然會重新編譯,此時會生成全新的序列化版本號,這個時候java虛擬機會認爲這是一個全新的類。
最終結論:
凡是一個類實現了Serializable接口,建議給該類提供一個固定不變的序列化版本號,這樣,以後這個類即使代碼修改了,但是版本號不變,java虛擬機會認爲是同一個類。
所以建議將序列化版本號手動的寫出來,不建議自動生成。
private static final long serialVersionUID=1L;
總結:記得在一級的時候,不知道序列化是幹什麼的,不知道怎麼去讀取學生的文件的,現在終於是明白了。另外我發現流在讀取的時候和集合的遍歷很相似。