目录:系统学习 Java IO ---- 目录,概览html
是Java IO API中全部输入流的父类。
表示有序的字节流,换句话说,能够将 InputStream 中的数据做为有序的字节序列读取。
这在从文件读取数据或经过网络接收时很是有用。
InputStream 一般链接到某些数据源,如文件,网络链接,管道等
看以下代码片断:java
public class InputStreamExample { public static void main(String[] args) throws IOException { InputStream inputStream = new FileInputStream("D:\\out.txt"); //do something with data... int data = inputStream.read(); while (data != -1) { System.out.print((char) data); data = inputStream.read(); } inputStream.close(); } }
注意:为了代码清晰,这里并无考虑处理异常的状况,IO 异常处理有专门的介绍。算法
此方法返回的是 int 值,其中包含读取的字节的字节值,能够将返回的 int 强制转换为 char 输出。
若是 read() 方法返回 -1 ,则表示已到达流的末尾,这意味着在 InputStream 中再也不有要读取的数据。
也就是说,-1 做为 int 值,而不是 -1 做为 char 或 short,这里有区别!数组
InputStream 类还包含两个 read() 方法,这些方法能够将 InputStream 源中的数据读入字节数组。
这些方法是:缓存
一次读取一个字节数比一次读取一个字节要快得多,因此在能够的时候,使用这些读取方法而不是 read() 方法。网络
read(byte [])方法将尝试将尽量多的字节读入做为参数给出的字节数组,由于数组具备空间。
该方法返回一个 int ,其值是实际读取了多少字节,这点和 read() 方法不同。
若是能够从 InputStream 读取的字节少于字节数组的空间,则字节数组的其他部分将包含与读取开始以前相同的数据。例如:源码分析
InputStream input = new ByteArrayInputStream("123456789".getBytes()); byte[] bytes = new byte[4]; // 每次只读取 4 个字节 int data = input.read(bytes); while (data != -1) { System.out.print(new String(bytes)); data = input.read(bytes); }
将输出 123456789678 ,而不是预期的 123456789 !学习
由于第一次读取进 bytes 是 1234 ,第二次将是 5678 ,如今只剩下 9 一个数字了,注意此时 bytes 的值是 5678 ,而后再读取剩下 1个 9,不能装满 bytes 了,只能覆盖 bytes的第一个字节,最后返回的bytes 是 9678。
因此记住检查返回的 int 以查看实际读入字节数组的字节数。ui
int read(byte[], int offset, int length);方法和 read(byte [])方法差很少,只是增长了偏移量和指定长度。
和 read() 同样,都是返回 -1 表示数据读取结束。
使用实例以下:this
InputStream inputstream = new FileInputStream("D://out.txt"); byte[] data = new byte[1024]; int bytesRead = inputstream.read(data); while(bytesRead != -1) { doSomethingWithData(data, bytesRead); bytesRead = inputstream.read(data); } inputstream.close();
首先,此示例建立一个字节数组。
而后它建立一个名为 bytesRead 的 int 变量来保存每次读取 byte [] 调用时读取的字节数,
并当即分配 bytesRead 从第一次读取 byte [] 调用返回的值。
InputStream 类有两个名为 mark() 和 reset() 的方法,InputStream 的子类可能支持也可能不支持:
public boolean markSupported() { return false; }
也是不支持 mark( )和 reset() 方法mark() 在 InputStream 内部设置一个标记,默认值在位置 0 处。
能够手动标记到目前为止已读取数据的流中的点,而后,代码能够继续从 InputStream 中读取数据。
若是想要返回到设置标记的流中的点,在 InputStream 上调用 reset() ,而后 InputStream “倒退”并返回标记,
如此,即可再次从该mark点开始返回(读取)数据。很明显这可能会致使一些数据从 InputStream 返回屡次。我来举个例子:
public static void testMarkAndReset() throws IOException { InputStream input = new ByteArrayInputStream("123456789".getBytes()); System.out.println("第一次打印:"); int count = 0;// 计算是第几回读取,将在第二次读取时作标记; byte[] bytes = new byte[3]; // 每次只读取 3 个字节 int data = input.read(bytes); while (data != -1) { System.out.print(new String(bytes)); if (++count == 2) { // 在第二轮读取,即读到数字 4 的时候,作标记 input.mark(16); // 从 mark 点开始再过 readlimit 个字节,mark 将失效 } data = input.read(bytes); } input.reset(); System.out.println("\n在通过 mark 和 reset 以后从 mark 位置开始打印:"); data = input.read(bytes); while (data != -1) { System.out.print(new String(bytes)); data = input.read(bytes); } }
将会输出:
第一次打印: 123456789 在通过 mark 和 reset 以后从 mark 位置开始打印: 789
另外要说明一下 mark(int readlimit) 参数,readlimit 是告诉系统,过了这个 mark 点以后,给本宫记住日后的 readlimit 个字节,由于到时候 reset 以后,要从 mark 点开始读取的;但实际状况和 jdk 文档有出入,不少状况下调用 mark(int readlimit) 方法后,即便读取超过 readlimit 字节的数据,mark 标记仍有效,这又是为何呢?网上有人解答,但我仍是决定亲自探索一番。
咱们这个实例引用的实际对象是 ByteArrayInputStream
先看一下它的源码:
/* Note: The readAheadLimit for this class has no meaning.*/ public void mark(int readAheadLimit) { mark = pos; }
好家伙,它说这个参数对于这个类没有任何做用。
那咱们在看看其余的 InputStream 子类,经验证,FileInputStream 和一些实现类不支持 mark() 方法,咱们看看 BufferedInputStream
类源码:
我先把一些字段的含义说明一下:
count
索引1大于缓冲区中最后一个有效字节的索引。 该值始终在0到buf.length的范围内; 元素buf [0]到buf [count-1]包含从底层输入流得到的缓冲输入数据。在 read() 方法中读完数据返回 -1 就是由于if (pos >= count) return -1;
pos
指缓冲区中的当前位置。 这是要从 buf 数组中读取的下一个字符的索引。markpos
是调用最后一个 mark() 方法时 pos 字段的值。该值始终在-1到pos的范围内。 若是输入流中没有标记位置,则此字段为-1。BufferedInputStream 是每次读取必定量的数据到 buf 数组中的,设置了 readlimit 确定是想让数组从 mark 索引开始至少记录到 (mark + readlimit) 索引。
public synchronized void mark(int readlimit) { marklimit = readlimit; markpos = pos; } public synchronized void reset() throws IOException { getBufIfOpen(); // Cause exception if closed if (markpos < 0) throw new IOException("Resetting to invalid mark"); pos = markpos; } private void fill() throws IOException { byte[] buffer = getBufIfOpen(); if (markpos < 0) pos = 0; /* 若是标记点不在缓冲数组里(没标记点),丢掉buffer,取新数据 */ else if (pos >= buffer.length) /* 缓冲区中当前位置比buffer数组大,才执行下面代码 */ if (markpos > 0) { /* 能够把 markpos 左边的数据丢掉 */ int sz = pos - markpos; // 须要缓存的字节长度,从 markpos 开始 System.arraycopy(buffer, markpos, buffer, 0, sz); // 复用内存空间 pos = sz; markpos = 0; } else if (buffer.length >= marklimit) { // 若是 buffer 的长度已经大于 marklimit markpos = -1; /* 那 mark 就失效了*/ pos = 0; /* 删除buffer内容,取新数据 */ } else if (buffer.length >= MAX_BUFFER_SIZE) { // 若是buffer过长就抛错 throw new OutOfMemoryError("Required array size too large"); } else { /* buffer 还没 marklimit 大,扩容到 pos 的2倍或者最大值 */ int nsz = (pos <= MAX_BUFFER_SIZE - pos) ? pos * 2 : MAX_BUFFER_SIZE; if (nsz > marklimit) nsz = marklimit; byte nbuf[] = new byte[nsz]; System.arraycopy(buffer, 0, nbuf, 0, pos); if (!bufUpdater.compareAndSet(this, buffer, nbuf)) { throw new IOException("Stream closed"); } buffer = nbuf; } count = pos; int n = getInIfOpen().read(buffer, pos, buffer.length - pos); if (n > 0) count = n + pos; }
能够得出:设置标记后,
Math.min(2倍 pos 或最大值 ,marklimit);
咱们就只分析了 ByteArrayInputStream 和 BufferedInputSteam 类的算法,其它输入流不知道。所以 mark() 方法标记时,务必考虑好 readlimit 的值。
OutputStream 一般始终链接到某个数据目标,如文件,网络链接,管道等。 OutputStream 的目标是将数据写入到外部。
write(byte) 方法用于将单个字节写入 OutputStream。 OutputStream 的 write() 方法接受一个 int ,其中包含要写入的字节的字节值。 只写入 int 值的第一个字节。 其他的被忽略了。
OutputStream 的子类有重写的 write() 方法。 例如,DataOutputStream 容许使用相应的方法writeBoolean(),writeDouble() 等编写诸如 int,long,float,double,boolean 等 Java 基本类型。
和 InputStream 同样,它们也能够将一个数组或一部分字节写入 OutputStream 。
OutputStream 的flush() 方法将写入 OutputStream 的全部数据刷新到底层数据目标。 例如,若是 OutputStream 是 FileOutputStream ,则写入 FileOutputStream 的字节可能还没有彻底写入磁盘。 即便您的代码已将其写入 FileOutputStream ,但数据也可能还在某处缓存在内存中。 经过调用 flush() 能够确保将任何缓冲的数据刷新(写入)到磁盘(或网络,或 OutputStream 的目标)。