文章出自:听云博客

题主将以三个章节的篇幅来讲解JAVA IO的内容 。

第一节JAVA IO包的框架体系和源码分析,第二节,序列化反序列化和IO的设计模块,第三节异步IO。

本文是第一节。

      IO框架

从上图我们可以看出IO可以分为两大块 字节流和字符流

字节流是 InputStream 和 OutputStream 分别对应输入与输出

字符流是Reader和Writer分别对应输入与输出

       ByteArrayInputStream 

它字节数组输入流。继承于InputStream。它包含一个数组实现的缓冲区ByteArrayInputStream也是数组实现的,提供read()来读取数据,内部有一个计数器用来确定下一个要读取的字节。

分析源码

//pos是下一个会被读取字节的索引

//count字节流的长度

//pos为0就是从0开始读取

//读取下一个字节, &0xff的意思是将高8位全部置0

// 将“字节流的数据写入到字节数组b中”

// off是“字节数组b的偏移地址”,表示从数组b的off开始写入数据

// len是“写入的字节长度”

        ByteArrayOutputStream

它是字节数组输出流。继承于OutputStream。ByteArrayOutputStream 实际也是数组实现的,它维护一个字节数组缓冲。缓冲区会自动扩容。

源码分析

//我们看到不带参的构造方法默认值是32 数组大小必须大于0否则会报 Negative initial size错误 ByteArrayOutputStream本质是一个byte数组

//是将字节数组buffer写入到输出流中,offset是从buffer中读取数据的起始下标,len是写入的长度。

//ensureCapacity方法是判断数组是否需要扩容

//System.arraycopy是写入的实现

//数组如果已经写满则grow

//int Capacity=oldCapacity<<1,简单粗暴容量X2

       Piped(管道) 

多线程可以通过管道实现线程中的通讯,在使用管道时必须PipedInputStream,PipedOutputStream配套缺一不可

       PipedInputStream 

//初始化管道

//链接管道

//将“管道输入流”和“管道输出流”绑定。

//调用的是PipedOutputStream的connect方法

       PipedOutputStream 

//指定配对的PedpedInputStream

示例

  1. package ioEx;
  2. import java.io.PipedInputStream;
  3. import java.io.PipedOutputStream;
  4. import java.io.IOException;
  5. public class PipedStreamEx {
  6. public static void main(String[] args) {
  7. Writer t1 = new Writer();
  8. Reader t2 = new Reader();
  9. //获取输入输出流
  10. PipedOutputStream out = t1.getOutputStream();
  11. PipedInputStream in = t2.getInputStream();
  12. try {
  13. //将管道连接 也可以这样写 out.connect(in);
  14. in.connect(out);
  15. t1.start();
  16. t2.start();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. class Reader extends Thread {
  23. private PipedInputStream in = new PipedInputStream();
  24. // 获得“管道输入流”对象
  25. public PipedInputStream getInputStream(){
  26. return in;
  27. }
  28. @Override
  29. public void run(){
  30. readOne() ;
  31. //readWhile() ;
  32. }
  33. public void readOne(){
  34. // 虽然buf的大小是2048个字节,但最多只会从输入流中读取1024个字节。
  35. // 输入流的buffer的默认大小是1024个字节。
  36. byte[] buf = new byte[2048];
  37. try {
  38. System.out.println(new String(buf,0,in.read(buf)));
  39. in.close();
  40. } catch (IOException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. // 从输入流中读取1024个字节
  45. public void readWhile() {
  46. int total=0;
  47. while(true) {
  48. byte[] buf = new byte[1024];
  49. try {
  50. int len = in.read(buf);
  51. total += len;
  52. System.out.println(new String(buf,0,len));
  53. // 若读取的字节总数>1024,则退出循环。
  54. if (total > 1024)
  55. break;
  56. } catch (IOException e) {
  57. e.printStackTrace();
  58. }
  59. }
  60. try {
  61. in.close();
  62. } catch (IOException e) {
  63. e.printStackTrace();
  64. }
  65. }
  66. }
  67. class Writer extends Thread {
  68. private PipedOutputStream out = new PipedOutputStream();
  69. public PipedOutputStream getOutputStream(){
  70. return out;
  71. }
  72. @Override
  73. public void run(){
  74. writeSmall1024();
  75. //writeBigger1024();
  76. }
  77. // 向输出流中写入1024字节以内的数据
  78. private void writeSmall1024() {
  79. String strInfo = "I < 1024" ;
  80. try {
  81. out.write(strInfo.getBytes());
  82. out.close();
  83. } catch (IOException e) {
  84. e.printStackTrace();
  85. }
  86. }
  87. // 向“管道输出流”中写入一则较长的消息
  88. private void writeBigger1024() {
  89. StringBuilder sb = new StringBuilder();
  90. for (int i=0; i<103; i++)
  91. sb.append("0123456789");
  92. try {
  93. // sb的长度是1030 将1030个字节写入到输出流中, 测试一次只能读取1024个字节
  94. out.write( sb.toString().getBytes());
  95. out.close();
  96. } catch (IOException e) {
  97. e.printStackTrace();
  98. }
  99. }
  100. }

       ObjectInputStream 和 ObjectOutputStream 

Object流可以将对象进行序列化操作。ObjectOutputStream可以持久化存储对象, ObjectInputStream,可以读出这些这些对象。

源码很简单直接上例子,关于序列化的内容题主将于下一节叙述

  1. package ioEx;
  2.  
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7. import java.io.Serializable;
  8. import java.util.Map;
  9. import java.util.Map.Entry;
  10. import java.util.HashMap;
  11. import java.util.Iterator;
  12.  
  13. public class ObjectStreamTest {
  14. private static final String FILE_NAME= "test.txt";
  15.  
  16. public static void main(String[] args) {
  17. testWrite();
  18. testRead();
  19. }
  20. private static void testWrite() {
  21. try {
  22. ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
  23. out.writeByte((byte)1);
  24. out.writeChar('a');
  25. out.writeInt(20160329);
  26. out.writeFloat(3.14F);
  27. out.writeDouble(Math.PI);
  28. out.writeBoolean(true);
  29. // 写入HashMap对象
  30. Map<String,String> map = new HashMap<String,String>();
  31. map.put("one", "one");
  32. map.put("two", "two");
  33. map.put("three", "three");
  34. out.writeObject(map);
  35. // 写入自定义的Box对象,Box实现了Serializable接口
  36. Test test = new Test("a", 1, "a");
  37. out.writeObject(test);
  38.  
  39. out.close();
  40. } catch (Exception ex) {
  41. ex.printStackTrace();
  42. }
  43. }
  44. private static void testRead() {
  45. try {
  46. ObjectInputStream in = new ObjectInputStream(new FileInputStream(FILE_NAME));
  47. System.out.println(in.readBoolean());
  48. System.out.println(in.readByte()&0xff);
  49. System.out.println(in.readChar());
  50. System.out.println(in.readInt());
  51. System.out.println(in.readFloat());
  52. System.out.println(in.readDouble());
  53. Map<String,String> map = (HashMap) in.readObject();
  54. Iterator<Entry<String, String>> iter = map.entrySet().iterator();
  55. while (iter.hasNext()) {
  56. Map.Entry<String, String> entry = (Map.Entry<String, String>)iter.next();
  57. System.out.println(entry.getKey()+":"+ entry.getValue());
  58. }
  59. Test test = (Test) in.readObject();
  60. System.out.println("test: " + test);
  61.  
  62. in.close();
  63. } catch (Exception e) {
  64. e.printStackTrace();
  65. }
  66. }
  67. }
  68. class Test implements Serializable {
  69. private String a;
  70. private int b;
  71. private String c;
  72.  
  73. public Test(String a, int b, String c) {
  74. this.a = a;
  75. this.b = b;
  76. this.c = c;
  77. }
  78.  
  79. @Override
  80. public String toString() {
  81. return "a, "+b+", c";
  82. }
  83. }

       FileInputStream 和 FileOutputStream

FileInputStream 是文件输入流,它继承于InputStream。我们使用FileInputStream从某个文件中获得输入字节。

FileOutputStream 是文件输出流,它继承于OutputStream。我们使用FileOutputStream 将数据写入 File 或 FileDescriptor 的输出流。

File操作十分简单,这里就不再展示示例了。

FilterInputStream

它的作用是用来封装其它的输入流,并为它们提供额外的功能。它的常用的子类有BufferedInputStream和DataInputStream和PrintStream。。

BufferedInputStream的作用就是为“输入流提供缓冲功能,以及mark()和reset()功能”。

DataInputStream 是用来装饰其它输入流,它允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用          DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据

(01) BufferedOutputStream的作用就是为“输出流提供缓冲功能”。

(02) DataOutputStream 是用来装饰其它输出流,将DataOutputStream和DataInputStream输入流配合使用,“允许应用程序以与机器无关方式从底层输入流中读写基本 Java 数据类型”。

(03) PrintStream 是用来装饰其它输出流。它能为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。

主要了解一下Buffered流

       BufferedInputStream 

它是缓冲输入流。它继承于FilterInputStream。它的作用是为另一个输入流添加一些功能,例如,提供“缓冲功能”以及支持“mark()标记”和“reset()重置方法”。它本质上是通过一个内部缓冲区数组实现的

源码分析

方法不再一一解读,重点讲两个方法 read1 和 fill

       根据fill()中的判断条件可以分为五种情况

情况1:读取完buffer中的数据,并且buffer没有被标记

(01) if (markpos < 0) 它的作用是判断“输入流是否被标记”。若被标记,则markpos大于/等于0;否则markpos等于-1。

(02) 在这种情况下:通过getInIfOpen()获取输入流,然后接着从输入流中读取buffer.length个字节到buffer中。

(03) count = n + pos; 这是根据从输入流中读取的实际数据的多少,来更新buffer中数据的实际大小。

情况2:读取完buffer中的数据,buffer的标记位置>0,并且buffer中没有多余的空间

这种情况发生的情况是 — — 输入流中有很长的数据,我们每次从中读取一部分数据到buffer中进行操作。当我们读取完buffer中的数据之后,并且此时输入流存在标记时;那么,就发生情况2。此时,我们要保留“被标记位置”到“buffer末尾”的数据,然后再从输入流中读取下一部分的数据到buffer中。

其中,判断是否读完buffer中的数据,是通过 if (pos >= count) 来判断的;

判断输入流有没有被标记,是通过 if (markpos < 0) 来判断的。

判断buffer中没有多余的空间,是通过 if (pos >= buffer.length) 来判断的。

理解这个思想之后,我们再对这种情况下的fill()代码进行分析,就特别容易理解了。

(01) int sz = pos - markpos; 作用是“获取‘被标记位置’到‘buffer末尾’”的数据长度。

(02) System.arraycopy(buffer, markpos, buffer, 0, sz); 作用是“将buffer中从markpos开始的数据”拷贝到buffer中(从位置0开始填充,填充长度是sz)。接着,将sz赋值给pos,即pos就是“被标记位置”到“buffer末尾”的数据长度。

(03) int n = getInIfOpen().read(buffer, pos, buffer.length - pos); 从输入流中读取出“buffer.length - pos”的数据,然后填充到buffer中。

(04) 通过第(02)和(03)步组合起来的buffer,就是包含了“原始buffer被标记位置到buffer末尾”的数据,也包含了“从输入流中新读取的数据”。

情况3:读取完buffer中的数据,buffer被标记位置=0,buffer中没有多余的空间,并且buffer.length>=marklimit

执行流程如下,

(01) read() 函数中调用 fill()

(02) fill() 中的 else if (pos >= buffer.length) ...

(03) fill() 中的 else if (buffer.length >= marklimit) ...

说明:这种情况的处理非常简单。首先,就是“取消标记”,即 markpos = -1;然后,设置初始化位置为0,即pos=0;最后,再从输入流中读取下一部分数据到buffer中。

情况4:读取完buffer中的数据,buffer被标记位置=0,buffer中没有多余的空间,并且buffer.length<marklimit

执行流程如下,

(01) read() 函数中调用 fill()

(02) fill() 中的 else if (pos >= buffer.length) ...

(03) fill() 中的 else { int nsz = pos * 2; ... }

这种情况的处理非常简单。

(01) 新建一个字节数组nbuf。nbuf的大小是“pos*2”和“marklimit”中较小的那个数。

(02) 接着,将buffer中的数据拷贝到新数组nbuf中。通过System.arraycopy(buffer, 0, nbuf, 0, pos)

(03) 最后,从输入流读取部分新数据到buffer中。通过getInIfOpen().read(buffer, pos, buffer.length - pos);

注意:在这里,我们思考一个问题,“为什么需要marklimit,它的存在到底有什么意义?”我们结合“情况2”、“情况3”、“情况4”的情况来分析。

假设,marklimit是无限大的,而且我们设置了markpos。当我们从输入流中每读完一部分数据并读取下一部分数据时,都需要保存markpos所标记的数据;这就意味着,我们需要不断执行情况4中的操作,要将buffer的容量扩大……随着读取次数的增多,buffer会越来越大;这会导致我们占据的内存越来越大。所以,我们需要给出一个marklimit;当buffer>=marklimit时,就不再保存markpos的值了。

情况5:除了上面4种情况之外的情况

执行流程如下,

(01) read() 函数中调用 fill()

(02) fill() 中的 count = pos...

这种情况的处理非常简单。直接从输入流读取部分新数据到buffer中。

       BufferedOutputStream 

BufferedOutputStream 是缓冲输出流。它继承于FilterOutputStream。

BufferedOutputStream 的作用是为另一个输出流提供“缓冲功能”。

代码很简单,就不一一分析了 这里只分析一下write方法

 字符流和字符流的区别和使用

字符流的实现与字节流基本相同,最大的区别是字节流是通过byte[]实现的,字符流是通过char[]实现的,这里就不在一一介绍了

按照以下分类我们就可以很清楚的了解在何时使用字节流或字符流

一、按数据源分类:

1 、是文件: FileInputStream, FileOutputStream, ( 字节流 )FileReader, FileWriter( 字符 )

2 、是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 字节流 )

3 、是 Char[]: CharArrayReader, CharArrayWriter( 字符流 )

4 、是 String: StringBufferInputStream, StringBufferOuputStream ( 字节流 )StringReader, StringWriter( 字符流 )

5 、网络数据流: InputStream, OutputStream,( 字节流 ) Reader, Writer( 字符流 )

二、按是否格式化输出分:要格式化输出: PrintStream, PrintWriter

三、按是否要缓冲分:要缓冲: BufferedInputStream, BufferedOutputStream,( 字节流 ) BufferedReader, BufferedWriter( 字符流 )

四、按数据格式分:

1 、二进制格式(只要不能确定是纯文本的) : InputStream, OutputStream 及其所有带 Stream 结束的子类

2 、纯文本格式(含纯英文与汉字或其他编码方式); Reader, Writer 及其所有带 Reader, Writer 的子类

五、按输入输出分:

1 、输入: Reader, InputStream 类型的子类

2 、输出: Writer, OutputStream 类型的子类

六、特殊需要:

1 、从 Stream 到 Reader,Writer 的转换类: InputStreamReader, OutputStreamWriter

2 、对象输入输出: ObjectInputStream, ObjectOutputStream

3 、进程间通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter

4 、合并输入: SequenceInputStream

5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader

原文链接:http://blog.tingyun.com/web/article/detail/351

JAVA IO 字节流与字符流的更多相关文章

  1. Java IO 字节流与字符流 (三)

    概述 IO流用来处理设备之间的数据传输 Java对数据的操作时通过流的方式 Java用于操作流的对象都在IO包中 流按操作的数据分为:字节流和字符流 流按流向不同分为:输入流和输出流 IO流常用基类 ...

  2. Java IO 字节流与字符流 (二)

    1. 什么是流 Java中的流是对字节序列的抽象,我们可以想象有一个水管,只不过现在流动在水管中的不再是水,而是字节序列.和水流一样,Java中的流也具有一个“流动的方向”,通常可以从中读入一个字节序 ...

  3. Java IO 字节流与字符流 (五)

    Java的IO流分为字符流(Reader,Writer)和字节流(InputStream,OutputStream),字节流顾名思义字节流就是将文件的内容读取到字节数组,然后再输出到另一个文件中.而字 ...

  4. java中字节流与字符流的区别

    字节流 在I/O类库中,java.io.InputStream和java.io.OutputStream分别表示字节输入流和字节输出流,它们都是抽象类,不能实例化,数据流中的最小单位是字节,所以叫做字 ...

  5. Java中字节流和字符流的比较(转)

    字节流与和字符流的使用非常相似,两者除了操作代码上的不同之外,是否还有其他的不同呢? 实际上字节流在操作时本身不会用到缓冲区(内存),是文件本身直接操作的,而字符流在操作时使用了缓冲区,通过缓冲区再操 ...

  6. java中字节流和字符流的区别

    流分类: 1.Java的字节流   InputStream是所有字节输入流的祖先,而OutputStream是所有字节输出流的祖先.2.Java的字符流  Reader是所有读取字符串输入流的祖先,而 ...

  7. java IO(三):字符流

    */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...

  8. Java中字节流和字符流复制文件

    字节流和字符流复制文件的过程: 1.建立两个流对象 绑定数据源和目的地 2.遍历出需复制的文件写入复制过后的新文件中(只不过是遍历的时候是区分字节和字符的) 3.访问结束后关闭资源 字节流复制文件: ...

  9. 14、IO (字节流、字符流)

    输入和输出 * A:输入和输出 * a: 参照物 * 到底是输入还是输出,都是以Java程序为参照 * b: Output * 把内存中的数据存储到持久化设备上这个动作称为输出(写)Output操作 ...

随机推荐

  1. 问题:Virtual Box如何复制镜像

    今天遇到的情况就是vagrant启动的默认Virtalbox镜像变了,大致可以处理的方法是 1 修改vagrant的默认virtalbox 2 重新在这个新的virtualbox中安装需要的软件 3 ...

  2. react路由深度解析

    先看一段代码能否秒懂很重要 这是app.js  全局js的入口 import React from 'react' import { render } from 'react-dom' import ...

  3. 当前标识(IIS APPPOOL\dfcreport)没有对“C:\Windows\Microsoft.NET\Framework64\v2.0.50727\Temporary ASP.NET Files”的写访问权限。

    Asp.NET网站部署到IIS上面,浏览出现如下图所示错误. 原因原因最 原因: 1.IIS对该文件夹没有写的权限. 2.IIS和asp.net安装顺序错误,应该先IIS,然后asp.net. 3.没 ...

  4. js 比较好的博客

    1.0 作者:cloudgamer http://www.cnblogs.com/cloudgamer/archive/2010/04/01/ImageZoom.html

  5. js清除cookie

    例如要清除键为abc的cookie: document.cookie = "abc=0;expires=" + new Date().toUTCString();

  6. 【SQL】找出行数与自增标识值不相等的表(即有缺行)

    环境:mssql ent 2k8 r2 原理:遍历所有含自增列的用户表,用sp_spaceused过程分别获取每张表的行数并写入临时表,然后使用IDENT_CURRENT函数获取表的最大标识值,比较二 ...

  7. 二路归并排序算法实现-完整C语言程序

    /*********************************************************************************************** 1.设 ...

  8. 修复 XE8 FMX Windows 列印旋转文字问题

    问题:XE8 Firemonkey Windows 无法列印旋转文字(与显示在视窗里的代码相同时) 适用:XE8 Windows 平台(其它平台测试没问题) 修复前效果: 修复后效果: 修复方法: 请 ...

  9. 用xutils3.0进行下载

    写的例子比较简单,是用xutils3.0来进行下载项目更新 1.先通过网络请求,判断版本是否要更新 2.若要更新,则弹出一个弹窗,我用的是系统自带的Dialog,将下载的版本号及下载的内容提示展示出来 ...

  10. mysql和oracle jdbc连接

    加载驱动. Class.forName("oracle.jdbc.driver.OracleDriver"); 1 创建连接. Connection con = DriverMan ...