zoukankan      html  css  js  c++  java
  • Request.getInputStrema只能读取一次的分析过程

    1. 我们先来看一下继承关系HttpServletRequest 接口继承ServletRequest接口

    public abstract interface  ServletRequest
    {

    public abstract ServletInputStream getInputStream()  throws IOException;

    从上面可知request.getInputStream()返回的是ServletInputSteam。查看ServletInputStream源码可知,ServletInputStream extends InputSteam

    2.为什么说request.getInputStream()只能读取一次呢,再读一次就没有内容了,这个问题要复习下java的IO知识了。

    ,在java中读取一个文件或者字符串的内容的代码大家都会写,下边是使用ByteArrayInputStream和ByteArrayOutputStream进行演示:

    @Test
      public void testByteArrayInputStream() throws Exception {
             String str = "AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
              //ByteArrayInputStream是把一个byte数组转换成一个字节流,读取的内容是从byte数组中读取的
             ByteArrayInputStream byteInputStream = new ByteArrayInputStream(str.getBytes());
              
              //ByteArrayOutputStream生成对象的时候,是生成一个100大小的byte的缓冲区,写入的时候,是把内容写入内存中的一个缓冲区
              ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);
            
             int i =0;
             byte [] b = new byte[100];
             while((i = byteInputStream.read(b))!= -1){
                 byteOutput.write(b, 0, i);
             }
             System.out.println(new String(byteOutput.toByteArray()));
             
         }

    打印结果是:AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

    把一个String字符串的内容使用ByteArrayOutputStream读取出来,然后打印显示。这个代码没有什么问题,估计大家都能写出来,但是看一下下边添加一行代码之后的内容:

    @Test
      public void testByteArrayInputStream() throws Exception {
             String str = "AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
              //ByteArrayInputStream是把一个byte数组转换成一个字节流,读取的内容是从byte数组中读取的
             ByteArrayInputStream byteInputStream = new ByteArrayInputStream(str.getBytes());
    
              //调用这个方法,会影响到下次读取,下次再调用这个方法,读取的起始点会后移5个byte
              byteInputStream.read(new byte[5]);
              
              //ByteArrayOutputStream生成对象的时候,是生成一个100大小的byte的缓冲区,写入的时候,是把内容写入内存中的一个缓冲区
              ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);
            
             int i =0;
             byte [] b = new byte[100];
             while((i = byteInputStream.read(b))!= -1){
                 byteOutput.write(b, 0, i);
             }
             System.out.println(new String(byteOutput.toByteArray()));
             
         }

    打印结果是:CCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

    我在第8行添加了一行代码,这行代码可以把String生成的byte数组中读取5个字节的内容,这行代码会影响到后边第15行byteInputStream的读取结果,显然”AAAAA“在输出中没有了,这是为什么呢?我感觉需要查看java中ByteArrayInputStream的Read方法的实现源码,查看的结果其实可以总结一个句话,就是在InputStream读取的时候,会有一个pos指针,他指示每次读取之后下一次要读取的起始位置,在API文档中是这样解释的:(看过InputStream源码的都明白,read方法其实调用的都是带有三个参数的方法)

    public int read(byte[] b,int off, int len)   
     Reads up to len bytes of data into an array of bytes from this input stream. If pos equals count, then -1 is returned to indicate end of file. Otherwise, the number k of
    bytes read is equal to the smaller of len and count-pos. If k is positive, then bytes buf[pos] through buf[pos+k-1] are copied into b[off] through b[off+k-1] in the manner
    performed by System.arraycopy. The value k is added into pos and k is returned.

       就是在每次读取的时候会更新pos的值,当你下次再来读取的时候是从pos的位置开始的,而不是从头开始,所以第二次获取String中的值的时候是不全的,”AAAAA“丢掉了,这也就导致了两次调用request.getInputStream,第二次的时候肯定获取不了值,因为第一次读取完成之后pos指针在末尾,下次再读取肯定读取不到,同request.getInputStream两次调用返回的对象是同一个对象。读取的是同一个Stream。

    但是仔细查看API文档你会发现有这样一个方法:

    public void reset()
    Resets the buffer to the marked position. The marked position is 0 unless another position was marked or an offset was specified in the constructor. 

    就是可以把pos的指针的位置重置为起始位置,但是调用它是有条件的,不是所有的IO读取流都能调用这个方法.看一下有这个方法

    public boolean markSupported()
    Tests if this input stream supports the mark and reset methods. Whether or not mark and reset are supported is an invariant property of a particular input stream instance. 
    The markSupported method of InputStream returns false.

    这个方法可以判断是不是支持reset()方法的调用,

    通过查看ServletInputStream的源码,

    ServletInputStream继承了InputStream同时没有重写reset()方法,查看一下InputStream源码,InputStream的reset()方法源码是这样的:

    public synchronized void reset() throws IOException {
             throw new IOException("mark/reset not supported");
         }

    调用reset方法直接抛出异常,所以ServletInputStream是不能调用reset方法,这就导致了只能调用一次getInputStream(),第二次调用的时候没有办法获取到InputStream流中的原因。

    现在我们更改一下上边读取String字符串的例子:

     1       @Test
     2     public void testByteArrayInputStream() throws Exception {
     3         String str = "AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
     4         //ByteArrayInputStream是把一个byte数组转换成一个字节流,读取的内容是从byte数组中读取的
     5         ByteArrayInputStream byteInputStream = new ByteArrayInputStream(str.getBytes());
     6         
     7         //调用这个方法,会影响到下次读取,下次再调用这个方法,读取的起始点会后移5个byte
     8         byteInputStream.read(new byte[5]);
     9         byteInputStream.reset();//调用reset方法可以使read中的pos指针复位
    10         
    11         
    12         //ByteArrayOutputStream生成对象的时候,是生成一个100大小的byte的缓冲区,写入的时候,是把内容写入内存中的一个缓冲区
    13         ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(100);
    14         
    15         int i =0;
    16         byte [] b = new byte[100];
    17         while((i = byteInputStream.read(b))!= -1){
    18             byteOutput.write(b, 0, i);
    19         }
    20         System.out.println(new String(byteOutput.toByteArray()));
    21     }
    

    打印结果是:AAAAACCCCcCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

    在第8行添加一行代码,byteInputStream调用reset()方法,打印的结果回复了正常。

  • 相关阅读:
    常用SQL语句
    一个很准的心理测试
    视图,存储实现行列转换
    『原创』+『转载』配置模拟器网络环境(访问局域网)Step by Step!
    『原创』老范的XML文档编辑程序——不是一般的山寨!(原创附程序)
    『原创』+『参考』基于PPC的图像对比程序——使用直方图度量
    『原创』.Net CF下ListView的数据绑定
    『原创』+『参考』使用C#在PPC的Today界面上的任务栏加入应用程序图标
    关于RDA远程访问数据库的一个例子(亲手完成,不容易啊)
    『原创』+『参考』亲手实验:使用C#在PPC中播放声音
  • 原文地址:https://www.cnblogs.com/whx7762/p/9475795.html
Copyright © 2011-2022 走看看