深入理解JAVA I/O系列二:字节流详解

流的概念

  JAVA程序通过流来完成输入/输出。流是生产或消费信息的抽象,流通过JAVA的输入输出与物理设备链接,尽管与它们链接的物理设备不尽相同,所有流的行为具有相同的方式。这样就意味一个输入流能够抽象多种不同类型的输入:从磁盘文件、从键盘或从网络套接字;同样,一个输出流可以输出到控制台、磁盘文件或相连的网络。

  在我们平时接触的输入/输出流中,有这样一个概念必须要弄明白,何谓输入、何谓输出?讨论这个问题的前提是要知道以什么为参考物,这个参考物就是程序或者内存。输入:就是从磁盘文件或者网络等外部的数据流向程序或者内存。输出就是相反的过程。其中这个“外部”可以是很多种介质:

1、本地磁盘文件、远程磁盘文件。

2、数据库链接

3、TCP、UDP、HTTP网络通信

4、进程间通信

5、硬件设备(键盘、串口)

流的分类

1、从功能上:输入流、输出流

2、从结构上:字节流、字符流

3、从来源上:节点流、过滤流

  其中InputStream/OutputStream是为字节流而设计的,Reader/Writer是为字符流而设计的。处理字节或者二进制对象使用字节流,处理字符或者字符串使用字符流。

在最底层,所有的输入/输出都是字节形式的,基于字符的流只在处理字符的时候提供方便有效的方法。

  节点流是从特定的地方读写的流,例如磁盘或者内存空间,也就是这种流是直接对接目标的。

  过滤流是使用节点流作为输入输出的,就是在节点流的基础上进行包装,新增一些特定的功能。

InputStream

其中有底色标注的为节点流,无底色标注的为过滤流,其中FilterInputStream在JDK中的定义为:包含其他一些输入流,它将这些流用作其基本数据源,可以直接传输数据或提供一些额外的功能,这个类本身并不经常被我们使用,常用的是它的子类。

定义了字节输入模式的抽象类,该类提供了三个重载的read方法:

我们可以看到,三个read方法中,其中有一个是抽象的。那在这里思考这样一个问题:为什么只有第一个是抽象的, 其他两个是具体的?

因为后面两个方法内部最终会去调用第一个方法,所以在InputStream派生类中只需要重写第一个方法就可以了。在这里可以看到第一个read方法是与具体的I/O设备相关的,需要子类去实现。

读写数据的逻辑步骤为:

open a stream

while more information

read/write information

close the stream

一、FileInputStream

 1 public static void main(String[] args) throws IOException
 2     {
 3         InputStream  is = new FileInputStream("c:/test.txt");
 4         int length = 0;
 5         byte[] buffer = new byte[20];
 6         while(-1 != (length = is.read(buffer,0,20)))
 7         {
 8             String str = new String(buffer,0,length);
 9             System.out.print(str);
10         }
11         is.close();
12     }

执行结果:

中国移动基地咪咕阅读
中国移动基地咪咕音乐
a

1、首先我们看下读取文件test的内容:

2、在这里我们使用的是输入流,读取的是我本机C盘中名为test文件中的内容。

3、每次读取的内容存放在buffer这个字节数组中,然后转换成String字符串打印在控制台中。

4、我们来看下第6行代码:因为一个文件内容有多少我们事先并不知道,所以在这里只能分批次读取,每次最多读取20个字节(即buffer数组定义的长度)。

5、length的作用是表示在最后一次读取的时候,读取的长度小于等于buffer数组的长度,while循环体执行结束后,下一次再来读取已经没有内容了,read方法在这个时候会返    回-1,然后跳出循环。

6、对于流的操作,最后一步永远是调用close方法,释放资源。

总结:文本中内容故意定义为41个字节长度,然后打印的时候用了换行。从执行结果可以看到,前两次读取的长度都是20个字节,第三次是1个字节,最后一次没有内容返回-1,然后就跳出循环体了。这个DEMO虽然很简单,但是也是IO种最基本最需要掌握的一部分。

二、FileOutputStream

这个类与FileInputStream是成对的,用法也类似:

1 public static void main(String[] args) throws IOException
2     {
3         OutputStream os = new FileOutputStream("c:/test1.txt");
4         String str = "中国移动手机阅读";
5         byte[] b = str.getBytes();
6         os.write(b);
7         os.close();
8     }

执行结果:

1、这个DEMO主要是将字符串写入磁盘文件中。

2、在第3行的构造函数处要注意下,这个方法中如果指定的文件不存在,则会创建一个新的;如果指定的文件存在,在后面的写入操作会覆盖原有的内容。

这个大家会有这样一个疑问,如果我不想覆盖原有的内容,只想在后面追加内容呢?

1 public static void main(String[] args) throws IOException
2     {
3         OutputStream os = new FileOutputStream("c:/test.txt",true);
4         String str = "注册用户6亿";
5         byte[] b = str.getBytes();
6         os.write(b);
7         os.close();
8     }

执行结果:

1、从执行结果看,这个是在原有内容后面进行追加;程序唯一的区别就在于第三行的构造函数。

OutputStream os = new FileOutputStream("c:/test.txt",true);

如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。

三、BufferedOutputStream

在前面介绍的FileOutputStream,是在程序里面读取一个字节,就往外写一个字节。在这种情况下,频繁的跟IO设备打交道,I/O的处理速度跟CPU的速度根本就不在一个量级上(I/O是一种物理设备),在信息量很多的时候,就比较消耗性能。基于这种问题,JAVA提供了缓冲字节流,通过这种流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。

1 public static void main(String[] args) throws IOException
2     {
3         OutputStream os = new FileOutputStream("c:/test.txt");
4         OutputStream bs = new BufferedOutputStream(os);
5         byte[] buffer = "中国移动阅读基地".getBytes();
6         bs.write(buffer);
7         bs.close();
8         os.close();
9     }

执行结果:

缓冲输出流在输出的时候,不是直接一个字节一个字节的操作,而是先写入内存的缓冲区内。直到缓冲区满了或者我们调用close方法或flush方法,该缓冲区的内容才会写入目标。才会从内存中转移到磁盘上。下面来看看不调用close方法会出现什么情况:

1 public static void main(String[] args) throws IOException
2     {
3         OutputStream os = new FileOutputStream("c:/test1.txt");
4         OutputStream bs = new BufferedOutputStream(os);
5         byte[] buffer = "中国移动阅读基地".getBytes();
6         bs.write(buffer);
7 //        bs.close();
8 //        os.close();
9     }

执行结果:

在这里没有调用close方法,相当于内容还在内存的缓冲区中。BufferedOutputStream本身没有close方法,调用的是父类FilterOutputStream的close方法:

1  public void close() throws IOException {
2     try {
3       flush();
4     } catch (IOException ignored) {
5     }
6     out.close();
7     }

在这里可以看到这个方法的本质是在在关闭资源之前,调用的flush方法。

而flush在JDK中的定义为:刷新此缓冲的输出流。这迫使所有缓冲的输出字节被写出到底层输出流中

四、DataOutputStream

数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。

 1     public static void main(String[] args) throws IOException
 2     {
 3         DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(
 4                 new FileOutputStream("c:/data.txt")));
 5         byte b = 4;
 6         char c = ‘c‘;
 7         int i = 12;
 8         float f = 3.3f;
 9         dos.writeByte(b);
10         dos.writeChar(c);
11         dos.writeInt(i);
12         dos.writeFloat(f);
13         dos.close();

执行结果:

打开之后,里面是乱码,程序写入之后是一个二进制文件。我们的程序中是将java的基本数据类型写入文本,注意这里不是字符串,而是基本数据类型。我们这样写入是没有意义的,下面我们用同样的方式去读取。

 1         DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(
 2                 new FileOutputStream("c:/data.txt")));
 3         byte b = 4;
 4         char c = ‘c‘;
 5         int i = 12;
 6         float f = 3.3f;
 7         dos.writeByte(b);
 8         dos.writeChar(c);
 9         dos.writeInt(i);
10         dos.writeFloat(f);
11         dos.close();
12
13         DataInputStream dis = new DataInputStream(new BufferedInputStream(
14                 new FileInputStream("c:/data.txt")));
15         System.out.println(dis.readByte());
16         System.out.println(dis.readChar());
17         System.out.println(dis.readInt());
18         System.out.println(dis.readFloat());
19         dis.close();
20     

执行结果:

4
c
12
3.3

这里看到,我们的数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本JAVA数据类型。

这里特别注意的地方是:读取数据类型的顺序与当初写入的数据类型的顺序要一致,否则会出现乱码或者读取的信息不准确。

这个原因我们可以这样来理解:写入的时候是不同类型的基本数据,不同的基本数据类型的字节长度不一样,如果读取时候顺序不一致,会导致字节的组合混乱,导致乱码或者走义。

时间: 2024-08-02 02:50:25

深入理解JAVA I/O系列二:字节流详解的相关文章

Vue学习系列(二)——组件详解

前言 在上一篇初识Vue核心中,我们已经熟悉了vue的两大核心,理解了Vue的构建方式,通过基本的指令控制DOM,实现提高应用开发效率和可维护性.而这一篇呢,将对Vue视图组件的核心概念进行详细说明. 什么是组件呢? 组件可以扩展HTML元素,封装可重用的HTML代码,我们可以将组件看作自定义的HTML元素. 为什么要用到组件呢? 为了可重用性高,减少重复性开发,让我们可以使用独立可复用的小组件来构建大型应用. 基本 一.组件注册 1.通过Vue.extend()创建,然后由component来

Java并发编程系列之Semaphore详解

简单介绍 我们以饭店为例,假设饭店只有三个座位,一开始三个座位都是空的.这时如果同时来了三个客人,服务员人允许他们进去用餐,然后对外说暂无座位.后来的客人必须在门口等待,直到有客人离开.这时,如果有一个客人离开,服务员告诉客人,可以进来用餐,如果又有客人离开,则又可以进来客人用餐,如此往复.在这个饭店中,座位是公共资源,每个人好比一个线程,服务员起的就是信号量的作用.信号量是一个非负整数,表示了当前公共资源的可用数目(在上面的例子中可以用空闲的座位类比信号量),当一个线程要使用公共资源时(在上面

( 转)深入理解java内存模型系列

深入理解Java内存模型(一)——基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递. 在共享内存的并发模型里,线程之间共享程序的公共状态,线程之间通过写-读内存中的公共状态来隐式进行通信.在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信. 同步是指程序用于控制不同线程之

深入理解java内存模型系列文章

转自:http://ifeve.com/java-memory-model-0/ 提纲 java线程之间的通信对程序员完全透明,内存可见性问题很容易困扰java程序员,本文试图揭开java内存模型神秘的面纱.本文大致分三部分:重排序与顺序一致性:三个同步原语(lock,volatile,final)的内存语义,重排序规则及在处理器中的实现:java内存模型的设计目标,及其与处理器内存模型和顺序一致性内存模型的关系. 原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 

Android自定义控件系列八:详解onMeasure()(二)--利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题

上一篇文章详细讲解了一下onMeasure/measure方法在Android自定义控件时的原理和作用,参看博文:Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一),今天就来真正实践一下,让这两个方法大显神威来帮我们搞定图片的屏幕适配问题. 请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45038329,非允许请勿用于商业或盈利用途,违者必究. 使用ImageView会遇到

JAVA定时任务调度之Timer入门详解(二)

在上篇的JAVA定时任务调度之Timer入门详解(一)文章中,主要介绍了下Timer,接下来我们一起来看看Timer的一些常用方法. 1.schedule()的4种用法. 第一种:schedule(TimerTask task, Date time); task:安排的任务,time:具体执行时间.这个函数表达的意义是:在时间等于或超过time的时候执行且执行一次task.测试内容如下 MyTimerTask.java的代码跟第一篇一样,MyTimer.java的部分代码截图如下: 运行后,控制

Android组件系列----Activity组件详解

[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3924567.html 联系方式:[email protected] [正文] 注:四大组件指的是应用组件:Activity.Service.BroadcastReceiver.ContentProvider:之前的控件指的是UI组件. 博文目录: 一.Activity简介 二.Activity的状

Java并发编程之---Lock框架详解

Java 并发开发:Lock 框架详解 摘要: 我们已经知道,synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等.Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题.本文以synchronized与Lock的对比为切入点,对Java中的Lock框架的枝干部分进行了详细介绍,最后给出了锁的一些相关概念. 一

java提高篇(九)-----详解匿名内部类

摘自http://blog.csdn.net/chenssy/article/details/13170015 java提高篇(九)-----详解匿名内部类 在Java提高篇-----详解内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节问题,所以就衍生出这篇博客.在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意的事项.如何初始化匿名内部类.匿名内部类使用的形参为何要为final. 一.使用匿名内部类内部类 匿名内部类由于没有名字,所以它的创建方式有点儿奇怪.创建格式