Java输入输出流(一)#

 


Android 是基于 Java 语言编写的,在安卓程序的很多时候会用到有关 I/O 操作,要在 Adroid 中使用 I/O 操作就必须学会 JavaI/O 操作。

首先要知道的是,Java 中输入和输出的概念都是对于程序来说的。就是 外部往程序输入,程序向外部输出。外部往程序输入,则程序必须去 取外部,程序向外部输出,则程序必须去 给外部。在外部和程序之间,Java 分别通过 输入流InputStream输出流OutStream 进行联系。

流的示意图如下

Java.io 的结构树##

本人才疏学浅,对于Java I/O也是初学阶段,因此本着与读者一同探讨的心态,按照结构树从上至下逐个去看一下每一个类的使用方法。

字节流与字符流##

从结构树我们可以看到位于树的顶端是字节流和字符流。

  1. 字节流 :表示以字节为单位从 stream 中读取或往 stream 中写入信息,即 io 包中的 inputstream 类和 outputstream 类的派生类。通常用来读取二进制数据,如图象和声音。
  2. 字符流 :以 Unicode 字符为导向的 stream,表示以 Unicode 字符为单位从 stream 中读取或往 stream 中写入信息。 区别: ReaderWriter 要解决的,最主要的问题就是国际化。原先的 I/O 类库只支持8位的字节流,因此不可能很好地处理16位的 Unicode 字符流。Unicode 是国际化的字符集(更何况 Java 内置的 char 就是16位的 Unicode 字符),这样加了 ReaderWriter 之后,所有的 I/O 就都支持 Unicode 了。

InputStream与OutputStream##

对于字节流,它有两个抽象类:InputStreamOutputStream,在这两个抽象类里面没有具体的实现方法,它的具体实现需要它的 子类 来实现,在它里面定义了一些函数用以 子类 的去实现。

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 相关的系统资源

InputStreamOutputStream 两个抽象类定义了输入流和输出流应该具备什么功能,但是具体却没有实现。她们的具体实现是通过继承于它们的子类来实现的

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 类的源码我们可以发现,对于返回不同数据类型的方法,其实现方法大体相同:

  1. 首先调用 read() 方法,read()的方法读取一个字节,然后为高位补零,返回一个 int 类型值
  2. 根据数据类型的所占用的字节数,相应调用几次 read()方法
  3. 然后进行移位操作,最后把几个字节拼接起来,返回对应的数据类型

下面以 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

2.1 FileOutputStream###

2.1.1 BufferedOutputStream###

2.1.2 DataOutputStream###

2.1.3 PrintStream###



posted @ 2020-01-08 17:30  那些年的代码  阅读(825)  评论(0编辑  收藏  举报