• 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###



  • 相关阅读:
    Spark ListenerBus 和 MetricsSystem 体系分析
    Scala使用JUnit4单元测试
    Error:java: Compilation failed: internal java compiler error
    Spark 2.2 DataFrame的一些算子操作
    Spark SQL中UDF和UDAF
    Scala基础
    Spark Sql的UDF和UDAF函数
    Kafka笔记整理(二):Kafka Java API使用
    Kafka笔记整理(一)
    【口语英语】小学英语1-6年级听力必备知识汇总,建议收藏!
  • 原文地址:https://www.cnblogs.com/zhuyeshen/p/12167858.html
Copyright © 2020-2023  润新知