Java基础教程(25)--I/O流

??I/O流表示输入源或输出目标。流可以表示许多不同类型的源和目标,例如磁盘文件、设备、其他程序等。
??流支持许多不同类型的数据,包括字节、原始数据类型、字符和对象等。有些流只传递数据; 有些流则可以操纵和转换数据。
??无论各种流的内部是如何工作的,所有流都提供相同的简单模型:流是一系列数据。程序使用输入流从源头获取数据,一次一项:

??程序使用输出流将数据写入目的地,一次一项:

??在本文中,我们会看到流可以处理各种各样的数据,无论是基本数据还是复杂对象。先来几张IO流的全家福:
??InputStream家族:

??OutputStream家族:

??Reader家族:

??Writer家族:

??点击此处可查看完整大图。

一.字节流

??一个字节(byte)代表8个二进制位(bit)。字节流,顾名思义,它所传递和操作的最小单位是字节。所有的字节流类都是抽象类InputStream和OutputStream的子类。
??I/O体系中有许多字节流类。为了演示字节流如何工作,我们选择了文件I/O字节流——FileInputStream和FileOutputStream。其他字节流类的使用方式都大致相同,不同之处主要在于它们的构造方式。
??下面的程序使用字节流将src.txt中的文本复制到dest.txt中,每次一个字节:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyBytes {
    public static void main(String[] args) throws IOException {
        FileInputStream in = null;
        FileOutputStream out = null;
        try {
            in = new FileInputStream("src.txt");
            out = new FileOutputStream("dest.txt");
            int c;
            while ((c = in.read()) != -1) {
                out.write(c);
            }
        } finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }
    }
}

??在while循环中,程序每次从输入流in中读取一个字节,然后再将这个字节写入输出流out中,直到输入流中的字节全部被读取完。
??在不再需要流时关闭流是非常重要的。上面的程序中使用了finally块来保证无论是否发生错误都要关闭流。
??虽然上面的程序看起来很正常,但是实际上我们应该避免使用这种低级的,或者说底层的I/O。由于src.txt文件中存储的是字符数据,因此我们应该使用字符流,我们马上会在下一节中见到它。字节流应该仅用于最原始的I/O。那么为什么要谈论字节流呢?因为所有其他流类型都是基于字节流构建的。

二.字符流

??所有的字符流类都是Reader和Writer的子类。为了演示字符流如何工作,和上一节一样,这里我们选择文件I/O字符流——FileReader和FileWriter。
??下面的程序使用字符流将src.txt中的文本复制到dest.txt中,每次一个char(注意,这里不是每次一个字符,因为有些字符无法使用一个char类型来表示,具体可以参考我之前的文章《Java基础教程(5)--变量》中关于char类型的介绍):

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyCharacters {
    public static void main(String[] args) throws IOException {
        FileReader inputStream = null;
        FileWriter outputStream = null;
        try {
            inputStream = new FileReader("src.txt");
            outputStream = new FileWriter("dest.txt");
            int c;
            while ((c = inputStream.read()) != -1) {
                outputStream.write(c);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

??字符流通常是字节流的“包装器”。字符流使用字节流来执行物理I/O,而字符流则负责处理字符和字节之间的转换。FileReader内部使用FileInputStream来读取数据,而FileWriter则使用FileOutputStream来写入数据。
??FileReader和FileWriter是专门用于文件读写的字符流。如果需要从其他的源读取字符,或者需要向其他目标写入字符,可以使用InputStreamReader和OutputStreamWriter来定制自己的流。这两个类只是简单的从输入源读取字符和向输出目标写入字符,我们可以使用它们自定义输入源和输出目标。
??FileReader和FileWriter所处理的最小单位是char类型。实际上,还可以每次处理一行字符。行是指一串字符与末尾的行终止符。现在我们修改上面的程序,来让我们的程序每次处理一行字符。这里会使用到两个新的类——BufferedReader和PrintWriter,我们会在下一节更深入地讨论这些类:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;

public class CopyLines {
    public static void main(String[] args) throws IOException {
        BufferedReader inputStream = null;
        PrintWriter outputStream = null;
        try {
            inputStream = new BufferedReader(new FileReader("src.txt"));
            outputStream = new PrintWriter(new FileWriter("dest.txt"));
            String l;
            while ((l = inputStream.readLine()) != null) {
                outputStream.println(l);
            }
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }
}

三.缓冲流

??到目前为止,我们上面的例子使用的都是无缓冲的I/O。这意味着每次读取或写入请求都由底层操作系统直接处理。这样会使得程序的效率低很多,因为每个请求都会触发磁盘访问、网络请求或其他代价相对较高的操作。
??为了减少这种开销,Java平台实现了使用缓冲的I/O流。缓冲输入流从称为缓冲区的存储区读取数据,仅当缓冲区为空时才会重新获取数据。类似地,缓冲输出流将数据写入缓冲区,并且仅在缓冲区已满时才将数据写入输出目标。
??下面的两个例子将无缓冲的流转换为缓冲流:

inputStream = new BufferedReader(new FileReader("src.txt"));
outputStream = new BufferedWriter(new FileWriter("dest.txt"));

??有四个缓冲流类用于包装无缓冲流:BufferedInputStream与BufferedOutputStream创建缓冲字节流,而 BufferedReader与BufferedWriter创建缓冲字符流。
??有时候我们需要再缓冲区未填充满的时候就将它写出缓冲区,此时就需要刷新缓冲区。一些缓冲输出流支持自动刷新,这个行为由可选的构造方法参数指定。在启用自动刷新时,某些关键事件会导致刷新缓冲区。例如,PrintWriter会在每次调用println和format的时候刷新缓冲区。如果要手动刷新缓冲区,可以调用该输出流的flush方法。

四.格式化

??通过使用具有格式化功能的流可以将数据转换为格式标准、易于阅读的形式。提供格式化功能的流是是字符流类PrintWriter和字节流类PrintStream。
??注意,唯一需要使用PrintStream的地方是System.out和System.err(具体内容见下一小节)。当你需要格式化输出流,应该使用PrintWriter而不是PrintStream。
??正如其他输出流一样,PrintStream和PrintWriter为简单的字节或字符输出提供了一组write方法。除此之外,它们还提供了两种格式化方法来对输出内容进行格式化:

  • print和println——以标准方式格式化单个值。
  • format——基于格式化字符串来对任意数量的值进行格式化。

print和println方法

??print和println用于打印单个变量的值,如果是对象则会打印对该对象调用toString方法后返回的字符串,它们唯一的区别是println会在每次调用后换行,而print则不会。下面是一个使用print和println的例子:

public class Root {
    public static void main(String[] args) {
        int i = 2;
        double r = Math.sqrt(i);

        System.out.print("The square root of ");
        System.out.print(i);
        System.out.print(" is ");
        System.out.print(r);
        System.out.println(".");

        i = 5;
        r = Math.sqrt(i);
        System.out.println("The square root of " + i + " is " + r + ".");
    }
}

??该程序将会输出:

The square root of 2 is 1.4142135623730951.
The square root of 5 is 2.23606797749979.

format方法

??format方法使用指定的格式字符串对多个参数进行格式化。格式字符串是指嵌入格式说明符的字符串。格式字符串支持非常多的格式,本文中只会介绍一些基础知识。有关完整说明请参考官方提供的格式字符串语法
??下面的例子调用了一次format方法,但同时格式化了两个值:

public class Root2 {
    public static void main(String[] args) {
        int i = 2;
        double r = Math.sqrt(i);
        System.out.format("The square root of %d is %f.%n", i, r);
    }
}

??下面是输出:

The square root of 2 is 1.414214.

??上面的例子中,%d、%f和%n是三个格式说明符。所有的格式说明符都以一个%开头,并以一个或两个字符结束。这里使用的三个格式说明符是:

  • %d——将整数值转换为十进制数。
  • %f——将浮点数值转换为十进制数。
  • %n——表示基于当前平台的行结束符。

??还有很多格式说明符,由于篇幅所限,再加上本文的重点是I/O流,因此这里就不再列举其他格式说明符和其他格式化的细节,感兴趣的读者可以自行查阅官方文档。

五.标准流

??标准流是许多操作系统的特性。默认情况下,它们从键盘读取输入并将输出写入显示器。它们还支持文件I/O以及程序之间的I/O,但该功能由命令行解释器控制,而不是程序。
??Java平台支持三种标准流:标准输入,通过System.in访问;标准输出,通过System.out访问;标准错误,通过System.err访问。这些流是自动定义的并且不需要打开。标准输出和标准错误均用于输出。
??你可能觉得标准流失字符流,但由于历史原因,他们实际上是字节流。System.out和System.err都是PrintStream类型。虽然从技术上讲她们是字节流,但是PrintStream内部利用字符流对象来模拟字符流的许多功能。
??相比之下,System.in是一个没有字符流功能的字节流。如果要使用标准输入作为字符流,可以使用InputStreamReader或Scanner对它进行包装。

六.数据流

??文本格式是易于人类阅读的,因此使用起来很方便。但是它并不像以二进制格式传递数据那样高效。下面我们将会学习如何用二进制数据来完成输入和输出。
??DataOutput接口定义了一组用于以二进制格式写各种类型的值的方法。例如,writeInt总是将一个整数写出为4字节的二进制整数,writeDouble总是将一个Double值写出为8字节的二进制浮点数。这样产生的结果并非人类可阅读的,但是对于给定类型的每个值,所需的空间都是相同的,而且将其读回也比解析文本要更快。
??同理,为了读取二进制数据,可以使用在DataInput接口中定义的一组方法。DataInputStream类实现了DataInput接口。为了读入二进制数据,可以将DataInputStream与某个字节流相结合,例如:

DataInputStream in = new DataInputStream(new FileInputStream("a.dat"));

??类似的,如果要写出二进制数据,可以使用实现了DataOutput接口的DataOutputStream类:

DataOutputStream out = new DataOutputStream(new FileOutputStream("a.dat"));

七.对象流

??就像数据流支持基本数据类型的I/O,对象流支持对象的I/O。Java支持一种称为对象序列化的机制,它可以将对象写出到对象输出流中,也可以从对象输入流中将对象读入。这样我们就可以将对象通过文件、网络等环境来传递并随时将其恢复。所有需要在对象输出流中存储或从对象输入流中恢复的类都必须实现Serializable接口,这只是一个标记接口,它没有定义任何方法。
??Java中提供的对象输入流和输出流分别是ObjectInputStream和ObjectOutputStream。通过ObjectOutputStream的writeObject方法可以将一个对象写入到输出流中,通过ObjectInputStream的readObject方法则可以从输出流中读取一个对象。
??对于一些所有域都是基本数据类型的对象来说,对其进行序列化是很简单的。但是对于某些域是引用类型的对象来说,在对这些对象进行序列化时,还要对其引用的对象也进行序列化,并且这些引用的对象内部可能还含有对其他对象的引用。在这种情况下,writeObject方法将会遍历该对象内部所有的引用,并将这些对象写入流。
??下图演示了这种情况。调用writeObject方法将a对象写入流,但该对象包含了对对象b和c的引用,而b包含对d和e的引用。在将a写入输出流时,会同时写入其他四个对象。当读回a时,也会读回其他四个对象,并保留原有的引用关系。

??您可能想知道如果将一个对象向同一个流中写入两次会发生什么。当他们读回时,还会引用同一个对象吗?答案是肯定的。无论写入多少次,流只会包含一个对象的副本。因此,如果明确地将对象写入流两次,那么实际上只写入了两次引用。例如,下面的代码将对象ob写入两次:

Object ob = new Object();
out.writeObject(ob);
out.writeObject(ob);

??每次调用readObject方法都会读回一个ob对象:

Object ob1 = in.readObject();
Object ob2 = in.readObject();
System.out.println(ob1 == ob2);

??上面的程序会输出true,因为ob1和ob2引用了同一个对象。
??但是,如果将一个对象写入两个不同的流,那么读回的将是两个不同的对象。

原文地址:https://www.cnblogs.com/maconn/p/10851198.html

时间: 2024-11-12 19:20:48

Java基础教程(25)--I/O流的相关文章

Java基础教程:JDBC编程

Java基础教程:JDBC编程 快速开始 什么是JDBC JDBC 指 Java 数据库连接,是一种标准Java应用编程接口( JAVA API),用来连接 Java 编程语言和广泛的数据库. JDBC API 库包含下面提到的每个任务,都是与数据库相关的常用用法. 制作到数据库的连接. 创建 SQL 或 MySQL 语句. 执行 SQL 或 MySQL 查询数据库. 查看和修改所产生的记录. 从根本上来说,JDBC 是一种规范,它提供了一套完整的接口,允许便携式访问到底层数据库,因此可以用 J

Java基础教程 - 组合

1. 什么是组合? 如果一个类的对象和另一个类满足"has-a"关系的话,我们就可以在一个类中,把另一个类的对象作为其对象成员. 什么是"has-a"关系,举个例子:现在有一个类LapTop.class,还有一个是Moniter.class.好显然,Laptop "has-a" Moniter,也就是说,他们是满足"has-a"关系的.这时候,我们就可以把Moniter作为Laptop的一个数据成员. class Laptop

Java基础教程:面向对象编程

Java基础教程:面向对象编程 Java语言概述 Java语言特点 1.Java为纯面向对象的语言,它能够直接反映现实生活中的对象.总之,Everything is object! 2.平台无关性.Java为解释型语言,编译器会把Java代码变成"""中间代码",然后在JVM上解释执行. 3.Java提供了很多内置的类库,这些类库简化了开发人员的程序设计工作,同时缩短了项目开发时间. 4.Java语言提供了对Web应用的支持. 5.Java语言提供了较好的安全性和健

Java基础教程:面向对象编程[2]

Java基础教程:面向对象编程[2] 三大特性 封装 封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部份包装.隐藏起来的方法.封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问. 使用封装我们可以对成员变量进行更精确的控制,同时隐藏信息,实现细节等. 方法: public class Person{ private String name; private int age; ? public int getAge(){ return age;

Java基础教程:HashTable与HashMap比较

Java基础教程:HashTable与HashMap比较 1.  关于HashMap的一些说法: a)  HashMap实际上是一个"链表散列"的数据结构,即数组和链表的结合体.HashMap的底层结构是一个数组,数组中的每一项是一条链表. b)  HashMap的实例有俩个参数影响其性能: "初始容量" 和 装填因子. c)  HashMap实现不同步,线程不安全.  HashTable线程安全 d)  HashMap中的key-value都是存储在Entry中的

Java基础教程:多线程基础(2)——线程间的通信

Java基础教程:多线程基础(2)--线程间的通信 使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对各线程任务在处理的过程中进行有效的把控与监督. 线程间的通信 思维导图 等待中 等待/通知机制 不使用等待/通知机制 我们可以使用使用sleep()与 whle(true) 死循环来实现多个线程间的通信. 虽然两个线程实现了通信,但是线程B必须不断的通过while语句轮训机制来检测某一个条件,这样会浪费CPU资源. 如果轮询间隔较小,更浪费时间间隔.如果轮训

Java基础教程:枚举类型

Java基础教程:枚举类型 枚举类型 枚举是将一具有类似特性的值归纳在一起的方法.比如,我们可以将周一到周日设计为一个枚举类型.彩虹的七种颜色设计为一个枚举类型. 常量实现枚举 我们通过定义常量的方式来实现,如下: Public static class RainbowColor { // 红橙黄绿青蓝紫七种颜色的常量定义 public static final int RED = 0; public static final int ORANGE = 1; public static fina

Java基础教程:内部类

Java基础教程:内部类 内部类 内部类,是指在一个类的内部定义的类.就像下面这样: public class EnclosingClass {   . . .   public class NestedClass {   . . .     } } 内部类拥有访问外部类所有元素(包括private.static)的访问权.当某个外部类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用.然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员. 内部类

Java 基础(四)| IO 流之使用文件流的正确姿势

为跳槽面试做准备,今天开始进入 Java 基础的复习.希望基础不好的同学看完这篇文章,能掌握泛型,而基础好的同学权当复习,希望看完这篇文章能够起一点你的青涩记忆. 一.什么是 IO 流? 想象一个场景:我们在电脑上编辑文件,可以保存到硬盘上,也可以拷贝到 U 盘中.那这个看似简单的过程,背后其实是数据的传输. 数据的传输,也就是数据的流动.既然是流动也就会有方向,有入方向和出方向.举个上传文件的栗子,现在有三个对象,文件.应用程序.上传的目标地址(服务器).简化的上传文件有两步: 应用程序读文件