构建用于测试的超大型内存InputStream
由于某種原因,我需要非常大的,甚至可能是無限的InputStream ,它會反復(fù)地反復(fù)返回相同的byte[] 。 這樣,我可以通過重復(fù)小樣本來產(chǎn)生大量的數(shù)據(jù)流。 在Guava中可以找到類似的功能: Iterable<T> Iterables.cycle(Iterable<T>)和Iterator<T> Iterators.cycle(Iterator<T>) 。 例如,如果您需要0和1的無限來源,只需說出Iterables.cycle(0, 1)并無限獲取Iterables.cycle(0, 1) 0, 1, 0, 1, 0, 1... 不幸的是,我還沒有為InputStream找到這樣的工具,所以我跳入自己編寫的工具。 本文記錄了我在此過程中犯的許多錯誤,主要是由于過于復(fù)雜和過度設(shè)計的簡單解決方案。
我們實際上不需要無限的InputStream ,能夠創(chuàng)建非常大的InputStream (例如32 GiB)就足夠了。 因此,我們采用以下方法:
它基本上采用字節(jié)sample數(shù)組,并返回一個InputStream返回這些字節(jié)。 但是,當sample用完時,它將翻轉(zhuǎn),再次返回相同的字節(jié)-重復(fù)此過程指定的次數(shù),直到InputStream信號結(jié)束。 我尚未真正嘗試過但似乎最明顯的一種解決方案:
public static InputStream repeat(byte[] sample, int times) {final byte[] allBytes = new byte[sample.length * times];for (int i = 0; i < times; i++) {System.arraycopy(sample, 0, allBytes, i * sample.length, sample.length);}return new ByteArrayInputStream(allBytes); }我看到你在那里笑! 如果sample是100字節(jié),并且我們需要32 GiB的輸入重復(fù)這100字節(jié),則生成的InputStream不應(yīng)真正分配32 GiB的內(nèi)存,我們在這里必須更加聰明。 事實上,上面的repeat()有另一個細微的錯誤。 Java中的數(shù)組限制為2 31 -1個條目( int ),而32 GiB遠高于此。 該程序編譯的原因是此處無聲的整數(shù)溢出: sample.length * times 。 此乘法不適用于int 。
好的,讓我們嘗試至少在理論上可行的方法。 我的第一個想法是:如果我創(chuàng)建許多ByteArrayInputStream共享同一byte[] sample (它們不進行急切的復(fù)制)并以某種方式將它們連接在一起怎么辦? 因此,我需要一些InputStream適配器,該適配器可以采用任意數(shù)量的基礎(chǔ)InputStream并將它們鏈接在一起-當?shù)谝粋€流耗盡時,切換到下一個。 當您在Apache Commons或Guava中尋找某些東西,并且顯然永遠在JDK中時,這一尷尬時刻…… java.io.SequenceInputStream幾乎是理想的選擇。 但是,它只能精確地鏈接兩個基礎(chǔ)InputStream 。 當然,由于SequenceInputStream本身就是InputStream ,我們可以遞歸地將其用作外部SequenceInputStream的參數(shù)。 重復(fù)此過程,我們可以將任意數(shù)量的ByteArrayInputStream在一起:
public static InputStream repeat(byte[] sample, int times) {if (times <= 1) {return new ByteArrayInputStream(sample);} else {return new SequenceInputStream(new ByteArrayInputStream(sample),repeat(sample, times - 1));} }如果times為1,則將sample包裝在ByteArrayInputStream 。 否則,遞歸使用SequenceInputStream 。 我認為您可以立即發(fā)現(xiàn)此代碼的問題:太深的遞歸。 嵌套級別與times參數(shù)相同,將達到數(shù)百萬甚至數(shù)十億。 肯定有更好的辦法。 幸運的是,較小的改進將遞歸深度從O(n)更改為O(logn):
public static InputStream repeat(byte[] sample, int times) {if (times <= 1) {return new ByteArrayInputStream(sample);} else {return new SequenceInputStream(repeat(sample, times / 2),repeat(sample, times - times / 2));} }老實說,這是我嘗試的第一個實現(xiàn)。 這是分而治之原理的簡單應(yīng)用 ,在這里我們將結(jié)果平均分成兩個較小的子問題。 看起來很聰明,但是有一個問題:容易證明我們創(chuàng)建了t( t =times ) ByteArrayInputStreams和O(t) SequenceInputStream 。 共享sample字節(jié)數(shù)組時,數(shù)百萬個各種InputStream實例浪費了內(nèi)存。 這使我們另一種實現(xiàn),創(chuàng)建只有一個InputStream ,無論價值times :
import com.google.common.collect.Iterators; import org.apache.commons.lang3.ArrayUtils;public static InputStream repeat(byte[] sample, int times) {final Byte[] objArray = ArrayUtils.toObject(sample);final Iterator<Byte> infinite = Iterators.cycle(objArray);final Iterator<Byte> limited = Iterators.limit(infinite, sample.length * times);return new InputStream() {@Overridepublic int read() throws IOException {return limited.hasNext() ?limited.next() & 0xFF :-1;}}; }畢竟,我們將使用Iterators.cycle() 。 但是在我們必須將byte[]轉(zhuǎn)換為Byte[]因為迭代器只能與objets一起使用,而不能與原語一起使用。 沒有慣用的方法ArrayUtils.toObject(byte[])語數(shù)組轉(zhuǎn)換為盒裝類型數(shù)組,因此我使用來自Apache Commons Lang的ArrayUtils.toObject(byte[]) 。 有了對象數(shù)組,我們可以創(chuàng)建一個infinite迭代器,循環(huán)遍歷sample值。 由于我們不需要無限的流,因此再次使用了來自Guava的Iterators.limit(Iterator<T>, int)來切斷無限迭代Iterators.limit(Iterator<T>, int) 。 現(xiàn)在我們只需要從Iterator<Byte>到InputStream進行橋接–畢竟它們在語義上代表著同一件事。
該解決方案遭受兩個問題。 首先,由于拆箱,它會產(chǎn)生大量垃圾。 垃圾收集并不太關(guān)心死的,短命的物品,但看起來仍然很浪費。 我們之前已經(jīng)遇到的第二個問題: sample.length * times乘以倍數(shù)可能會導(dǎo)致整數(shù)溢出。 由于Iterators.limit()占用int不long ,因此沒有固定的理由,因此無法修復(fù)。 順便說一句,我們通過做按位與避免第三個問題0xFF -否則, byte值為-1將信號流的結(jié)束,這是情況并非如此。 x & 0xFF被正確轉(zhuǎn)換為無符號255 ( int )。
因此,即使上面的實現(xiàn)是簡短而甜美,聲明式而不是命令式的,但它仍然太慢且受限制。 如果您具有C背景,我可以想象您看到我掙扎時會感到多么不舒服。 在我最后想到的是最簡單,痛苦的簡單和低級實現(xiàn)之后:
public static InputStream repeat(byte[] sample, int times) {return new InputStream() {private long pos = 0;private final long total = (long)sample.length * times;public int read() throws IOException {return pos < total ?sample[(int)(pos++ % sample.length)] :-1;}}; }免費的GC,純JDK,快速且易于理解。 給您上一課:從您想到的最簡單的解決方案開始,不要過度設(shè)計,也不要太聰明。 我以前的解決方案(聲明性,功能性,不變性等)–也許它們看起來很聰明,但是它們既不快速也不容易理解。
我們剛剛開發(fā)的實用程序不僅是一個玩具項目,還將在后續(xù)文章中使用 。
翻譯自: https://www.javacodegeeks.com/2014/07/building-extremely-large-in-memory-inputstream-for-testing-purposes.html
總結(jié)
以上是生活随笔為你收集整理的构建用于测试的超大型内存InputStream的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文本内容之间的关键词提取和相似度计算
- 下一篇: 15寸笔记本电脑尺寸是多少(15寸笔记本