IO流+File類
File類
講IO流之前先來講以下File類。Java的標準庫Java.io提供了File類來操作文件和目錄。操作可以有:新建、刪除、重命名等,但是不能訪問文件本身的內容,如果想要訪問,需要使用IO流。
新建File對象:
package day01;
import java.io.File;
class Battery{
public static void main(String[] args) {
File a=new File("C:\\Users\\97464\\Desktop\\test\\File1.txt");
File b=new File("C:\\Users\\97464\\Desktop","test\\File1.txt");
/*
1.上面兩種方式創建的FIle對象是一個意思,File類的不同構造方法導致可以有不同的參數。
2.構造File對象的時候,既可以傳入絕對路徑,也可以傳入相對路徑。
3.注意Windows平臺使用\作為路徑分隔符,在Java字符串中需要用\\表示一個\。Linux平臺使用/作為路徑分隔符。傳入相對路徑時,相對路徑前面加上當前目錄就是絕對路徑。
4. .代表當前目錄..代表上級目錄
*/
}
}
獲取文件或者目錄的路徑和名字等操作
package day01;
import java.io.File;
import java.io.IOException;
class Battery{
public static void main(String[] args){
File tt=new File("F:\\test\\test\\..\\hello.txt");
//獲取文件或者目錄的路徑:
System.out.println(tt.getPath()); //返回構造方法傳入的路徑
System.out.println(tt.getAbsolutePath()); //返回絕對路徑
try {
System.out.println(tt.getCanonicalPath());
//返回規范路徑(C:\\Users\\..----->C:\\)
}catch (IOException e){
e.printStackTrace();
}
//獲取文件或者目錄的名字:
System.out.println(tt.getName());
//返回一個用當前文件的絕對路徑構建的File對象;
File gg=tt.getAbsoluteFile();
System.out.println(gg.getName());
//返回文件或目錄的父級目錄:
System.out.println(tt.getParent());
//重命名文件或目錄:
tt.renameTo(new File("F:\\test\\test\\..\\hello2.txt"));
}
}
/*運行結果為:
F:\test\test\..\hello.txt
F:\test\test\..\hello.txt
F:\test\hello.txt
hello.txt
hello.txt
F:\test\test\..
*/
/*----------------補充:--------------------------------
上面的getCanonicalPath()方法,如果去查看它的源碼可以發現它拋出了IOException異常,如果想要使用它,需要在調用的時候try...catch捕獲異常,或者由main()繼續拋出異常,交給JVM處理。
*/
文件檢測
package day01;
import java.io.File;
class Battery{
public static void main(String[] args){
File a=new File("F:\\test\\hello2.txt");
//判斷文件或者目錄是否存在:
System.out.println(a.exists());
//判斷文件是否可讀或可寫:
System.out.println(a.canRead());
System.out.println(a.canWrite());
//判斷當前File對象是不是文件或者目錄:
System.out.println(a.isFile());
System.out.println(a.isDirectory());
}
}
/*運行結果為:
true
true
true
true
false
*/
獲取文件大小和文件的最后修改時間
package day01;
import java.io.File;
class Battery{
public static void main(String[] args){
File a=new File("F:\\test\\hello2.txt");
System.out.println(a.length()); //返回文件的大小,以字節為單位
System.out.println(a.lastModified()); //返回最后修改時間,是一個毫秒數
}
}
/*運行結果為:
11
1580719765617
*/
新建和刪除文件或目錄
package day01;
import java.io.File;
import java.io.IOException;
class Battery{
public static void main(String[] args){
File a=new File("F:\\test\\hello.txt");
if(!a.exists()){ //判斷文件是否不存在
try{
a.createNewFile();
}catch (IOException e){
e.printStackTrace();
}
}
a.delete(); //刪除文件
File b=new File("F:\\test2");
b.mkdir(); //創建單層目錄
File c=new File("F:\\test3\\kobe\\number24");
c.mkdirs();//創建多層目錄
}
}
遍歷目錄下的文件和子目錄
package day01;
import java.io.File;
class Battery{
public static void main(String[] args){
File d=new File("F:\\test3");
System.out.println(d.list().getClass());
//可以看到d.list()返回的是數據類型是class [Ljava.lang.String;
//[:表示返回的類型是數組,L:表示數組元素是一個對象實例
// Java.land.String表示對象是String類型
//下面進行循環輸出目錄下的文件或者子目錄:
String []strArr=d.list();
for (String i:strArr){
System.out.println(i);
}
File e=new File("F:\\test3");
File [] arrFile=e.listFiles();
//e.listFiles()返回test3目錄下的文件或子目錄的file對象
for (File i: arrFile){
System.out.println(i);
}
}
}
/*運行結果為:
class [Ljava.lang.String;
kobe
F:\test3\kobe
*/
上面遍歷文件和子目錄是單層遍歷,如果想要進行多層遍歷(子目錄的子目錄也會被遍歷),可以進行遞歸遍歷。
案例:
package day01;
import java.io.File;
class Rabbit{
public void fds(File a){
if(a.isFile()){
System.out.println(a.getName());
}else {
System.out.println(a.getName());
File []fileArr=a.listFiles();
for (File i:fileArr){
fds(i); //遞歸
}
}
}
}
class RunClass{
public static void main(String[] args) {
Rabbit test=new Rabbit();
File tf=new File("F:\\test3");
test.fds(tf);
}
}
IO流
IO概念:
IO是指Input/Output,即輸入和輸出,以內存為中心:
Input指從外部讀入數據到內存。----例如把文件從磁盤讀取到內存,從網絡讀取數據到內存等
Output指把數據從內存輸出到外部。----例如把數據從內存寫入文件,把數據從內存輸出到網絡等
為什么要以內存為中心?
------因為數據被讀取到內存中才能被處理,代碼是在內存中運行的,數據也必須內存。
IO流概念:
IO流是一種順序讀寫數據的模式,它的特點是單向流動。
------例如: 想要把一張圖片放入一個文件夾,不能整張直接塞進去,而是需要把圖片轉化為一個數據集(例如二進制),把這些數據一點一點傳到文件夾,這個數據的傳遞類似于自來水在水管中的流動,稱為IO流。
同步和異步
同步IO是指,讀寫IO時代碼必須等待數據返回后才繼續執行后續代碼,它的優點是代碼編寫簡單,缺點是CPU執行效率低。
而異步IO是指,讀寫IO時僅發出請求,然后立刻執行后續代碼,它的優點是CPU執行效率高,缺點是代碼編寫復雜。
Java標準庫的包java.io提供了所有的同步IO,而java.nio則是異步IO。
下面我們即將討論的InputStream、OutputStream、Reader和Writer都是同步IO的抽象類的具體實現類
流的分類
按操作數據單位不同分為:字節流(8 bit),字符流(16 bit)
按數據流的流向不同分為:輸入流,輸出流
按流的角色不同分為:節點流,處理流
抽象基類
字節流
字符流
輸入流
InputStream
Reader
輸出流
OutputStream
Writer
Java的IO流共有40多個相關類,實際上都是從上面的四種抽象基類派生的。
字節流
IO流以byte(字節)為最小單位,稱為字節流。字節流非常通用,不僅可以用來操作字符型文檔,還可以操作任何地其它類型文件(圖片、壓縮包等),因為字節流本身使用地就是二進制。
╔════════════╗
║ Memory ║ 內存
╚════════════╝
▲
│0x48
│0x65
│0x6c
│0x6c
│0x6f
│0x21
╔═══════════╗
║ Hard Disk ║ 硬盤
╚═══════════╝
//上面,內存從硬盤(磁盤)讀入了6個字節的數據,是按順序讀入的,是輸入字節流。
//反過來就是輸出字節流:
╔════════════╗
║ Memory ║ 內存
╚════════════╝
│0x21
│0x6f
│0x6c
│0x6c
│0x65
│0x48
▼
╔═══════════╗
║ Hard Disk ║ 硬盤
╚═══════════╝
InputStream
InputStream是Java標準庫提供的最基本的輸入字節流。位于Java.io這個包里。InputStream是一個抽象類,是所有輸入流的父類,這個抽象類定義的最重要的方法是int read()。源代碼如下:
public abstract int read() throws IOException;
構建InputStream對象:
import java.io.IOException;
import java.io.InputStream;
class MainClass{
public static void main(String[] args) {
InputStream is=new InputStream() {
@Override //可以看到新建InputStream輸入流對象必須重寫抽象方法int read()
public int read() throws IOException {
return 0;
}
};
}
}
read()這個方法會讀取輸入流的下一個字節,并返回字節表示的int值(0-255)(ascii碼),讀到末尾就會返回-1
read()方法還可以傳遞參數——read(byte [] b),作用是一次讀取一個字節數組,把輸入流讀取到的內容存放到這個字節數組中,并返回存放到數組的內容的字節數,同樣是到末尾就返回-1。byte數組是作為一個緩沖區,每次存入數組大小為byte.length的數據,存入的數據是一個int
FileInputStream
FileInputStream是InputStream的一個子類,用來從文件中讀取數據。FileInputStream類的構造方法有:
FileInputStream(File file): 傳遞一個文件的File類對象
FileInputStream(String name): 傳遞一個String類型的文件路徑
案例1: int read()的使用
F:\\test\\hello.txt的文件內容:
abclove
package day01;
import java.io.FileInputStream;
import java.io.IOException;
class MainClass{
public static void main(String[] args) throws IOException { //所有與IO操作相關的代碼都必須正確處理IOException
FileInputStream fis=new FileInputStream("F:\\test\\hello.txt"); //創建流對象
for(;;){ //無限循環
int n = fis.read(); //反復調用read()方法,直到n=-1
if (n==-1){
break;
}
System.out.print(n+":"); //打印read()返回的byte值
System.out.println((char)n);
}
fis.close(); //關閉流,釋放對應的底層資源
}
}
/*運行結果為
97:a
98:b
99:c
108:l
111:o
118:v
101:e
*/
//從運行結果可知,read()返回的單字節整數值,是對應字符的acsii碼。通過(char)就可以轉換為對應字符。
案例2:int read(byte [] b)的使用(緩沖)
package day01;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
class MainClass{
public static void main(String[] args) throws IOException {
InputStream cup=new FileInputStream("F:\\test\\hello.txt");
byte [] b=new byte[3];
//b數組是作為一個緩沖區,在下面的循環中,每循環一次,b就會更新一次,輸入流把讀取到的內容放到b中
int len=0; //初始化len為0
while((len=cup.read(b))!=-1){ //len代表存放到數組b的數據的字節數
System.out.print(len+":");
System.out.println(new String(b,0,len)); //用法看下面的補充
}
cup.close();
}
}
/*運行結果為:
3:abc
3:lov
1:e
*/
/*-------------------------補充:----------------------------------------
String類的構造方法public String(byte bytes[], int offset, int length)的用法:
作用:將字節數組的某部分轉為字符串
參數:byte bytes[]表示要被轉的數組
int offset表示偏移量,即從數組的第幾個位置開始轉為字符串,
int length表示總共要轉化的字節數
read()方法是阻塞的(blocking)。讀取IO流相比執行普通代碼,速度要慢很多。
package day01;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
class MainClass{
public static void main(String[] args) throws IOException {
InputStream a=new FileInputStream("F:\\test\\hello.txt");
System.out.println("kobe");
int b=a.read(); // 必須等待read()方法返回才能執行下一行代碼
System.out.println("gigi");
}
}
OutputStream
OutputStream是Java標準庫提供的最基本的輸出流。OutputStream和InputStream一樣,也是抽象類,是所有輸出流的父類。抽象類定義的一個重要的方法是void write(int b),源代碼如下:
public abstract void write(int b) throws IOException;
write()還有重載方法:
write(byte b[]):用于一次性寫入多個字節
write(byte b[], int off, int len):將數組b從off位置開始,把長度為len字節的數據寫入到文件中
和InputStream一樣,OutputStream的write()方法也是阻塞的。
FileOutputStream
FileOutputStream是OutputStream的子類。FileOutputStream類的構造方法有:
FileOutputStream(File file): 傳遞一個文件的File類對象
FileOutputStream(String name): 傳遞一個String類型的文件路徑
案例1:一次寫入一個字節
package day01;
import java.io.*;
class MainClass{
public static void main(String[] args) throws IOException {
OutputStream ops=new FileOutputStream("F:\\test\\outPut.txt");
ops.write(97); //往文件寫入97ascii碼代表的字符a
ops.write(98); //往文件寫入98ascii碼代表的字符b
ops.write(99); //往文件寫入99ascii碼代表的字符c
ops.flush();
ops.close();
}
}
//運行結果是:往F:\\test\\outPut.txt這個文件寫入“abc",如果這個文件不存在,則會新建文件。
從案例1可以看到,OutputStream還提供了一個flush()方法,它的目的是將緩沖區的內容真正輸出到目的地。
為什么要有flush()?因為向磁盤、網絡寫入數據的時候,出于效率的考慮,操作系統并不是輸出一個字節就立刻寫入到文件或者發送到網絡,而是把輸出的字節先放到內存的一個緩沖區里(本質上就是一個byte[]數組),等到緩沖區寫滿了,再一次性寫入文件或者網絡。對于很多IO設備來說,一次寫一個字節和一次寫1000個字節,花費的時間幾乎是完全一樣的,所以OutputStream有個flush()方法,能強制把緩沖區內容輸出。
通常情況下,我們不需要調用這個flush()方法,因為緩沖區寫滿了OutputStream會自動調用它,并且,在調用close()方法關閉OutputStream之前,也會自動調用flush()方法。
但是,在某些情況下,我們必須手動調用flush()方法。舉個例子:
小明正在開發一款在線聊天軟件,當用戶輸入一句話后,就通過OutputStream的write()方法寫入網絡流。小明測試的時候發現,發送方輸入后,接收方根本收不到任何信息,怎么肥四?
原因就在于寫入網絡流是先寫入內存緩沖區,等緩沖區滿了才會一次性發送到網絡。如果緩沖區大小是4K,則發送方要敲幾千個字符后,操作系統才會把緩沖區的內容發送出去,這個時候,接收方會一次性收到大量消息。
解決辦法就是每輸入一句話后,立刻調用flush(),不管當前緩沖區是否已滿,強迫操作系統把緩沖區的內容立刻發送出去。
實際上,InputStream也有緩沖區。例如,從FileInputStream讀取一個字節時,操作系統往往會一次性讀取若干字節到緩沖區,并維護一個指針指向未讀的緩沖區。然后,每次我們調用int read()讀取下一個字節時,可以直接返回緩沖區的下一個字節,避免每次讀一個字節都導致IO操作。當緩沖區全部讀完后繼續調用read(),則會觸發操作系統的下一次讀取并再次填滿緩沖區。
案例2:一次性寫入若干字節
package day01;
import java.io.*;
/**
* 一次性寫入若干字節,通過void write(byte [])來實現
*/
class MainClass{
public static void main(String[] args) throws IOException {
OutputStream ops=new FileOutputStream("F:\\test\\outPut.txt");
ops.write("hello Krystal".getBytes("utf-8") );
ops.flush();
ops.close();
}
}
/*----------------------補充:------------------------------------------
String.getBytes(String decode)方法會根據指定的decode編碼返回某字符串在該編碼下的byte數組
*/
案例3:復制文件
package day01;
import java.io.*;
class MainClass{
public static void main(String[] args) throws IOException {
Stero st=new Stero();
st.fun1("F:\\test\\outPut.txt","F:\\test\\outPut2.txt");
//把outPut.txt復制為outPut2.txt
}
}
class Stero{
void fun1(String intputPath,String outputPath) throws IOException {
InputStream ips=new FileInputStream(intputPath);
OutputStream ops=new FileOutputStream(outputPath);
byte[]a=new byte[3];
int len;
while((len=ips.read(a)) != -1){
ops.write(a,0,len);
}
ops.close(); //先關閉輸出流
ips.close(); //后關閉輸入流
//最早開的最晚關
}
}
正確關閉流
上面的流的案例,存在一個潛在的問題,如果讀取或者寫入過程中,發生了IO錯誤,流就沒法及時關閉,資源也無法釋放。 Java7引入了新的try(resource)語法,只需要編寫try語句,就可讓編譯器自動為我們關閉資源。
案例:自動正確地關閉流
class Cable{
public static void main(String[] args) throws IOException{
//把新建流對象地語句放在try()里面即可
try(FileInputStream sf=new FileInputStream("F:\\test\\hello.txt")){
int n;
while((n=sf.read())!=-1){
System.out.println((char)n);
}
}
}
}
字符流
如果我們需要讀寫的是字符(不是圖片什么的),并且字符不全是單字節表示的ASCII字符,那么按照char來讀寫更方便,這種流稱為字符流。字符流傳輸的最小數據單位是char,Java提供了Reader和Writer兩個基類來操作字符流。
Reader和Writer本質上是一個能自動編解碼的InputStream和OutputStream。
使用Reader,數據源雖然是字節,但我們讀入的數據都是char類型的字符,原因是Reader內部把讀入的byte做了解碼,轉換成了char。使用InputStream,我們讀入的數據和原始二進制數據一模一樣,是byte[]數組,但是我們可以自己把二進制byte[]數組按照某種編碼轉換為字符串。究竟使用Reader還是InputStream,要取決于具體的使用場景。如果數據源不是文本,就只能使用InputStream,如果數據源是文本,使用Reader更方便一些。Writer和OutputStream是類似的。
Reader
Reader是Java的IO庫提供的另一個輸入流接口。和InputStream的區別是,InputStream是一個字節流,即以byte為單位讀取,而Reader是一個字符流,即以char為單位讀取,Reader是所有字符輸入流的父類。Reader類的主要方法是int read(),源代碼是:
public int read() throws IOException;
FileReader
FileReader是Reader的子類,FileReader的用法和FileInputStream的用法極其相似。
案例1:
//hello.txt的內容如下(注意編碼格式要為utf-8,要不然會出錯,另存為就可以改編碼格式)
武漢,加油。
China,add oil.
package day01;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
class RunClas{
public static void main(String[] args) {
Water w=new Water();
try {
w.fun1("F:\\test\\hello.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Water{
public void fun1(String rPath) throws IOException {
try(Reader fr=new FileReader(rPath)){
char[] a=new char[4]; //創建用于緩沖的字符數組
int len;
while((len=fr.read(a))!=-1){
System.out.println(new String(a,0,len));
}
}
}
}
Writer
Reader是帶編碼轉換器的InputStream,它把byte轉換為char,而Writer就是帶編碼轉換器的OutputStream,它把char轉換為byte并輸出。
Writer是所有字符輸出流的超類,它提供的方法主要有:
寫入一個字符(0~65535):void write(int c);
寫入字符數組的所有字符:void write(char[] c);
寫入String表示的所有字符:void write(String s)。
FileWriter
FileWriter是Writer的一個子類。FileWriter的用法和FileOutputStream的用法很相似。
案例1:使用void write(String s)方法
package day01;
import java.io.*;
class RunClas {
public static void main(String[] args) {
Desk d=new Desk();
try {
d.funF("F:\\test\\FileWriterOutput.txt","Hello,krystal,i'm missing you.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Desk {
public void funF(String oPath, String content) throws IOException {
try (Writer a = new FileWriter(oPath)) {
a.write(content);
a.flush();
a.close();
}
}
}
案例2:用字符流進行復制文件
package day01;
import java.io.*;
class RunClas {
public static void main(String[] args) throws IOException {
Desk d = new Desk();
d.funF("F:\\test\\hello.txt", "F:\\test\\hello3.txt");
}
}
class Desk {
public void funF(String iPath, String oPath) throws IOException {
Reader ra = new FileReader(iPath);
Writer wa = new FileWriter(oPath);
char[] charArr = new char[100];
int len;
while ((len = ra.read(charArr)) != -1) {
wa.write(charArr);
}
wa.flush();
wa.close();
}
}
流對文件的操作注意事項:
在寫入一個文件時,目錄下的同名文件會被覆蓋
在讀取一個文件時,必須保證改文件是存在的,否則報異常
緩沖流
為了提高 數據讀寫的速度,Java提供了帶緩沖功能的流類,在使用這些流類時,會創建一個內部緩沖區數組。(基于內存的)。
BufferedInputStream-->FileInputStream
BufferedOutputStream-->FileOutputStream
BufferedReader-->FileReader
BufferedWriter-->FileWriter
緩沖流先把數據緩沖到內存里,然后在內存中做io操作,基于內存的io操作比基于硬盤的操作快7500倍。
案例1:BufferedInputStream的使用
package day01;
import java.io.*;
class ExaF{
public static void main(String[] args) throws IOException {
//創建File流對象:
FileInputStream a=new FileInputStream("F:\\test\\hello.txt");
//創建緩沖流對象:
BufferedInputStream b=new BufferedInputStream(a); //需要傳入FIle流作為參數
byte[] bArr=new byte[100];
int len;
while((len=b.read(bArr))!=-1){
System.out.println(new String(bArr,0,len));
}
}
}
案例2:BufferedOutputStream的使用
package day01;
import java.io.*;
class ExaF{
public static void main(String[] args) throws IOException {
FileOutputStream a=new FileOutputStream("F:\\test\\BOutput.txt");
BufferedOutputStream b=new BufferedOutputStream(a);
b.write("I love you,krystal".getBytes());
b.flush();
b.close();
}
}
案例3:使用字節流+緩沖流實現文件復制
package day01;
import java.io.*;
class ExaF{
public static void main(String[] args) throws IOException {
//創建File流對象:
FileInputStream ia=new FileInputStream("F:\\test\\hello.txt");
FileOutputStream oa=new FileOutputStream("F:\\test\\Chello.txt");
//創建緩沖流對象:
BufferedInputStream ib=new BufferedInputStream(ia);
BufferedOutputStream ob=new BufferedOutputStream(oa);
byte[] bArr=new byte[100];
int len;
while((len=ib.read(bArr))!=-1){
ob.write(bArr);
}
ob.flush();
ob.close();
ib.close();
oa.close();
ia.close();
}
}
案例4:BufferedReader的使用
package day01;
import java.io.*;
class ExaB{
public static void main(String[] args) throws IOException {
FileReader a=new FileReader("F:\\test\\hello.txt");
BufferedReader b=new BufferedReader(a);
char []cArr=new char[100];
int len;
while((len=b.read(cArr))!=-1){
System.out.println(new String(cArr,0,len));
}
b.close();
a.close();
}
}
案例5:BufferedWriter的使用
package day01;
import java.io.*;
class ExaB{
public static void main(String[] args) throws IOException {
FileWriter a=new FileWriter("F:\\test\\brout.txt");
BufferedWriter b=new BufferedWriter(a);
b.write("Hello,krystal!");
b.flush();
b.close();
a.close();
}
}
案例6:使用字符流+緩沖流實現文件的復制
package day01;
import java.io.*;
class ExaB{
public static void main(String[] args) throws IOException {
BufferedReader br=new BufferedReader(new FileReader("F:\\test\\hello.txt"));
BufferedWriter bw=new BufferedWriter(new FileWriter("F:\\test\\em.txt"));
char [] cArr=new char[100];
int len;
while((len=br.read(cArr))!=-1){
bw.write(cArr);
}
bw.flush();
bw.close();
br.close();
}
}
轉換流
轉換流是指InputStreamReader和OutputStreamWriter,上面講了,流的數據都是字符時,轉成字符流更高效,那么轉換流就是用來將字節流轉換成字符流的,并且可以指定字符集的編碼解碼格式。
案例1:轉換字節輸入流為字符輸入流
//"F:\\test\\aa.txt"文件的編碼為GBK,文件內容如下:
中國我愛你。
I love you,China.
package day01;
import java.io.*;
class Bear{
public static void main(String[] args) throws IOException {
//創建文件字節輸入流對象
FileInputStream fis=new FileInputStream("F:\\test\\aa.txt");
//創建轉換流對象
InputStreamReader isr=new InputStreamReader(fis,"utf-8");//指定字符集編碼
char []cArr=new char[10];
int len;
while((len=isr.read(cArr))!=-1){
System.out.println(new String(cArr,0,len));
}
isr.close();
fis.close();
}
}
/*運行結果為:
��?��?
I love you
,China.
*/
/*出現了亂碼,是因為代碼中指定的字符集編碼與讀取的文件的數據的編碼格式不一致,
指定編碼的一行代碼改成:InputStreamReader isr=new InputStreamReader(fis,"GBK");即可避免亂碼*/
案例2:轉換字節輸出流為字符輸出流
package day01;
import java.io.*;
class Bear{
public static void main(String[] args) throws IOException {
FileOutputStream fos=new FileOutputStream("F:\\test\\ors.txt");
OutputStreamWriter osw=new OutputStreamWriter(fos,"utf-8");
osw.write("中國,我愛你");
osw.flush();
osw.close();
fos.close();
}
}
標準輸入輸出流
System.out和System.in是系統標準的輸入和輸出設備(鍵盤和顯示器)
System.in的類型是InputStream
System.out的類型是PrintStream。
案例1:創建一個接受鍵盤輸入的標準輸入流
package day01;
import java.io.*;
class Bear{
public static void main(String[] args) throws IOException {
RedPap rd=new RedPap();
rd.fun();
}
}
class RedPap{
public void fun() throws IOException {
//創建接受鍵盤輸入的輸入流
InputStreamReader isr=new InputStreamReader(System.in);
//把輸入流放到緩沖流里:
BufferedReader bfr=new BufferedReader(isr);
//創建臨時接受數據的字符串:
String str="";
while((str=bfr.readLine())!=null){ //readLine()是緩沖輸入字符流提供的按行讀取終端數據的方法,每一次調用會以字符串形式返回一行內容,讀取完會返回null
System.out.println(str);
}
}
}
//運行結果如下圖:
案例2:將控制臺的輸入內容輸出到文件krystal.txt中,控制臺出現”over“時結束輸出。
package day01;
import java.io.*;
class Bear{
public static void main(String[] args) throws IOException {
Krystal cf=new Krystal();
cf.fun("F:\\test\\Krystal.txt");
}
}
class Krystal{
public void fun(String kPath) throws IOException {
BufferedReader bfr=new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bfw=new BufferedWriter(new FileWriter(kPath));
String str="";
while((str=bfr.readLine())!=null){
if(str.equals("over")){
break;
}
bfw.write(str);
}
bfw.close();
bfr.close();
}
}
序列化與反序列化
首先思考兩個問題:
1、如果想把一個類的實例化對象存到電腦的硬盤上,要怎么做?
硬盤存儲的基礎是二進制,需要把對象轉化為一個二進制的字節流,然后把流保存到硬盤上。如果要用存到硬盤里的對象,又得把流轉化為對象再使用。
2、如果想把一個對象通過網絡傳到另一臺機器上,要怎么做?
網絡通信的基礎也是二進制,需要把對象轉化為二進制的數據流,然后通過網絡傳輸流。接收者如果想要使用接收的對象得先把對象的流轉為對象。
因為上面兩類問題的存在,產生了對象的輸入與輸出流。
序列化(Serialize):序列化是指把一個Java對象變成二進制內容,本質上就是一個byte[]數組,用ObjectOutputStream類將一個對象寫入IO流中。
反序列化(Deserialize):用ObjectInputStream類從IO流中恢復對象。
注意事項:
序列化和反序列化針對的是實例化對象的各種屬性,不包括類的屬性。
一個Java對象要能序列化,必須實現一個特殊的java.io.Serializable接口
實現Serializable接口的類的對象的是可序列化的
Serializable接口沒有定義任何方法,它是一個空接口。
我們把這樣的空接口稱為“標記接口”(Marker Interface),實現了標記接口的類僅僅是給自身貼了個“標記”,并沒有增加任何方法。
案例1:
package day01;
import java.io.*;
class AbleObj implements Serializable {
String name;
int age;
String school;
double weigth;
}
class ObjOut{ //序列化類
public void funO(String oosPath) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(oosPath)); //定義對象輸出流
AbleObj ao=new AbleObj();
ao.name="krystal";
ao.age=20;
ao.school="SongSanHu";
oos.writeObject(ao);
oos.flush();
oos.close();
}
}
class ObjIn{ //反序列化類
public void funI(String oisPath) throws IOException, ClassNotFoundException {
ObjectInputStream ois =new ObjectInputStream(new FileInputStream(oisPath));
Object a=ois.readObject();
AbleObj b=(AbleObj)a; //強制轉換為AbleObj類型
/*對象的序列化和反序列化使用的類要嚴格一致,序列化是什么類反序列化就用什么類,
包名、類名、類結構等等所有都要一致。*/
System.out.println(b.name);
System.out.println(b.school);
ois.close()
}
}
public class Test01{ //運行類
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjOut tt=new ObjOut();
tt.funO("F:\\test\\KrystalInfo.txt");
ObjIn kk=new ObjIn();
kk.funI("F:\\test\\KrystalInfo.txt");
}
}
/*運行結果為:
krystal
SongSanHu
*/
安全性
因為Java的序列化機制可以導致一個實例能直接從byte[]數組創建,而不經過構造方法,因此,它存在一定的安全隱患。一個精心構造的byte[]數組被反序列化后可以執行特定的Java代碼,從而導致嚴重的安全漏洞。
實際上,Java本身提供的基于對象的序列化和反序列化機制既存在安全性問題,也存在兼容性問題。更好的序列化方法是通過JSON這樣的通用數據結構來實現,只輸出基本類型(包括String)的內容,而不存儲任何與代碼相關的信息。
隨機存取流
RandomAccessFile類支持"隨機訪問"的方式,即程序可以直接跳到文件的任意地方進行讀寫操作。RandomAccessFile對象包含一個記錄指針,用來標記當前讀寫開始位置,通過void seek(long pos)方法可以將記錄指針定位到pos位置。
案例1:隨機訪問文件(任意位置讀取)
//F:\\test\\AccessTest.txt文件的內容為:
123456789I love you,krystal. Where are you?
package day01;
import java.io.FileInputStream;
import java.io.RandomAccessFile;
public class Test01{
public static void main(String[] args) throws Exception {
RandomIn ii=new RandomIn();
ii.Rfi("F:\\test\\AccessTest.txt");
}
}
class RandomIn{
public void Rfi(String path1) throws Exception{
RandomAccessFile raf=new RandomAccessFile(path1,"rw");
/*參數2是mode模式:
"r":以只讀模式打開文件
"rw":打開以便讀取和寫入(常用)
"rwd":打開以便讀取和寫入,同步文件內容的更新
"rws":打開以便讀取和寫入,同步文件內容和元數據的更新
*/
raf.seek(5); //設置讀取文件內容的起點
byte [] bArr=new byte[100];
int len;
while((len=raf.read(bArr))!=-1){
System.out.println(new String(bArr,0,len));
}
raf.close();
}
}
/*運行結果為:
6789I love you,krystal. Where are you?
*/
案例2:隨機寫入
//F:\\test\\AccessTest.txt文件的內容為:
123456789I love you,krystal. Where are you?
package day01;
import java.io.IOException;
import java.io.RandomAccessFile;
public class Test01{
public static void main(String[] args) throws Exception {
RandomOut oo=new RandomOut();
oo.rof("F:\\test\\AccessTest.txt");
}
}
class RandomOut{
public void rof(String path2) throws IOException {
RandomAccessFile rdaf=new RandomAccessFile(path2,"rw");
rdaf.seek(0);
rdaf.write("Hi,this is Jimmy! ".getBytes());
rdaf.seek(rdaf.length()); //這個相當于給文件末尾追加內容
rdaf.write(" I miss you so much!".getBytes());
rdaf.close();
}
}
/*運行完成后,F:\\test\\AccessTest.txt文件的內容變化如下:
123456789I love you,krystal. Where are you?
Hi,this is Jimmy! u,krystal. Where are you? I miss you so much!
可以看到,在開頭或者中間寫入內容時,會覆蓋掉等長度的原內容。
*/