Java输入输出流(一)#
Android 是基于 Java 语言编写的,在安卓程序的很多时候会用到有关 I/O 操作,要在 Adroid 中使用 I/O 操作就必须学会 Java 的 I/O 操作。
首先要知道的是,Java 中输入和输出的概念都是对于程序来说的。就是 外部往程序输入,程序向外部输出。外部往程序输入,则程序必须去 读 取外部,程序向外部输出,则程序必须去 写 给外部。在外部和程序之间,Java 分别通过 输入流InputStream 和 输出流OutStream 进行联系。
流的示意图如下
Java.io 的结构树##
本人才疏学浅,对于Java I/O也是初学阶段,因此本着与读者一同探讨的心态,按照结构树从上至下逐个去看一下每一个类的使用方法。
字节流与字符流##
从结构树我们可以看到位于树的顶端是字节流和字符流。
- 字节流 :表示以字节为单位从 stream 中读取或往 stream 中写入信息,即 io 包中的 inputstream 类和 outputstream 类的派生类。通常用来读取二进制数据,如图象和声音。
- 字符流 :以 Unicode 字符为导向的 stream,表示以 Unicode 字符为单位从 stream 中读取或往 stream 中写入信息。 区别: Reader 和 Writer 要解决的,最主要的问题就是国际化。原先的 I/O 类库只支持8位的字节流,因此不可能很好地处理16位的 Unicode 字符流。Unicode 是国际化的字符集(更何况 Java 内置的 char 就是16位的 Unicode 字符),这样加了 Reader 和 Writer 之后,所有的 I/O 就都支持 Unicode 了。
InputStream与OutputStream##
对于字节流,它有两个抽象类:InputStream 和 OutputStream,在这两个抽象类里面没有具体的实现方法,它的具体实现需要它的 子类 来实现,在它里面定义了一些函数用以 子类 的去实现。
1 InputStream###
我们打开 InputStream 源码看到里面定义一些方法:
- public abstract int read() :读取 input stream 的下一个字节,返回值为下一个字节
- public int read(byte b[]) : 读取 input stream ,并将其存在数组 b 中,以b[0]开始存储,最多能读取 b.length 个字节
- public int read(byte b[], int off, int len) :读取 input stream ,读取的长度为 len ,以 b[off] 开始存储,read(byte b[]) 的实现就是调用该方法: read(b,0,b.length)
- public long skip(long n) :跳过 input stream 的 n 个字节
- public int available() :粗略估计 input stream 可以读取的字节,返回估计值,不一定准确。不会阻塞调用 input stream 的下一个方法
- public void close() : 关闭 input stream, 释放与input stream 相关的系统资源
- public synchronized void mark(int readlimit) :标记input stream当前 (mark()被调用的这一刻 )位置,参数 readlimit 用来告诉系统,当之后读取的字节数超过 readlimit 后,mark 失效
- public synchronized void reset() :退回到最后一次 input stream 调用 mark()方法地方,之后读取字节的时候就会从 mark 处开始。当 mark() 调用的时候 input stream 就会记住从这一刻开始读入的所有字节,当 reset() 被调用的时候,就会准备好提供这些字节,而如果读入的字节数超过了 readlimit,reset()还没有被调用,那么 input stream 就不会继续记录,也就是 mark 失效了
- public boolean markSupported() :测试该 IO 类是否支持 mark() 和 reset()方法
2 OutputStream###
同样我们打开 OutputStream 的源码,可以看到里面有这些方法:
- public abstract void write(int b) :往 output stream 写出一个字节,这个字节是 b 的低八位,b 的高24位会被忽略
- public void write(byte b[]) :往 output stream 写出 b.length 长度的字节,通过调用 write( b, 0, b.length) 来实现
- public void write(byte b[], int off, int len) :写出长度为 len 的字节,第一个写出的是 b[off],
- public void flush() :清空当前 output stream以及强制输出所有缓冲数据
- public void close() :关闭 output stream, 释放与input stream 相关的系统资源
InputStream 和 OutputStream 两个抽象类定义了输入流和输出流应该具备什么功能,但是具体却没有实现。她们的具体实现是通过继承于它们的子类来实现的
1.1 FileInputStream###
对文件进行写入的类。对 file 类没有了解的读者可以查阅相关资料,这里给出一个网址以供产考:
https://zhayh.gitbooks.io/java/content/ch11_file_io/1_1_file_class.html
首先看看它的构造方法:
- FileInputStream(File file) :通过传入一个 file 类对象,来创建 file input stream
- FileInputStream(FileDescriptor fdObj) :通过 file descriptor fdObj 来创建一个 file input stream
- FileInputStream(String name) :通过传入一个 file 在系统中的名字(或者路径)来创建 file input stream,实际上他是通过调用 FileInputStream(name != null ? new File(name) : null) 来实现的
再来看看它的方法:
- int available() :粗略估计 input stream 可以读取的字节,返回估计值,不一定准确。不会阻塞调用 input stream 的下一个方法
- void close() :关闭 input stream ,释放与之相关的系统资源
- protected void finalize() :确保 input stream 已经关闭如果没有,则继续调用 close()
- int read() :读取 input stream 的下一个字节。这个方法是Java的原始类,底层是用C/C++来实现的
- int read(byte[] b) :读取 b.length 长度的字节并存到 b 中
- int read(byte[] b, int off, int len) :读取 input stream ,读取的长度为 len ,以 b[off] 开始存储,read(byte b[]) 的实现就是调用该方法: read(b,0,b.length)
- long skip(long n) :跳过 input stream 的 n 个字节
代码举例:
FileInputStream fileInput = null;
byte [] b = new byte[2097152];//用来存放读取输入流的数据
File file = new File("C:/Users/加盐/Desktop/logo.jpg");
try { //try catch 是用来捕获异常
fileInput = new FileInputStream(file);//通过传递file参数来新建file input stream 对象
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
fileInput.read(b); //读取数据,并存到b中
fileInput.close();
} catch (IOException e) {
e.printStackTrace();
}
要注意的是在 windows 和 Linux 系统中,"/"表示分割符,在 windows 中还可以用 "",但是由于java中 ""表示转义符号,因此要用 "\",例如:C:\\Users\\加盐\\Desktop\\logo.jpg
1.1.1 BufferedInputStream###
如果传输的文件比较大,直接利用 FileInputStream 时,是程序直接与硬盘进行访问,我们知道硬盘的存取速度是最慢的,因此这样就会比较耗时。如果我们先把要存储的数据放到内存中,等到内存区满了之后,我们再把内存的数据写到硬盘中,这样速度就会快很多。因为首先计算机对内存的访问速度比访问硬盘要快,其次,由于是每次指定的内存区满了之后,再把数据写到硬盘,这样就减少了对硬盘的访问次数,节省了很多时间。
BufferedInputStream 又称为包装流,因为它能将其他的 InputStream 封装成 BufferedInputStream,这一点从它的构造函数就可以看到:
BufferedInputStream(InputStream in) :创建一个 BufferedInputStream ,并把它的参数 in 保存起来,用以后续的使用
BufferedInputStream(InputStream in, int size) :创建一个BufferedInputStream ,并把它的参数 in 保存起来,用以后续的使用,而且 size 指定了这个缓冲区的大小,也就是输入设备一次往缓冲区写入 size 字节大小的数据。待程序读取完,缓冲区为空后,再次输入设备再向缓冲区写入数据。若没有指定 size ,系统默认为8192。
它的方法:
- int available()
- void close()
- void mark(int readlimit)
- boolean markSupported()
- int read()
- int read(byte[] b, int off, int len)
- void reset()
- long skip(long n)
以上所有的方法与前面所讲的 InputStream 类的同名方法的作用一样的,下面来看例子:
在IO.txt中
现在利用 BufferedInputStream(InputStream in) 将它读入 ,并显示到控制台
public static void main(String[] arg)
FileInputStream fileInput = null;
BufferedInputStream bufferInput = null;
int len = 0;
byte [] b = new byte[1024];
File file = new File("C:/Users/加盐/Desktop/IO.txt");
try {
fileInput = new FileInputStream(file);
bufferInput = new BufferedInputStream(fileInput); //把FileInputStream包装成 BufferInputStream
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
len = bufferInput.read(b);
bufferInput.close();
fileInput.close();
} catch (IOException e) {
e.printStackTrace();
}
for(int i=0;i<len;i++){
System.out.print((char) b[i]+"");
}
}
}
运行结果如上图所示
现在我们来用一下 mark()方法:
public static void main(String[] arg) throws IOException{
FileInputStream fileInput = null;
BufferedInputStream bufferInput = null;
int len = 0;
byte [] b = new byte[1024];
File file = new File("C:/Users/加盐/Desktop/IO.txt");
fileInput = new FileInputStream(file);
bufferInput = new BufferedInputStream(fileInput);
bufferInput.read(b, 0, 3);//先让bufferInput被读取3个字节
bufferInput.mark(10); //然后进行标记
for(int i = 1;i<10;i++){
int a = 3*i;
bufferInput.read(b,a,3); //然后在往后读取三个字节后
bufferInput.reset(); //然后重置,也就是回到bufferInput的被标记的位置
} //下一次读取bufferInput的时候又从标记点开始
//所以我们可以猜想,最后一定会第四个字节和第六个字节重复出现九次
bufferInput.close();
fileInput.close();
for(int i=0;i<b.length;i++){
if(b[i]==0)break;
System.out.print((char) b[i]+"");
}
}
}
运行结果如上图,符合我们的猜想
1.1.2 DataInputStream###
前面我们看到,进入 input stream 的基本单位都是字节,尽管底层确实是以字节作为传输单位,但是有些时候就显得不太方便,,比如说我知道要传输的都是 int 类型的数据,那么我们是否可以直接以 int 类型的数据进行传输单位呢?
DataInputStream 类就解决了这个问题,允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据(请稍微注意这一句话)。
下面来看它的构造函数:
- DataInputStream(InputStream in) :通过传入 InputStream 来构造 DataInputstream
方法:
- int read(byte[] b) :与前面 InputStream 的同名方法一样
- int read(byte[] b, int off, int len) :与前面 InputStream 的同名方法一样
- boolean readBoolean() :读取一个字节,若该字节为非零,则返回 true ,若为零,则返回 false
- byte readByte() :读取一个字节
- char readChar() :读取两个字节并返回一个 char 类型数据
- double readDouble() :读取八个字节并返回一个 double 类型的数据
- float readFloat() :读取四位字节并返回一个 float 类型的数据
- void readFully(byte[] b) :与 int read(byte[] b) 类似,只是没有返回值
- void readFully(byte[] b, int off, int len) :与int read(byte[] b, int off, int len)类似,返回值为零,从源码中可以看到,这个方法有一个机制保证一定能读取到 len 长度的字节数
- int readInt() :读取四个字节并返回一个 int 类型的数据
- long readLong() :读取八个字节,并返回一个 long 类型的数据
- short readShort() :读取两个字节,并返回一个 short 类型的数据
- int readUnsignedByte() :读取一个字节,返回一个无符号类型的字节,实际上就是把读取到的数据转成 int 类型输出
- int readUnsignedShort() :读取梁个字节,返回一个无符号类型的 short 数据,实际上就是把读取到的数据转成 int 类型输出
- String readUTF() :读取用UTF-8编码的字符串
- int skipBytes(int n) :跳过 n 个字节
下面我们来看例子:
public static void main(String[] arg) throws IOException{
FileInputStream fileInput = null;
DataInputStream dataInput = null;
byte [] b = new byte[1024];
File file = new File("C:/Users/加盐/Desktop/IO.txt");
fileInput = new FileInputStream(file);
dataInput = new DataInputStream(fileInput);
System.out.print(dataInput.readBoolean());
dataInput.close();
fileInput.close();
}
}
一个简单的例子,从输入流中读取一个字节,并在控制台输出:输入流在文件中读取一个字符“6”,底层变成相应的字节,然后将文件输入流包装成数据输入流,调用数据输入流读取这个字节,返回一个 Boolean 类型的值
更多的方法不一一展示,读者可自行回去尝试,值得一提的是,。通过查看 DataInputStream 类的源码我们可以发现,对于返回不同数据类型的方法,其实现方法大体相同:
- 首先调用 read() 方法,read()的方法读取一个字节,然后为高位补零,返回一个 int 类型值
- 根据数据类型的所占用的字节数,相应调用几次 read()方法
- 然后进行移位操作,最后把几个字节拼接起来,返回对应的数据类型
下面以 readShort()为例,为大家演示一下,假设底下的字节数为无符号数,并且以原码形式存放(底下并非如此,但是为了方便说明,故如此假设)
代码:
public final short readShort() throws IOException {
int ch1 = in.read();
int ch2 = in.read(); //如果到了文件尾返回-1
if ((ch1 | ch2) < 0) //如果如果没有读够两个字节,则抛出异常
throw new EOFException();
return (short)((ch1 << 8) + (ch2 << 0));//第一个字节(实际上是一个高24为0的
//int)左移动8位成为高8位,节不动,
//然后拼接成为一个新数据,在把它强制
//类型转换变成 short 类型
}
假设输入流有两个字节为:00000001和00000002,那么ch1就为00000000000000000000000000000001,ch2为00000000000000000000000000000001
,ch1左移8位:00000000000000000000000100000000,最后拼接起来就是:00000000000000000000000100000002,强制类型转换后变成0000000100000002