1. 記憶體操作流
除了檔案之外,
I/O
的操作也可以發生在記憶體之中,這種流被稱為記憶體操作流。檔案流不管最後這個檔案資料是否會被保留都會産生一個檔案資料,而記憶體流是跟記憶體有關的,不需要關聯檔案。如果有個需求是要進行I/O處理,但是又不希望産生臨時檔案,這種情況下就可以使用記憶體流。
1.1 基本使用
記憶體流的分類:
1. 位元組記憶體流:ByteArrayInputStream、 ByteArrayOutputStream
2. 字元記憶體流:CharArrayReader、 CharArrayWriter
- 通過記憶體流實作小寫轉大寫:
package com.file;
import java.io.*;
//将字元串中的字元轉換成大寫,要求使用記憶體流
public class TestMemoryStream {
public static void main(String[] args) {
String message = "hello world";
byte[] messageBytes = message.getBytes();
//記憶體操作,不需要捕獲異常
//in out 都是記憶體流,資料都存在記憶體中
ByteArrayInputStream in = new ByteArrayInputStream(messageBytes);
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
int c = -1;
while((c = in.read()) != -1) {
//轉大寫
c = c-32;
out.write(c);
}
out.flush();
//輸出流裡面沒有把輸出流變成位元組數組的方法,是ByteArray裡面有的
//byte[] newMessage = ((ByteArrayOutputStream) out).toByteArray();
byte[] newMessage = out.toByteArray();
System.out.println(new String(newMessage));
} catch (IOException e) {
}
}
}
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLwQTN5IDMxYTMyEDNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
這個時候發生了
I/O
操作,但是沒有檔案産生,可以了解為一個臨時檔案處理。
1.2 檔案合并
package com.file;
import java.io.*;
public class MemoryStreamMerge {
public static void main(String[] args) {
//data-1.txt + data-2.txt = data.txt
/*步驟:
* 1. data-1.txt 複制到記憶體的輸出流
* 2. data-2.txt 複制到記憶體的輸出流
* 3. 記憶體的輸出流 -> byte[]位元組流
* 4. byte[] -> 輸出到檔案的輸出流
*/
File part1 = new File("D:" + File.separator + "test" + File.separator + "data-1.txt");
File part2 = new File("D:" + File.separator + "test" + File.separator + "data-2.txt");
File part = new File("D:" + File.separator + "test" + File.separator + "data.txt");
try(FileInputStream in1 = new FileInputStream(part1);
FileInputStream in2 = new FileInputStream(part2);
ByteArrayOutputStream out1 = new ByteArrayOutputStream();
FileOutputStream out2 = new FileOutputStream(part)
) {
byte[] buff = new byte[1024];
int len = -1;
while((len = in1.read(buff)) != -1) {
out1.write(buff, 0, len);
}
while((len = in2.read(buff)) != -1) {
out1.write(buff, 0, len);
}
out1.flush();
byte[] data = out1.toByteArray();
out2.write(data);
out2.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 既然是在記憶體中讀寫,記憶體流速度雖然快,但是受制于記憶體大小,适合于處理批量的少量資料。
/**源碼:
* Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
* this class can be called after the stream has been closed without
* generating an <tt>IOException</tt>.
*/
public void close() throws IOException {
}
上面是記憶體流的源碼,看出來記憶體流可以關閉也可以不關閉,因為
close()
裡面沒有任何代碼。
2. 列印流
PrintStream
可以稱之為
OutputStream
的加強版。
OutputStream
隻能放位元組或者位元組數組,如果操作的不是二進制資料,就需要
getBytes
,是以不是很友善,總結下來其缺點有兩個:
① 所有的資料必須轉換為位元組數組。
② 如果要輸出的是
int
或者
double
等類型就不友善了。
2.1 基本使用
列印流設計的主要目的是為了解決
OutputStream
的設計問題,其本質不會脫離
OutputStream
。
package com.file;
import java.io.*;
/**
* 目的:為了OutputStream的輸出更加簡單
* 類似代理模式,但是不完全遵循
* 代碼複用性強
*/
public class PrintUtil {
private final OutputStream out;
public PrintUtil(OutputStream out) {
this.out = out;
}
public void print(String value) {
try {
this.out.write(value.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
public void println(String value) {
this.print(value);
try {
this.out.write((int)'\n');
} catch (IOException e) {
e.printStackTrace();
}
}
public void print(int value) {
this.println(String.valueOf(value));
}
public static void main(String[] args) {
try(FileOutputStream out = new FileOutputStream("D:" + File.separator + "test"
+ File.separator + "printutil.txt")
) {
PrintUtil printUtil = new PrintUtil(out);
printUtil.print("hello");
printUtil.print(" world");
printUtil.print(22);
printUtil.print(10);
/*
* hello world
* 22
* 10
*/
} catch (IOException e) {
e.printStackTrace();
}
}
}
經過簡單處理之後,讓
OutputStream
的功能變的更加強大了,其實本質就隻是對
OutputStream
的功能做了一個封裝而已。
- 但是上面僅僅是我們自己定義的列印流,還不能滿足全部的需求。
2.2 系統提供的列印流
1. 位元組列印流:PrintStream
2. 字元列印流:PrintWriter
列印流的設計屬于裝飾設計模式:核心依然是某個類的功能,但是為了得到更好的操作效果,讓其支援的功能更多一些。
-
不光支援數字、字元、字元串和字元數組等,還可以支援格式化輸出。
格式化輸出:
printf(String format, Object ... args)
package com.file;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
public class TestPrintStream {
public static void main(String[] args) {
String file = "D:" + File.separator + "test"
+ File.separator + "printStream.txt";
//字元的列印流
try(PrintWriter writer = new PrintWriter(file)) {
writer.write("姓名");
writer.write("張三");
writer.write("\n");
writer.write(50);
writer.write("\n");
writer.write(new char[]{'A', 'B', 'C'});
//鍊式調用
writer.append("A").append("B").append("C");
//格式化輸出,String裡面也有個格式化輸出的方法, System.out.printf也是
writer.printf("姓名:%s 年齡:%d 身高:%.2fcm \n", "張三", 22, 180.25F);
String str = String.format("姓名:%s 年齡:%d 身高:%.2fcm", "張三", 22, 180.25F);
writer.println(str);
writer.flush();
} catch(IOException e) {
}
}
}
3. System對I/O的支援
實際上我們一直在使用的系統輸出
System.out.println
就是利用了
I/O
流的模式完成。
/*
System:類
out:靜态屬性,PrintStream類型
println:out屬性的一個對象的方法
*/
System.out.println("hello world");
- 在
System
類中定義了三個操作的常量:
① 标準輸出(顯示器) :
② 錯誤輸出:public final static PrintStream out
③ 标準輸入(鍵盤):public final static PrintStream err
public final static InputStream in
3.1 系統輸出
系統輸出一共有兩個常量:
out
和
err
,并且這兩個常量表示的都是
PrintStream
類的對象:
①
out
輸出的是希望使用者能看到的内容
②
err
輸出的是不希望使用者看到的内容
package com.file;
public class SystemIO {
public static void main(String[] args) {
try {
//String -> Integer
Integer.parseInt("abc");
} catch(NumberFormatException e) {
System.out.println(e.getMessage());
System.err.println(e.getMessage());
}
}
}
但是這兩種輸出在實際的開發之中都沒用了,取而代之的是 “ 日志 ” 。
-
隻是作為一個保留的屬性而存在,現在幾乎用不到。唯一可能用得到的就是System.err
。System.out
- 由于
是System.out
的執行個體化對象,而PrintStream
又是PrintStream
的子類,是以可以直接使用OutputStream
直接為System.out
執行個體化對象,也就是說如果要得到輸出到控制台或螢幕的對象,選擇OutputStream
。System.out
- 使用
為System.out
執行個體化:OutputStream
package com.file;
import java.io.*;
public class SystemIO {
public static void main(String[] args) {
//輸出
PrintStream printStream = System.out;
//PrintStream是OutputStream的子類
OutputStream out = System.out;
try {
out.write("hello".getBytes());
} catch(IOException e) {
e.printStackTrace();
}
}
}
3.2 系統輸入
System.in
對應的類型是
InputStream
,而這種輸入流指的是由使用者通過鍵盤進行輸入(使用者輸入)。java本身并沒有直接的使用者輸入處理,如果要想實作這種操作,必須使用
java.io
的模式來完成。
- 使用
實作資料輸入:System.in
package com.file;
import java.io.*;
public class SystemIO {
public static void main(String[] args) {
//輸入
InputStream in = System.in;
try {
byte[] buff = new byte[5];
int len = in.read(buff);
System.out.println("讀取了"+len+"位元組, 内容是:"+new String(buff, 0, len));
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 以上的程式本身有一個緻命的問題,核心點在于:開辟的位元組數組長度固定,如果現在輸入的長度超過了位元組數組長度,那麼隻能夠接收部分資料。這個時候是由于一次讀取不完所造成的問題,是以此時最好的做法是引入記憶體操作流來進行控制,這些資料先儲存在記憶體流中而後一次取出。
package com.file;
import java.io.*;
public class SystemIO {
public static void main(String[] args) {
InputStream in = System.in;
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
byte[] buff = new byte[5];
int len = -1;
while((len = in.read(buff)) != -1) {
out.write(buff, 0, len);
if(len < buff.length) {
//已經讀到最後一批
break;
}
}
byte[] data = out.toByteArray();
System.out.println("讀取了"+data.length+"位元組, 内容是:"+new String(data));
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 至于為什麼命名輸入的隻有8個位元組,卻顯示讀取了9個位元組的問題,是因為輸入完成之後在鍵盤上敲下回車,所有的鍵盤操作都是會被記錄下來的。
現在雖然實作了鍵盤輸入資料的功能,但是整體的實作邏輯過于混亂了,即java提供的
System.in
并不好用,還要結合記憶體流來完成,導緻複雜度很高。是以也就有了下面兩種輸入流的産生。
4. 兩種輸入流
4.1 BufferedReader類
BufferedReader
類屬于一個緩沖的輸入流,而且是一個字元流的操作對象。
1. 位元組緩沖流:BufferedInputStream
2. 字元緩沖流:BufferedReader
之是以選擇
BufferedReader
類操作是因為在此類中提供有如下方法:
按行讀取,回車換行:
String readLine() throws IOException
- 利用
實作鍵盤輸入:BufferedReader
package com.file;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Buffer {
public static void main(String[] args) {
//位元組輸入流
InputStream inputStream = System.in;
//字元輸入流(位元組流轉字元流)
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
//緩沖的字元輸入流
BufferedReader reader = new BufferedReader(inputStreamReader);
//互動式的反複輸入
while(true) {
System.out.println("請輸入名字:");
try {
String line = reader.readLine();
System.out.println(line);
if(line.equals("quit")) {
break;
}
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
以上操作形式是java十多年前輸入的标準格式,但是時過境遷,這個類也淹沒在曆史的潮流之中,被
JDK1.5
提供的
java.util.Scanner
類所取代。
4.2 java.util.Scanner類
Scanner
是一個專門進行輸入流處理的程式類,利用這個類可以友善處理各種資料類型,同時也可以結合正規表達式進行各項處理,在這個類中主要關注以下方法:
① 判斷是否有指定類型資料:
public boolean hasNextXxx()
② 取得指定類型的資料:
public 資料類型 nextXxx()
③ 定義分隔符:
public Scanner useDelimiter(Pattern pattern)
- 使用
實作資料輸入:Scanner
package com.file;
import java.util.Scanner;
public class TestScanner {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入資料:");
if(scanner.hasNext()) {
System.out.println("輸入的内容:"+scanner.next());
}
}
}
- 接收其他類型資料:
- 使用
還可以接收各種資料類型,并且幫助使用者減少轉型處理。Scanner
package com.file;
import java.util.Scanner;
public class TestScanner {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入年齡:");
if(scanner.hasNextInt()) {
System.out.println("輸入的内容是int");
} else {
System.out.println("輸入的内容不是int");
}
}
}
- 對接收的資料類型使用正規表達式判斷:
package com.file;
import java.util.Scanner;
public class TestScanner {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
//正規表達式
System.out.println("請輸入生日:");
if(scanner.hasNext("\\d{4}-\\d{2}-\\d{2}")) {
System.out.println("輸入的内容:"+scanner.next());
} else {
System.out.println("輸入的内容不是生日格式");
}
}
}
-
讀取檔案操作:Scanner
- 使用
本身能夠接收的是一個Scanner
對象,那麼也就意味着可以接收任意輸入流,例如:檔案輸入流,InputStream
完美的替代了Scanner
,而且更好的實作了BufferedReader
的操作。InputStream
package com.file;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Scanner;
public class TestScanner {
public static void main(String[] args) {
//讀取檔案(針對字元)
try(Scanner scanner = new Scanner(Paths.get("D:", "test", "data.txt"))) {
//比File.separater更友善
scanner.useDelimiter("\n");
while(scanner.hasNext()) {
System.out.println(scanner.next());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
總結:
PrintStream 解決的是 OutputStream 類的缺陷,BufferedReader 解決的是 InputStream 類的缺陷。而 Scanner 則解決的是 BufferedReader 類的缺陷(替換了BufferedReader類)。
5. 序列化與反序列化
- 序列化(Java Object -> byte[]):ObjectOutputStream
- 反序列化(byte[] -> Java Object):ObjectInputStream
5.1 序列化技術
概念:把Java對象變成
byte
數組(二進制流),主要是用于網絡傳輸。
- 一個類的執行個體化對象要能夠進行序列化必須實作序列化(
)接口!Serializable
-
ObjectOutputStream
裡面的方法有:
①
②ObjectOutputStream(OutputStream out)
,寫對象writeObject(Object obj)
package com.serializable;
import java.io.*;
class Person implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestSerializable {
public static void main(String[] args) {
Person person1 = new Person();
person1.setName("張三");
person1.setAge(22);
//序列化
//Java Object -> byte[]
//可以寫到file或者byte[]裡面
try(ByteArrayOutputStream stream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(stream)
) {
out.writeObject(person1);
out.flush();
//data對象變成的二進制流
byte[] data = stream.toByteArray();
System.out.println(new String(data));
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.1 反序列化技術
概念:把
byte
數組轉換成對象。
-
ObjectInputStream
裡面的方法有:
①
②ObjectInputStream(InputStream in)
,讀對象readObject(Object obj)
package com.serializable;
import java.io.*;
class Person implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestSerializable {
public static void main(String[] args) {
Person person1 = new Person();
person1.setName("張三");
person1.setAge(22);
System.out.println("person1:"+person1);
//序列化
//Java Object -> byte[]
//可以寫到file或者byte[]裡面
try(ByteArrayOutputStream stream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(stream)
) {
out.writeObject(person1);
out.flush();
//data對象變成的二進制流
byte[] data = stream.toByteArray();
//反序列化
//byte[] -> java Object
try(ByteArrayInputStream instream = new ByteArrayInputStream(data);
ObjectInputStream in = new ObjectInputStream(instream)
) {
Object returnValue = in.readObject();
System.out.println(returnValue.getClass());
Person person2 = (Person) returnValue;
System.out.println("Person2:" + person2);
//前者Person1是通過執行個體化new出來的,在堆上配置設定出來的
//後者person2是通過二進制流變成的Java對象
System.out.println(person1 == person2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
5.3 通過網絡傳輸
package com.serializable;
import java.io.*;
class Person implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestSerializable {
public static void main(String[] args) {
Person person1 = new Person();
person1.setName("張三");
person1.setAge(22);
try(FileOutputStream stream = new FileOutputStream("D:" + File.separator + "test" + File.separator + "person.obj");
ObjectOutputStream out = new ObjectOutputStream(stream)
) {
out.writeObject(person1);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
假設我把
person.obj
複制粘貼到了
D:\\test
目錄下,相當于通過網絡傳輸給了别人,那麼别人如何打開這個檔案并擷取到其中的内容呢?
package com.serializable;
import java.io.*;
class Person implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class TestSerializable {
public static void main(String[] args) {
try(FileInputStream stream = new FileInputStream("D:" + File.separator + "person.obj");
ObjectInputStream in = new ObjectInputStream(stream)
) {
Object returnValue = in.readObject();
Person person = (Person) returnValue;
System.out.println(person);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
5.4 transient關鍵字
Serializable
預設會将對象中所有屬性進行序列化儲存,如果現在某些屬性(比如密碼)不希望被儲存了,那麼就可以使用
transient
關鍵字,即被transient修飾過的屬性不再參與序列化。
package com.serializable;
import com.sun.corba.se.impl.orb.PropertyOnlyDataCollector;
import java.io.*;
class Person implements Serializable {
private String name;
private Integer age;
private transient String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", password='" + password + '\'' +
'}';
}
}
public class TestSerializable {
public static void main(String[] args) {
Person person1 = new Person();
person1.setName("李四");
person1.setAge(20);
person1.setPassword("abc123");
//序列化
try(FileOutputStream stream = new FileOutputStream("D:" + File.separator + "person.obj");
ObjectOutputStream out = new ObjectOutputStream(stream1)
) {
out.writeObject(person1);
out.flush();
//反序列化
try(FileInputStream inStream = new FileInputStream("D:" + File.separator + "person.obj");
ObjectInputStream in = new ObjectInputStream(inStream)
) {
Object returnValue = in.readObject();
Person person2 = (Person) returnValue;
System.out.println(person2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}