zoukankan      html  css  js  c++  java
  • HttpURLConnection

    HttpURLConnection  实现网络访问文件,并且将获取到的数据存放到字节数组中

     1 public class HttpURLConnHelper {
     2     private final static String TAG = "MyHttpHelperUtil";
     3 
     4     /**
     5      * 作用:实现网络访问文件,将获取到的数据存在字节数组中
     6      * 
     7      * @param url
     8      *            :访问网络的url地址
     9      * @return byte[]
    10      */
    11     public static byte[] downLoadByteFromURL(String url) { 
    12         Log.i(TAG, "==以get方式访问网络");
    13         HttpURLConnection httpConn = null;
    14         //BufferedInputStream 是一个带有缓冲区域的inputStream
    15         BufferedInputStream bis = null;
    16         ByteArrayOutputStream baos = null;
    17         try {
    18             Log.i(TAG, "==1、创建URL对象,url-->" + url);
    19             URL urlObj = new URL(url);
    20             Log.i(TAG, "==2、创建连接,openConnection");
    21             httpConn = (HttpURLConnection) urlObj.openConnection();
    22             // 以下两项都是默认值。虽然可以不写,但是建议写上。
    23             httpConn.setRequestMethod("GET");
    24             // // 设置将服务器返回数据写入到httpConn对象
    25             // httpConn.setDoInput(true);
    26             // 设置访问超时时间
    27             httpConn.setConnectTimeout(1000);
    28             // // 设置是否使用缓存
    29             // httpConn.setUseCaches(false);
    30             // // 建立远程对象实际连接
    31             // httpConn.connect();
    32             Log.i(TAG,
    33                     "==3、getResponseCode()方法获取服务器的返回码:"
    34                             + httpConn.getResponseCode());
    35             if (httpConn.getResponseCode() == 200) {
    36                 Log.i(TAG,
    37                         "==4、调用 HttpURLConnection对象的getInputStream()方法获取服务器返回的流信息");
    38                 bis = new BufferedInputStream(httpConn.getInputStream());
    39                 Log.i(TAG, "==5、执行标准的IO流操作");
    40                 baos = new ByteArrayOutputStream();
    41                 int c = 0;
    42                 byte[] buffer = new byte[8 * 1024];
    43                 while ((c = bis.read(buffer)) != -1) {
    44                     baos.write(buffer, 0, c);
    45                     baos.flush();
    46                 }
    47                 byte[] result = baos.toByteArray();
    48                 Log.i(TAG, "result==" + result.length);
    49                 return result;
    50             }
    51         } catch (Exception e) {
    52             e.printStackTrace();
    53         } finally {
    54             try {
    55                 if (bis != null) {
    56                     bis.close();
    57                 }
    58                 if (baos != null) {
    59                     baos.close();
    60                 }
    61                 if (httpConn != null) {
    62                     httpConn.disconnect();
    63                 }
    64             } catch (IOException e) {
    65                 e.printStackTrace();
    66             }
    67         }
    68         return null;
    69     }

    BufferedInputStream是一个带有缓冲区域的InputStream,它的继承体系如下:

    InputStream 
    |__FilterInputStream 
            |__BufferedInputStream 

    FilterInputStream: 
    FilterInputStream通过装饰器模式将InputStream封装至内部的一个成员变量:

    Java代码  收藏代码
    1. protected volatile InputStream in; 

    需要注意的是该成员变量使用了volatile关键字进行修饰,这意味着该成员变量的引用的内存可见性为多线程即时可见的。 
    其它地方FilterInputStream将所有的操作委托给了in这个成员进行操作。 

    了解了这些过后,来仔细看看BufferedInputStream的成员变量: 

    
    
     1 private static int defaultBufferSize = 8192 //该变量定义了默认的缓冲大小  
     2   
     3 protected volatile byte buf[]; //缓冲数组,注意该成员变量同样使用了volatile关键字进行修饰,作用为在多线程环境中,当对该变量引用进行修改时保证了内存的可见性。  
     4   
     5 private static final AtomicReferenceFieldUpdater<BufferedInputStream, byte[]> bufUpdater = AtomicReferenceFieldUpdater.newUpdater(BufferedInputStream.class,  byte[].class, "buf")//缓存数组的原子更新器,该成员变量与buf数组的volatile关键字共同组成了buf数组的原子更新功能实现。  
     6   
     7 protected int count;//该成员变量表示目前缓冲区域中有多少有效的字节。  
     8   
     9 protected int pos;//该成员变量表示了当前缓冲区的读取位置。  
    10   
    11 protected int markpos = -1;/*表示标记位置,该标记位置的作用为:实现流的标记特性,即流的某个位置可以被设置为标记,允许通过设置reset(),将流的读取位置进行重置到该标记位置,但是InputStream注释上明确表示,该流不会无限的保证标记长度可以无限延长,即markpos=15,pos=139734,该保留区间可能已经超过了保留的极限(如下)*/  
    12   
    13 protected int marklimit;/*该成员变量表示了上面提到的标记最大保留区间大小,当pos-markpos> marklimit时,mark标记可能会被清除(根据实现确定)。*/  
    
    
    
     

    通过构造函数可以看到:初始化了一个byte数组作为缓冲区域 

    1 public BufferedInputStream(InputStream in, int size) {  
    2     super(in);  
    3         if (size <= 0) {  
    4             throw new IllegalArgumentException("Buffer size <= 0");  
    5         }  
    6     buf = new byte[size];  
    7 }  

    这个类中最为重要的方法是fill()方法,它提供了缓冲区域的读取、写入、区域元素的移动更新等。下面着重分析一下该方法: 

     1 private void fill() throws IOException {
     2         byte[] buffer = getBufIfOpen();
     3     if (markpos < 0) {
     4           /*如果不存在标记位置(即没有需要进行reset的位置需求)
     5             则可以进行大胆地直接重置pos标识下一可读取位置,但是这样
     6             不是会读取到以前的旧数据吗?不用担心,在后面的代码里☆会实现输入流的新 
     7             数据填充*/
     8         pos = 0;        
     9else if (pos >= buffer.length){
    10        /* 位置大于缓冲区长度,这里表示已经没有可用空间了 */
    11         if (markpos > 0) {    
    12              /* 表示存在mark位置,则要对mark位置到pos位置的数据予以保留,
    13                 以确保后面如果调用reset()重新从mark位置读取会取得成功*/
    14         int sz = pos - markpos;
    15                 /*该实现是通过将缓冲区域中markpos至pos部分的移至缓冲区头部实现*/
    16         System.arraycopy(buffer, markpos, buffer, 0, sz);
    17         pos = sz;
    18         markpos = 0;
    19         } else if (buffer.length >= marklimit) {
    20                 /* 如果缓冲区已经足够大,可以容纳marklimit,则直接重置*/
    21                 markpos = -1;    
    22         pos = 0;/* 丢弃所有的缓冲区内容 */
    23         } else {        
    24                 /* 如果缓冲区还能增长的空间,则进行缓冲区扩容*/
    25         int nsz = pos * 2;
    26                 /*新的缓冲区大小设置成满足最大标记极限即可*/
    27         if (nsz > marklimit)
    28             nsz = marklimit;
    29         byte nbuf[] = new byte[nsz];
    30                 //将原来的较小的缓冲内容COPY至增容的新缓冲区中
    31         System.arraycopy(buffer, 0, nbuf, 0, pos);
    32                 //这里使用了原子变量引用更新,确保多线程环境下内存的可见性
    33                 if (!bufUpdater.compareAndSet(this, buffer, nbuf)) {
    34                     // Can't replace buf if there was an async close.
    35                     // Note: This would need to be changed if fill()
    36                     // is ever made accessible to multiple threads.
    37                     // But for now, the only way CAS can fail is via close.
    38                     // assert buf == null;
    39                     throw new IOException("Stream closed");
    40                 }
    41                 buffer = nbuf;
    42         }
    43         count = pos;
    44         //从原始输入流中读取数据,填充缓冲区
    45     int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
    46         //根据实际读取的字节数更新缓冲区中可用字节数
    47         if (n > 0)
    48             count = n + pos;
    49     }

    整个fill的过程,可以看作是BufferedInputStream对外提供滑动读取的功能实现,通过预先读入一整段原始输入流数据至缓冲区中,而外界对BufferedInputStream的读取操作实际上是在缓冲区上进行,如果读取的数据超过了缓冲区的范围,那么BufferedInputStream负责重新从原始输入流中载入下一截数据填充缓冲区,然后外界继续通过缓冲区进行数据读取。这样的设计的好处是:避免了大量的磁盘IO,因为原始的InputStream类实现的read是即时读取的,即每一次读取都会是一次磁盘IO操作(哪怕只读取了1个字节的数据),可想而知,如果数据量巨大,这样的磁盘消耗非常可怕。而通过缓冲区的实现,读取可以读取缓冲区中的内容,当读取超过缓冲区的内容后再进行一次磁盘IO,载入一段数据填充缓冲,那么下一次读取一般情况下就直接可以从缓冲区读取,减少了磁盘IO。减少的磁盘IO大致可以通过以下方式计算(限read()方式): 

    length  流的最终大小 
    bufSize 缓冲区大小 

    则通过缓冲区实现的输入流BufferedInputStream的磁盘IO数为原始InputStream磁盘IO的 
    1/(length/bufSize) 

    read方法解析:该方法返回当前位置的后一位置byte值(int表示). 

     1 public synchronized int read() throws IOException {
     2     if (pos >= count) {
     3            /*表示读取位置已经超过了缓冲区可用范围,则对缓冲区进行重新填充*/
     4         fill();
     5            /*当填充后再次读取时发现没有数据可读,证明读到了流末尾*/
     6         if (pos >= count)
     7         return -1;
     8     }
     9         /*这里表示读取位置尚未超过缓冲区有效范围,直接返回缓冲区内容*/
    10     return getBufIfOpen()[pos++] & 0xff;
    11 }

    一次读取多个字节(尽量读,非贪婪) 

     1 private int read1(byte[] b, int off, int len) throws IOException {
     2     int avail = count - pos;
     3     if (avail <= 0) {
     4         /*这里使用了一个巧妙的机制,如果读取的长度大于缓冲区的长度
     5               并且没有markpos,则直接从原始输入流中进行读取,从而避免无谓的
     6               COPY(从原始输入流至缓冲区,读取缓冲区全部数据,清空缓冲区,
     7               重新填入原始输入流数据)*/
     8         if (len >= getBufIfOpen().length && markpos < 0) {
     9         return getInIfOpen().read(b, off, len);
    10         }
    11             /*当无数据可读时,从原始流中载入数据到缓冲区中*/
    12         fill();
    13         avail = count - pos;
    14         if (avail <= 0) return -1;
    15     }
    16     int cnt = (avail < len) ? avail : len;
    17         /*从缓冲区中读取数据,返回实际读取到的大小*/
    18     System.arraycopy(getBufIfOpen(), pos, b, off, cnt);
    19     pos += cnt;
    20     return cnt;
    21     }

    以下方法和上面的方法类似,唯一不同的是,上面的方法是尽量读,读到多少是多少,而下面的方法是贪婪的读,没有读到足够多的数据(len)就不会返回,除非读到了流的末尾。该方法通过不断循环地调用上面read1方法实现贪婪读取

     1 public synchronized int read(byte b[], int off, int len)
     2     throws IOException
     3     {
     4         getBufIfOpen(); // Check for closed stream
     5         if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
     6         throw new IndexOutOfBoundsException();
     7     } else if (len == 0) {
     8             return 0;
     9         }
    10 
    11     int n = 0;
    12         for (;;) {
    13             int nread = read1(b, off + n, len - n);
    14             if (nread <= 0) 
    15                 return (n == 0) ? nread : n;
    16             n += nread;
    17             if (n >= len)
    18                 return n;
    19             // if not closed but no bytes available, return
    20             InputStream input = in;
    21             if (input != null && input.available() <= 0)
    22                 return n;
    23         }
    24     }

    略过多少字节 

     1 public synchronized long skip(long n) throws IOException {
     2         getBufIfOpen(); // Check for closed stream
     3     if (n <= 0) {
     4         return 0;
     5     }
     6     long avail = count - pos;
     7      
     8         if (avail <= 0) {
     9             // If no mark position set then don't keep in buffer
    10             //从上面的注释可以知道,这也是一个巧妙的方法,如果没有mark标记,
    11             // 则直接从原始输入流中skip
    12             if (markpos <0) 
    13                 return getInIfOpen().skip(n);
    14             
    15             // Fill in buffer to save bytes for reset
    16             fill();
    17             avail = count - pos;
    18             if (avail <= 0)
    19                 return 0;
    20         }
    21         //该方法的实现为尽量原则,不保证一定略过规定的字节数。
    22         long skipped = (avail < n) ? avail : n;
    23         pos += skipped;
    24         return skipped;
    25     }

    估计目前可用的字节数,原始流中可用的字节数+缓冲区中可用的字节数 

    1 public synchronized int available() throws IOException {
    2     return getInIfOpen().available() + (count - pos);
    3     }

    标记位置: 

    1 public synchronized void reset() throws IOException {
    2         getBufIfOpen(); // Cause exception if closed
    3     if (markpos < 0)
    4         throw new IOException("Resetting to invalid mark");
    5     pos = markpos;
    6     }

    重置位置:该实现清晰的表明下一读取位置被推到了以前的标记位置,以实现重新读取区段的功能 

    1 public synchronized void reset() throws IOException {
    2         getBufIfOpen(); // Cause exception if closed
    3     if (markpos < 0)
    4         throw new IOException("Resetting to invalid mark");
    5     pos = markpos;
    6     }

    关闭流:首先通过线程安全的方式设置了内部的缓冲区引用为空,然后再对原始输入流进行关闭。 

     1 public void close() throws IOException {
     2         byte[] buffer;
     3         while ( (buffer = buf) != null) {
     4             if (bufUpdater.compareAndSet(this, buffer, null)) {
     5                 InputStream input = in;
     6                 in = null;
     7                 if (input != null)
     8                     input.close();
     9                 return;
    10             }
    11             // Else retry in case a new buf was CASed in fill()
    12         }
    13     }
  • 相关阅读:
    SpringBoot系列之切换log4j日志框架
    SpringBoot系列之日志框架使用教程
    SpringBoot系列之集成logback实现日志打印(篇二)
    源码学习系列之SpringBoot自动配置(篇二)
    SpringBoot系列之@Conditional注解用法简介
    7.Maven命令
    6.Maven构建过程的各个环节
    5.Maven坐标
    4.用IntelliJ IDEA 创建Maven Web
    3.用IntelliJ IDEA 创建Maven
  • 原文地址:https://www.cnblogs.com/xuanyuanzhuo-blog/p/3983380.html
Copyright © 2011-2022 走看看