为什么是java。伊奥。读者#斯基普是如何实现的?
我还在学习Java中的面向对象编程。我在看java.io.Reader.skip
的Java实现,我想知道为什么它的实现方式是这样的。我特别注意到,我对这些事情有疑问:
- 用于
skip(long)
的缓冲区是Reader对象的字段,而不是方法中的正常变量李> - 最大缓冲区长度远小于
Integer.MAX_VALUE
2147483647。特别是,Java的实现使用8192李> java.io.InputStream
实现同样的方式李>
现在,我个人认为缓冲区是一个字段的原因是,缓冲区不必因为被重复初始化而被重复垃圾收集。这可能会使跳绳更快
缓冲区长度变小,我认为这与使读卡器阻塞更短的时间有关,但既然读卡器是同步的,这真的会有区别吗
字节流以同样的方式实现它,可能是为了一致性。我对这三件事的假设正确吗
总而言之,我的问题是:对于字符数组,使用字段而不是变量,平均速度会有多大差异?使用Integer.MAX_VALUE
作为最大缓冲区长度不是一样吗?在字节流的for循环中使用no-parameterread
方法不是更好更容易,因为其他read
方法只调用no-parameterread
抱歉,如果我的问题很奇怪,但我认为通过这个问题我可以学到很多关于面向对象编程的知识
# 1 楼答案
对于
InputStream
,通常会有一些子类,它们允许更有效的跳过,这些子类会适当地覆盖skip
方法。但是对于那些没有有效跳过方法的子类(比如压缩或解压缩输入流),方法skip
是基于读取实现的,所以不是每个子类都必须这样做关于如何在
java.io
包中实现这一点,有几种策略:跳过基本流:
FilterInputStream.skip()
只需委托给源流。不过,我不确定这有多有用DataInputStream
不重写skip()
,但有另一个名为skipBytes()
的方法,它做同样的事情(不过,只针对int
参数)。它委托给底层的源流BufferedInputStream.skip()
覆盖这个,首先跳过它自己缓冲区中的现有内容,然后在基本流上调用skip()
(如果没有mark()
集,如果有标记,它必须将所有内容读入缓冲区以支持reset()
)PushbackInputStream.skip()
首先跳过其后推缓冲区,然后调用super.skip()
(即FilterInputStream.skip()
,见上文)重置索引:
ByteArrayInputStream
只需设置下一步阅读的位置,就可以轻松支持跳过StringBufferInputStream
(这是StringReader
的一个弃用版本)只需重置索引即可支持跳过本土魔法:
FileInputStream
将skip()
作为native
方法。我认为这将是最有用的典型例子李>阅读所有内容并将其扔掉:
LineNumberInputStream.skip()
必须阅读所有内容才能数清行数。(我不知道这个类的存在。改用LineNumberReader
。)ObjectInputStream
不重写skip()
,但有另一个名为skipBytes()
的方法,它做同样的事情(不过,只针对int
参数)。它委托给一个内部类(BlockDataInputStream.skip()
),该类反过来从底层流中读取数据,这与块数据的对象流协议有关在
中的默认实现InputStream
:SequenceInputStream
和PipedInputStream
使用李>让我们看看
Reader
类。原则上,同样的策略也适用:跳过基本读取器/流:
FilterReader.skip()
就是这样做的PushBackReader
首先跳过它自己的回推缓冲区,然后跳过基本读取器重置一些索引:
StringReader
(这个实际上支持向后跳转)CharArrayReader
阅读所有内容并将其扔掉:
默认的
Reader.skip()
,也被PipedReader
使用对于
InputStreamReader
,“简单跳过基流”方法仅适用于固定字节计数的字符集(即ISO-8859
系列、UTF-16
和一些类似的字符集),而不适用于UTF-8
、UTF-32
或每个字符具有可变字节数的其他字符集,因为我们必须读取所有字节,才能知道它们实际上代表了多少个字符。这也适用于它的子类FileReader
BufferedReader
(它不调用自己的read()
,而是填充从基流读取的内部缓冲区)LineNumberReader
(它必须这样做才能跟踪行号)# 2 楼答案
您忘记了2^31-1的缓冲区是必须分配的2GB内存,然后不能用于任何其他用途
分配一个2gigabytes的大连续字节块对于以字节为单位的读取来说是过度的,它可能会导致内存不足的情况
8 kB的最大内存缓冲区是一个更好的选择,也是一个更好的折衷方案,因为它只分配一次(每次跳过操作都会重复使用)
顺便说一句,skipbuff是静态的,只分配过一次,但由于没有读取(它只是用作只写内存),因此不需要担心竞争
# 3 楼答案
这在不同的JVM中肯定会有所不同,但重复分配一个8K数组可能不如保持一个数组便宜。当然,这里隐藏的教训是,你不应该抓住读者,即使是封闭的读者,因为他们会受到8K的惩罚
缓冲区必须预先分配,而分配2Gb阵列似乎是一种过度消耗。请记住,分页的原因是分摊读调用的成本,读调用有时会变成本机操作
不能保证底层流是缓冲的,因此这可能会产生大量的每次调用开销
最后,请记住java。io类有很多很多缺陷,所以不要认为所有的东西都有充分的理由