最近在做一个需求时,遇到流的问题,在这总结下:
需求是,我要调别人的接口,对方给我返回一个pdf的电子发票流,我接收后进行保存到本地
首先贴一张猿友的图,
IO流分两种;字符流&字节流
百度===>字节流和字符流什么是流
流是个抽象的概念,是对输入输出设备的抽象,输入流可以看作一个输入通道,输出流可以看作一个输出通道。
输入流是相对程序而言的,外部传入数据给程序需要借助输入流。
输出流是相对程序而言的,程序把数据传输到外部需要借助输出流。字节流与字符流
字节流是由字节组成的,字符流是由字符组成的字节与字符
位:数据存储的最小单位。每个二进制数字0或者1就是1个位;
字节:8个位构成一个字节;即:1 byte (字节)= 8 bit(位);
字符:a、A、中、+、*、の......均表示一个字符;一般utf-8 编码下,一个汉字字符占用3个字节;一般gbk 编码下,一个汉字字符占用2个字节;继续百度
字节数据是二进制形式的,是我们计算机存储的一种形式;
字符就是我们在各种客户端看到的各种文字,是内存中的一种状态;
字节与字符之间差了一个编码;
回到最初的需求,要在别人接口中获取一个pdf流,那么获取到的是什么?
我们知道网络传输的都是二级制数据,字符流又不是二级制,那么不论对方是怎么处理的文件,他最终传给我的一定是字节流。
所以不管接到后怎么处理,反正直接节流就对了,
// 获取一个浏览器 DefaultHttpClient httpclient = new DefaultHttpClient(); // 获取get请求 HttpGet httpget = new HttpGet(url); // 请求对方服务器 HttpResponse response = httpclient.execute(httpget); // 获取返回数据 InputStream inputStream = response.getEntity().getContent();
事实证明了上面的结论,返回的确实是字节流;
然后怎么处理呢?
那就直接把字节流保存吧,反正保存后也是字节,
// new 一个本地文件 File file = new File("D:\11111.pdf"); // 创建相对于程序的输出流 OutputStream outputStream = null; try { outputStream = new FileOutputStream(file); // 读取数据 byte[] b = new byte[1024];// b - 数据 int off = 0;// off - 数据中的起始偏移量。 int len = -1;// len - 要写入的字节数。 while ((len = inputStream.read(b)) != -1) {// 从输入流读取一些字节数,并将它们存储到缓冲区b。实际读取的字节数作为整数返回,如果没有字节可用,因为流在文件末尾,则返回值-1;否则,读取至少一个字节并存储到b。 outputStream.write(b, off, len);// 数组b中的一些字节按顺序写入输出流; 元素off是写入的第一个字节,len是此操作写入的最后一个字节。 } outputStream.flush();// 刷新此输出流并强制任何缓冲的输出字节被写出。 } catch (IOException e) { System.out.println("异常"); e.printStackTrace(); } finally { inputStream.close();//关闭此输入流并释放与此流相关联的任何系统资源。 outputStream.close();//关闭此输出流并释放与此流相关联的任何系统资源。 }
然后尝试他们的子类们,例:BufferedInputStream 与 BufferedOutputStream
// 获取一个浏览器 DefaultHttpClient httpclient = new DefaultHttpClient(); // 获取get请求 HttpGet httpget = new HttpGet(url); // 请求对方服务器 HttpResponse response = httpclient.execute(httpget); // 获取返回数据 BufferedInputStream inputStream = (BufferedInputStream)response.getEntity().getContent(); // new 一个本地文件 File file = new File("D:\11111.pdf"); // 创建相对于程序的输出流 BufferedOutputStream outputStream = null; try { FileOutputStream fileOutputStream = new FileOutputStream(file); outputStream = new BufferedOutputStream(fileOutputStream); // 读取数据 byte[] b = new byte[1024];// b - 数据 int off = 0;// off - 数据中的起始偏移量。 int len = -1;// len - 要写入的字节数。 while ((len = inputStream.read(b)) != -1) {// 从输入流读取一些字节数,并将它们存储到缓冲区b。实际读取的字节数作为整数返回,如果没有字节可用,因为流在文件末尾,则返回值-1;否则,读取至少一个字节并存储到b。 outputStream.write(b, off, len);// 数组b中的一些字节按顺序写入输出流; 元素off是写入的第一个字节,len是此操作写入的最后一个字节。 } outputStream.flush();// 刷新此输出流并强制任何缓冲的输出字节被写出。 } catch (IOException e) { System.out.println("异常"); e.printStackTrace(); } finally { inputStream.close();//关闭此输入流并释放与此流相关联的任何系统资源。 outputStream.close();//关闭此输出流并释放与此流相关联的任何系统资源。 }
他们的子类在使用中并没有太大的区别,网上扒了下他们各自的特点,至于具体选择使用哪个类,可以参考这篇文章:https://www.cnblogs.com/penghuster/p/4869153.html
*********************************************
*******************字符流*********************
*********************************************
然后,如果接口返回的是字符串应该怎么处理呢,于是我换了一个get地址,返回的是一个字符串。
对于这样的返回,我们一般情况下肯定是不需要保存的的,肯定是在内存中转为字符串进行编辑展示什么的,但是为了理解流之间的关系,保存一下试试,
// 获取一个浏览器 DefaultHttpClient httpclient = new DefaultHttpClient(); // 获取get请求 HttpGet httpget = new HttpGet(url); // 请求对方服务器 HttpResponse response = httpclient.execute(httpget); // 获取返回数据 InputStream inputStream = response.getEntity().getContent(); InputStreamReader inputStreamReader = null; OutputStreamWriter outputStreamWriter = null; try { // 将返回的字节流转为字符流 inputStreamReader = new InputStreamReader(inputStream,"utf-8"); // 再将转出的字符流再转回字节流保存到磁盘 File file = new File("D:\11.txt");// 二级制字节文件 FileOutputStream fileOutputStream = new FileOutputStream(file);// 用于程序输出的字节流 outputStreamWriter = new OutputStreamWriter(fileOutputStream,"utf-8");// 将字符流转为字节流 // 读取数据 char[] chars = new char[1024]; int off =0; int len = -1; while ((len=inputStreamReader.read(chars))!=-1){ System.out.println(len); outputStreamWriter.write(chars,off,len); } } catch (IOException e) { System.out.println("异常"); e.printStackTrace(); } finally { // 关闭资源 outputStreamWriter.flush(); outputStreamWriter.close(); inputStreamReader.close(); }
事实证明是可以的,接口返回字节流,然后将字节流转为我们认识的字符,再将我们认识的字符转为二进制字节进行保存,基本可以明白流之间的关系了。
那么正常情况下怎么转为字符串打印呢?
// 获取一个浏览器 DefaultHttpClient httpclient = new DefaultHttpClient(); // 获取get请求 HttpGet httpget = new HttpGet(url); // 请求对方服务器 HttpResponse response = httpclient.execute(httpget); // 获取返回数据 InputStream inputStream = response.getEntity().getContent(); InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; try { // 将字节流转为字符流 inputStreamReader = new InputStreamReader(inputStream,"utf-8"); // BufferedReader类是从字符输入流中读取文本并缓冲字符,以便有效地读取字符,数组和行 bufferedReader = new BufferedReader(inputStreamReader); // 创建保存字符串的StringBuffer StringBuffer str = new StringBuffer(); String s = null; while ((s=bufferedReader.readLine())!=null){ str.append(s); } // 打印 System.out.println(str.toString()); } catch (IOException e) { System.out.println("异常"); e.printStackTrace(); } finally { inputStreamReader.close(); bufferedReader.close(); }
这里有一点点不用,不再使用输出流,因为相对于程序没有写磁盘嘛,所以用不着输出流。
使用了BufferedReader ,BufferedReader类是从字符输入流中读取文本并缓冲字符,以便有效地读取字符,数组和行,具体解释参考:https://blog.csdn.net/ai_bao_zi/article/details/81134801
还用了StringBuffer ,补充一个String,StringBuffer,StringBuilder三者的使用方法和区别:https://blog.csdn.net/qq_37856300/article/details/84340288