为什么java.io.Reader #skip以它的方式实现?

时间:2011-06-02 20:00:31

标签: java stream character java-io

我还在学习Java中的面向对象编程。我正在研究java.io.Reader.skip的Java实现,并且我想知道为什么它实现了它的实现方式。特别是我对这些事情有疑问,我注意到了:

  1. 用于skip(long)的缓冲区是Reader对象的字段,而不是方法中的普通变量。
  2. 最大缓冲区长度远小于Integer.MAX_VALUE 2147,483,647。特别是,Java的实现使用了8192。
  3. java.io.InputStream实现跳过相同的方式。
  4. 现在,我个人认为缓冲区是一个字段的原因是,由于重复重新初始化,缓冲区不必重复进行垃圾回收。这可能会更快地跳过。

    缓冲区长度越小我认为这与读取器阻塞更短的时间有关,但是由于Reader是同步的,这真的会产生影响吗?

    以相同方式实现它的字节流可能是为了保持一致性。这三件事我的假设是否正确?

    总而言之,我的问题是:关于字符数组使用字段而不是变量,平均速度差异有多大?使用Integer.MAX_VALUE作为最大缓冲区长度是不是一样的?并且在字节流的for循环中使用无参数read方法更好更容易,因为其他read方法只调用无参数read

    很抱歉,如果我的问题是一个奇怪的问题,但我认为我可以通过这个问题了解面向对象编程。

4 个答案:

答案 0 :(得分:2)

一次读取一个字符的效率会低得多 - 每个字节跳过一次方法调用,这对于大型跳过(大量开销)通常是不好的。

暂存缓冲区大小很容易回答:如果要从文件中跳过2G,是否真的要分配Integer.MAX_VALUE RAM 块?

至于确切的大小,以及是否使用实例varialbe,这是一个依赖于实现的折衷方案。您正在阅读选择8192成员的实现。一些实现具有较小的本地实现(可以看到512 here)。

标准中没有任何内容需要任何这些实现​​细节,因此根本不要依赖它们。

如果你打算做类似的事情,基准不同的方法,并在你的具体情况下选择最好的妥协。

答案 1 :(得分:2)

  

关于字符数组使用字段而不是变量,平均速度差异有多大?

这肯定会有所不同,从JVM到JVM,但重复分配一个8K阵列可能并不像保留一个那么便宜。当然,这里隐藏的教训是,不应该抓住读者,即使是封闭的读者,因为它们会带来8K的惩罚。

  

使用Integer.MAX_VALUE作为最大缓冲区长度不是一样的吗?

缓冲区必须预先分配,并且分配2Gb阵列似乎是一种过度杀伤力。请记住,分页的原因是分摊读取呼叫的成本 - 有时会变成本机操作。

  

在字节流的for循环中使用无参数读取方法是不是更好更容易,因为其他读取方法只调用无参数读取?

无法保证底层流是缓冲的,因此可能会产生大量的每次呼叫开销。

最后,请记住java.io类有许多不足之处,所以不要假设那里的所有内容都有充分的理由。

答案 2 :(得分:1)

你忘记了2 ^ 31 - 1的缓冲区是2 GB的内存,必须分配,然后才能用于其他任何东西

分配2 千兆字节的大型连续字节块对于读取字节数而言是过度的,并且可能导致内存不足的情况

8 kB的最大内存缓冲区是更好的替代方案,也是更好的权衡,因为它只会被分配一次(并且它将在每次跳过操作时重复使用)

btw在java.io.InputStream中,skipbuff是静态的,只有一次分配,但由于没有从中读取(它只是用作只写内存),所以不必担心比赛

答案 3 :(得分: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作为本机方法。我认为这将是最有用的规范示例。
  • 阅读所有内容并将其扔掉:

    • LineNumberInputStream.skip()必须阅读所有内容以计算行数。 (我不知道这个类是否存在。请改用LineNumberReader。)
    • ObjectInputStream不会覆盖skip,但有另一个名为skipBytes()的方法,它执行相同的操作(仅适用于int个参数)。它委托给一个内部类(BlockDataInputStream.skip),后者依次从底层流中读取块数据的Object流协议。
    • 如上所述,InputStream中的默认实现。 SequenceInputStreamPipedInputStream也会使用此功能。

让我们看看Reader类。原则上,适用相同的策略。

  • 跳过基础读者/流:

    • FilterReader.skip()这样做。
    • PushBackReader首先跳过自己的后推缓冲区,然后是基础读取器。
  • 重置一些索引:

    • StringReader(这个实际上支持向后跳过)
    • CharArrayReader
  • 阅读所有内容并将其扔掉:

    • 默认Reader.skip(),也由PipedReader使用。
      • 对于InputStreamReader,“简单地跳过基本流”方法仅适用于固定字节数字符集(即ISO-8859系列,UTF-16和一些类似的字符集),而不适用于UTF-8 ,UTF-32或每个字符具有可变字节数的其他字符集,因为我们必须读取所有字节以了解它们实际上代表了多少字符。这也适用于其子类FileReader
    • BufferedReader(它不会调用自己的read,但会填充其内部缓冲区,该缓冲区从基本流中读取。)
    • LineNumberReader(必须执行此操作以跟踪行号)
相关问题