首页 > 技术文章 > Request.getInputStrema只能读取一次的分析过程

whx7762 2018-08-14 17:07 原文

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()方法,打印的结果回复了正常。

推荐阅读