markSupported
InputStream是否支持mark,默認不支持。
public boolean markSupported() {
return false;
}
InputStream默認是不支持mark的,子類需要支持mark必須重寫這三個方法。
在此輸入流中標記當前的位置。對 reset 方法的后續調用會在最后標記的位置重新定位此流,以便后續讀取重新讀取相同的字節。
readlimit 參數告知此輸入流在標記位置失效之前允許讀取許多字節。
mark
mark接口。該接口在InputStream中默認實現不做任何事情。
public synchronized void mark(int readlimit) {}
reset
reset接口。該接口在InputStream中實現,調用就會拋異常。
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
將此流重新定位到對此輸入流最后調用 mark 方法時的位置。
reset 的常規協定是:
如果方法 markSupported 返回 true,則:如果創建流以來未調用方法 mark,或最后調用 mark 以來從該流讀取的字節數大於最后調用 mark 時的參數,則可能拋出 IOException。如果未拋出這樣的 IOException,則將該流重新設置為這種狀態:最近調用 mark 以來(或如果未調用 mark,則從文件開始以來)讀取的所有字節將重新提供給 read 方法的后續調用方,后接可能是調用 reset 時的下一輸入數據的所有字節。
如果方法 markSupported 返回 false,則:對 reset 的調用可能拋出 IOException。如果未拋出IOException,則將該流重新設置為一種固定狀態,該狀態取決於輸入流的特定類型和其創建方式的固定狀態。提供給 read 方法的后續調用方的字節取決於特定類型的輸入流。
簡而言之就是:*調用mark方法會記下當前調用mark方法的時刻,InputStream被讀到的位置。
調用reset方法就會回到該位置。*
Code
String content = "yydcdut!";
InputStream inputStream = new ByteArrayInputStream(content.getBytes());
// 判斷該輸入流是否支持mark操作
if (!inputStream.markSupported()) {
System.out.println("mark/reset not supported!");
}
int ch;
boolean marked = false;
while ((ch = inputStream.read()) != -1) {
//讀取一個字符輸出一個字符
System.out.print((char)ch);
//讀到 'c'的時候標記一下
if (((char)ch == 'c')& !marked) {
inputStream.mark(content.length()); //先不要理會mark的參數
marked = true;
}
//讀到'!'的時候重新回到標記位置開始讀
if ((char)ch == '!' && marked) {
inputStream.reset();
marked = false;
}
}
//程序最終輸出:yydcdut!dut!
我們知道InputStream是不支持mark的。要想支持mark子類必須重寫這三個方法,我想說的是不同的實現子類,mark的參數readlimit作用不盡相同。 常用的FileInputStream不支持mark。
-
對於BufferedInputStream,readlimit表示:InputStream調用mark方法的時刻起,在讀取readlimit個字節之前,標記的該位置是有效的。如果讀取的字節數大於readlimit,可能標記的位置會失效。
在BufferedInputStream的read方法源碼中有這么一段:
} else if (buffer.length >= marklimit) {
markpos = -1; /* buffer got too big, invalidate mark */
pos = 0; /* drop buffer contents */
} else {
為什么是可能會失效呢?
因為BufferedInputStream讀取不是一個字節一個字節讀取的,是一個字節數組一個字節數組讀取的。
例如,readlimit=35,第一次比較的時候buffer.length=0(沒開始讀)<readlimit.
然后buffer數組一次讀取48個字節。這時的read方法只會簡單的挨個返回buffer數組中的字節,不會做這次比較。直到讀到buffer數組最后一個字節(第48個)后,才重新再次比較。這時如果我們讀到buffer中第47個字節就reset。mark仍然是有效的。雖然47>35。
- 對於InputStream的另外一個實現類:ByteArrayInputStream,我們發現readlimit參數根本就沒有用,調用mark方法的時候寫多少都無所謂。
public void mark(int readAheadLimit) {
mark = pos;
}
public synchronized void reset() {
pos = mark;
}
因為對於ByteArrayInputStream來說,都是通過字節數組創建的,內部本身就保存了整個字節數組,mark只是標記一下數組下標位置,根本不用擔心mark會創建太大的buffer字節數組緩存。
- 其他的InputStream子類沒有去總結。原理都是一樣的。
所以由於mark和reset方法配合可以記錄並回到我們標記的流的位置重新讀流,很大一部分就可以解決我們的某些重復讀的需要。
這種方式的優點很明顯:不用緩存整個InputStream數據。對於ByteArrayInputStream甚至沒有任何的內存開銷。
當然這種方式也有缺點:就是需要通過干擾InputStream的讀取細節,也相對比較復雜。