Java Tomcat Servlet性能:StringBuilder与直接写入

Java Tomcat Servlet性能:StringBuilder与直接写入,java,tomcat,servlets,networking,Java,Tomcat,Servlets,Networking,这是为tomcat/网络专家准备的。我会对它进行基准测试/有线搜索,但这要求很高,也许有人会马上知道答案 比较这两种生成servlet输出的方法,从用户的角度来看,哪种方法最快: 直接写入servlet输出流: 对于(inti=0;i将您servletOutputStream包装在缓冲输出流中(除非它已经存在),您不需要担心像这样愚蠢的事情。我希望servletOutputStream实际上是 org.apache.tomcat.core.BufferedServletOutputStr

这是为tomcat/网络专家准备的。我会对它进行基准测试/有线搜索,但这要求很高,也许有人会马上知道答案

比较这两种生成servlet输出的方法,从用户的角度来看,哪种方法最快:

  • 直接写入servlet输出流:


    对于(inti=0;i将您
    servletOutputStream
    包装在
    缓冲输出流中(除非它已经存在),您不需要担心像这样愚蠢的事情。

    我希望
    servletOutputStream
    实际上是

        org.apache.tomcat.core.BufferedServletOutputStream 
    
    这是一个缓冲流(顾名思义)。这意味着最好直接将字符写入流,而不是将它们组合到
    StringBuffer
    StringBuilder
    中并写入结果。直接写入将避免至少一个字符副本

    如果您的
    ServletOutputStream
    没有被缓冲,那么您可以将其包装在
    BufferedOutputStream
    中,您将得到相同的结果


    假设您现在正在谈论流(刷新
    StringBuffer
    没有任何意义)

    何时刷新这些缓冲区

    当它们已满、在流上调用
    flush
    或流关闭时

    …使用它的最佳策略是什么

    通常情况下,写入数据并在完成后关闭文件。除非有充分的理由,否则不要显式刷新。如果您提供的是普通HTTP响应,则很少会这样做。(刷新可能会导致网络堆栈通过发送更多网络数据包来传输相同数量的信息。这可能会影响整体网络吞吐量。)


    在servlet框架中,我记得servlet规范说,
    ServletOutputStream
    将在请求/响应处理完成时自动刷新和关闭。如果您没有包装
    ServletOutputStream
    ,您甚至不需要关闭流。(不过这没什么害处。)

    我肯定会使用第一个字符串。servlet输出流是缓冲的,因此您不必担心发送太快。此外,每次使用第二个字符串时都会分配一个新字符串,这可能会增加GC开销。使用第一个字符串并在循环后调用flush。

    它已经缓冲,在某些情况下是写的通过tearrayoutputstream以10比1的速度写入
    ,这样Tomcat就可以预先写入内容长度头。不用担心。

    毫无疑问,直接写入输出流会更快,原因有很多:

  • 输出缓冲区是固定的
  • 输出缓冲区满时会自动刷新(我认为这种情况何时发生并不重要,所以不要担心)
  • 输出缓冲区将被重新使用
  • 您的
    StringBuilder
    可能会变得非常大,占用大量堆空间
  • 您的
    StringBuilder
    将每隔一段时间重新分配其空间,导致创建新对象、复制数据等
  • 所有这些内存活动都将创建GC必须处理的“垃圾”
  • 但是

    我认为您的分析没有考虑一个非常重要的因素:错误检测和恢复

    如果servlet正在执行一个半复杂的过程,它随时可能失败。如果在呈现一半的输出后失败,您将无法执行以下任何操作:

  • 发出“错误”HTTP状态代码(例如,500服务器错误)
  • 将用户重定向到另一页(错误页?)
  • 在屏幕上显示一条漂亮的错误消息,而不会破坏/中断页面
  • 因此,尽管手动缓冲方法(基于
    StringBuilder
    )效率较低,但我相信它为您处理错误提供了很大的灵活性

    这更像是一个宗教性的论点,但您会发现许多web应用程序程序员会说您的servlet不应该产生任何输出,生成响应的任务应该委托给另一个更适合该任务的组件(例如JSP、Velocity、FreeMarker等)

    但是,如果您在编写servlet时着眼于原始速度,那么无论如何:直接写入输出流。它将在微基准测试和负载下的总体速度方面为您提供最佳性能

    编辑2016-01-26

    这些缓冲区何时刷新

    servlet规范无法保证
    ServletOutputStream
    是否被缓冲,但不使用缓冲区将是一个实际错误:一次发送一个字符的TCP数据包肯定会对性能造成严重影响

    如果您确实需要确保对响应进行缓冲,则必须使用自己的
    BufferedOutputStream
    ,因为servlet容器可以随时更改其实现,并且如上所述,不能保证为您缓冲响应

    Tomcat中的缓冲究竟是如何工作的

    当前在Tomcat中实现的缓冲与标准JDK类中的缓冲一样:当缓冲区填满时,它被刷新到较低的流,然后在调用后剩余的字节保留在缓冲区中

    如果在流上手动调用
    flush
    ,将强制使用
    Transfer Encoding:chunked
    ,这意味着需要通过线路发送额外的数据,因为没有
    内容长度
    (除非在开始填充缓冲区之前手动设置)。如果可以避免分块编码,则可以为自己节省一些网络资源
    . packet( "aaaa....a(x1000)" )
    . packet( "aaaa....a(x1000)" )
    ...x10
    
    . packet( "a" ) // first write
    . packet( "aaaa...a(x99) ) // all remaining data available when buffer-empty interrupt.
    
        org.apache.tomcat.core.BufferedServletOutputStream