Java输入输出流(一)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,050评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,275评论 18 399
  • 一、流的概念和作用。 流是一种有顺序的,有起点和终点的字节集合,是对数据传输的总成或抽象。即数据在两设备之间的传输...
    布鲁斯不吐丝阅读 9,929评论 2 95
  • 近几天找来李碧华的《青蛇》看。没读多少,看到青蛇和白蛇的对话,有一段是这样写的: 晚上,我俩自湖底出来,吸收青烟紫...
    周YAJING阅读 1,609评论 1 4
  • 菱角分明,好坏不分。岁月静好,不知安分。沐浴在鸡汤的大澡盆中,排泄的是每天的安稳。 今天是大二的最后一天,寝...
    王汽水阅读 177评论 0 0