概述
之前介紹過一篇IO總覽的文章,概述性的講解了現有的一些Java IO。從這一篇開始,詳細講解Java IO的各個模塊,今天首先講一下InputStream。
InputStream是一個字節輸入流,什麼是字節流呢?我們知道在java中有一個叫byte的基本類型,就是同一個。字節是一個8位表示的二進制。很多人會誤認爲輸入流就是打開一個磁盤文件然後從裏邊讀數據,但是輸入流並不侷限於文件,應該說IO並不代表和磁盤交互。所以讀取磁盤文件僅僅是輸入流中的一種FileInputStream,在輸入流中還有很多其他的輸入流。接下會介紹幾種,雖然有些不常用。在介紹之前先看一下在JDK中,InputStream的繼承關係:
AutoCloseable
這裏的AutoCloseable是jdk1.7引入的一種語法糖,可配合jdk1.7出現的try-with-resources新語法特性一起使用,用於在使用完資源後自動關閉。Closeable是表示這是一個可關閉的數據源或目標。
public class AutoCloseableDemo {
public static void main(String[] args) {
try (FileInputStream fis = new FileInputStream("/tmp/test")){
int i;
while ((i=fis.read()) != -1){
System.out.println(i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
節點流
什麼是節點流呢?節點流指可以從一個數據源讀寫數據,這個數據源可以是磁盤文件,內存或者網絡。根據這個定義可以將上圖中的流FileInputStream、PipedInputStream、ByteArrayInputStream劃分爲節點流。最熟悉的可能就是FileInputStream,因爲平時讀取磁盤文件應該用得最多,先介紹FileInputStream。
FileInputStream
看一下該類的構造函數
//參數:文件路徑
public FileInputStream(String name) throws FileNotFoundException;
//參數:一個File對象
public FileInputStream(File file) throws FileNotFoundException;
//一個文件描述符
public FileInputStream(FileDescriptor fdObj);
通過jdk api文檔可以看到FileInputStream有三個構造函數,第一個和第二個比較常用,第三個是一個已存在的文件系統的真實連接。
public class FileInputStreamDemo {
/**
* 文件路徑參數創建
* @throws IOException
*/
public void FilePathDemo() throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream("/tmp/test");
int i;
while ((i=fis.read()) != -1){
System.out.println(i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fis != null) {
fis.close();
}
}
}
/**
* 以File對象方式創建
*/
public void FileDemo() throws IOException {
File file = new File("/tmp/test");
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
int i;
while ((i=fis.read()) != -1){
System.out.println(i);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fis != null) {
fis.close();
}
}
}
/**
* 以文件描述符參數創建
*/
public void FileDescriptorDemo() throws IOException {
FileInputStream fis = new FileInputStream(FileDescriptor.in);
System.out.println(fis.read());
fis.close();
}
public static void main(String[] args) {
FileInputStreamDemo demo = new FileInputStreamDemo();
try {
demo.FilePathDemo();
demo.FileDemo();
demo.FileDescriptorDemo();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ByteArrayInputStream
ByteArrayInputStream比較少見,ByteArrayInputStream內部維護着一個緩衝區,從該緩衝區中讀取字節,內部計數器跟蹤read要讀取的下一個字節。看一下構造函數:
ByteArrayInputStream(byte[] buf);
ByteArrayInputStream(byte[] buf, int offset, int length)
可以看到ByteArrayInputStream以byte數組爲參數構造ByteArrayInputStream對象。ByteArrayInputStream和FileInputStream的區別在於,FileInputStream以磁盤文件爲數據源,ByteArrayInputStream以內存數據爲數據源。ByteArrayInputStream的用法:
public class ByteArrayInputStreamDemo {
public static void main(String[] args) {
byte[] bytes = new byte[]{1, 2, 3, 4, 5};
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes, 2, 2);
int i;
while ((i = byteArrayInputStream.read()) != -1){
System.out.println(i);
}
try {
byteArrayInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
結果:
Connected to the target VM, address: '127.0.0.1:38135', transport: 'socket'
3
4
Disconnected from the target VM, address: '127.0.0.1:38135', transport: 'socket'
Process finished with exit code 0
從結果中看到雖然bytes有5個數據,但是隻能讀取2個數據,因爲在構造時傳入了offset和length表示從offset開始的length個數據。至於ByteArrayInputStream的內部實現細節,在以後的進階文章中再詳細論述。
PipedInputStream
PipedInputStream是一種很特殊的輸入流,因爲它單獨存在沒有什麼意義,它需要和PipedOutputStream一起使用,數據的流向是從PipedOutputStream流向PipedInputStream的緩存區,然後PipedInputStream的read方法從PipedInputStream的緩衝區中讀取數據。如下圖
接下來看一下使用PipedInputStream來實現一個簡單線程通信例子,當然線程通信有其他方式來實現,但是如果兩個線程之間需要傳遞字節數組時,PipedInputStream還是不錯的選擇。
public class PipedInputStreamDemo {
public static void main(String[] args) {
PipedInputStream pipedInputStream = new PipedInputStream();
PipedOutputStream pipedOutputStream = new PipedOutputStream();
try {
pipedOutputStream.connect(pipedInputStream);
} catch (IOException e) {
e.printStackTrace();
}
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(new ReadWorker(pipedInputStream));
executor.execute(new WirteWorker(pipedOutputStream));
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
}
}
class ReadWorker implements Runnable {
private PipedInputStream pipedInputStream;
public ReadWorker(PipedInputStream pipedInputStream){
this.pipedInputStream = pipedInputStream;
}
@Override
public void run() {
byte[] bytes = new byte[1024];
try {
while (pipedInputStream.read(bytes) != -1){
System.out.println(new String(bytes, Charset.forName("UTF-8")));
}
} catch (IOException e){
} finally {
try {
pipedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class WirteWorker implements Runnable {
private PipedOutputStream pipedOutputStream;
public WirteWorker(PipedOutputStream pipedOutputStream){
this.pipedOutputStream = pipedOutputStream;
}
@Override
public void run() {
String str = "hello world!";
try {
pipedOutputStream.write(str.getBytes(Charset.forName("UTF-8")));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pipedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
處理流
什麼又是處理流呢?處理流是連接在已存在的流(節點流或者處理流)之上,通過對數據的處理提供更強大的讀寫功能。在InputStream中有如下幾種處理流,BufferedInputStream、DataInputStream、ObjectInputStream、SequenceInputStream。接下來簡單介紹一下幾種流。
BufferedInputStream
BufferedInputStream是一個處理流,它的作用是增強InputStream,爲其提供緩衝區的功能。這裏可能會有疑問,BufferedInputStream實現的方法和InputStream的方法差不多,它存在的意義是什麼?
這裏要從Java IO讀取數據的過程說起,僅僅簡單的介紹一下,InputStream讀取數據源的數據需要操作系統支持,也就是說Java的線程發出讀取數據(read方法)的請求,操作系統接收到這個請求,然後從數據源讀取一個byte(read方法,不是read(byte[])),然後將數據從內核空間複製到用戶空間,java線程拿到要的數據,返回。可以看到,如果調用read()方法,那麼讀取10個字節,就要進行10個這樣的流程,是相當低效的,當然可以用read(byte[]),但是有時需要read()的場景,所以就出現了InputStream的增強類BufferedInputStream,該BufferedInputStream提供了緩存區的功能。
舉個例子,以前你要把一堆磚從一個地方搬到另一個地方整理好,你力氣小一次只能搬一塊,當然別人可能力氣大,一次可以搬兩塊,這樣你搬10塊磚要來回跑10次,那現在爲了增強你的能力,給你提供一個小車,你一次可以運50塊,這個時候你只需要一次就可以運50塊磚,而你要整理好這些磚,只需要從小車上一次一塊拿下來放好,就像從緩衝區裏讀取數據一樣。通過提供緩衝區來增強IO的能力,減小系統開銷。
簡單使用
public void readDemo(String path){
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(path);
//可以在構造時傳入參數指定緩衝區的大小
bis = new BufferedInputStream(fis, 2048);
int data;
while ((data = bis.read()) != -1){
System.out.println(data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null)
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void readBytesDemo(String path){
FileInputStream fis = null;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(path);
bis = new BufferedInputStream(fis);
byte[] bytes = new byte[1024];
while (bis.read(bytes) != -1){
System.out.println(bytes);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null)
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
BufferedInputStreamDemo demo = new BufferedInputStreamDemo();
demo.readDemo("/tmp/test");
demo.readBytesDemo("/tmp/test");
}
DataInputStream
DataInputStream提供了從字節輸入流中讀取Java基本類型的能力,比如,如果要想從輸入流中讀取一個int類型的數據,需要讀取4個字節然後自己組裝成一個int,而DataInputStream通過提供一個InputStream的裝飾器,增加了自動包裝的功能。
簡單使用
public class DataInputStreamDemo {
public static void main(String[] args) {
FileInputStream fis = null;
DataInputStream dis = null;
FileOutputStream fos = null;
DataOutputStream dos = null;
try {
fos = new FileOutputStream("/tmp/test1");
dos = new DataOutputStream(fos);
dos.writeChars("hello world");
dos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dos != null){
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
fis = new FileInputStream("/tmp/test1");
dis = new DataInputStream(fis);
while(dis.available() > 0) {
System.out.println(dis.readChar());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (dis != null){
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
打開文件test1看到的結果,如果以字節流讀取出來,將兩兩字節組合轉換爲char便可以還原。DataInputStream提供的就是這種功能,此外還有readInt()、readLong()、readFloat()等,比較特殊一點的readUTF(),readUTF是轉化成utf-8編碼格式的,用readUTF()讀,那麼你寫的時候也要用writeUTF()寫,writeUTF在寫時會在開頭寫兩個字節表示寫入的數據大小。readUTF時會首先讀取這兩個字節,來設置讀取的字節數。
^@h^@e^@l^@l^@o^@ ^@w^@o^@r^@l^@d
讀取結果:
Connected to the target VM, address: '127.0.0.1:44515', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:44515', transport: 'socket'
h
e
l
l
o
w
o
r
l
d
Process finished with exit code 0
ObjectInputStream
ObjectInputStream是java提供的反序列化的功能,ObjectOutputStream提供序列化輸出的功能,能將java對象序列化之後輸出到指定的輸出流中。被持久化的對象需要實現Serializable、Externalizable接口。
public class ObjectOutputStreamDemo {
public static void main(String[] args) {
FileOutputStream fileOutputStream;
ObjectOutputStream objectOutputStream = null;
{
try {
fileOutputStream = new FileOutputStream("/tmp/person");
objectOutputStream = new ObjectOutputStream(fileOutputStream);
Person person = new Person();
person.setName("mark");
person.setAge(20);
objectOutputStream.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (objectOutputStream != null) {
objectOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
FileInputStream fileInputStream;
ObjectInputStream objectInputStream = null;
try {
fileInputStream = new FileInputStream("/tmp/person");
objectInputStream = new ObjectInputStream(fileInputStream);
Person person = (Person) objectInputStream.readObject();
System.out.println("Person name:" + person.getName() + " age:" + person.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (objectInputStream != null) {
objectInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
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;
}
}
SequenceInputStream
SequenceInputStream的作用是將多個輸入流合併爲一個SequenceInputStream輸入流,然後從第一個輸入流開始讀取數據,接着第二個,直到最後一個。
public class SequenceInputStreamDemo {
public static void main(String[] args) {
try {
FileInputStream is1 = new FileInputStream("/tmp/test1");
FileInputStream is2 = new FileInputStream("/tmp/test2");
SequenceInputStream sis = new SequenceInputStream(is1, is2);
int i;
while ((i = sis.read()) != -1){
System.out.println(i);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
文章首發地址:IkanのBolg