目录
FileInputStream和FileOutputStream
BufferedInputStream和BufferedOutputStream
ByteArrayInputStream和ByteArrayOutputStream
DataInputStream和DatOutputStream
ObjectInputStream和ObjectOutputStream
字符缓冲流BufferedReader和BufferedWriter
转换流InputStreamReader和OutputStreamWriter
在java程序中,对于数据的输入/输出操作以“流”的方式进行。
一、分类
Java.io包中定义了多个流类型(类或抽象类)来实现输入/输出功能。
从数据流的方向分类:输入流、输出流
按处理数据单位分类:字节流、字符流
按功能分类:节点流、处理流
Jdk所提供的所有流类型都分别继承自以下四种抽象流类型:
| 字节流 | 字符流 |
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
二、字节流InputStream和OutputStream
- 在计算机中,无论是文本、图片、音频还是视频,所有的文件都是以二进制(字节)形式存在,IO流中针对字节的输入输出提供了一系列的流,统称为字节流
- 字节输入/输出流:以8位的字节为基本处理单位,本身是抽象基类,依靠其子类实现各种功能。所有的字节输入流继承自InputStream,所有的字节输出流都继承自OutputStream。
InputStream常用方法:
abstract int read() | 从输入流中读取一个8位的字节,把它转换为0~255之间的int值并返回。 | 如果因为已经到达流末尾而没有可用的字节,则返回-1。 在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。 抛出IOException - 如果发生 I/O 错误。 |
int read(byte[] b) | 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中,以int形式返回实际读取的字节数。 | |
int read(byte[] b, int off, int len) | 将输入流中最多 len 个字节读入字节数组b中。以整数形式返回实际读取的字节数。将读取的第一个字节存储在元素 b[off] 中,下一个存储在 b[off+1] 中,依次类推。 | |
void close() | 关闭此输入流并释放与该流关联的所有系统资源。 | 抛出IOException |
outputStream常用方法:
abstract void write(int b) | 将指定的字节写入此输出流。 | 抛出IOException - 如果发生 I/O 错误。 |
void write(byte[] b) | 将 b.length 个字节从指定的 byte 数组写入此输出流。 | |
void write(byte[] b, int off, int len) | 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 | |
void close() | 关闭此输出流并释放与此流有关的所有系统资源。 | |
void flush() | 刷新此输出流并强制写出所有缓冲的输出字节。 |
字节流的相关类(灰色:节点流;白色:处理流)
FileInputStream和FileOutputStream
- 针对文件的读写,JDK专门提供了两个类,分别是FileInputStream和FileOutputStream。其中,FileInputStream是InputStream的子类,它是操作文件的字节输入流,专门用于读取文件中的数据
- 如果是通过FileOutputStream向一个已经存在的文件中写入数据,那么该文件中的数据首先会被清空,再写入新的数据。
- 若希望在存在的文件内容之后追加新内容,则可使用FileOutputStream的构造函数FileOutputStream(String fileName, boolean append)来创建文件输出流对象,并把append 参数的值设置为true
例如,通过FileInputStream获取文件中的数据,FileOutputStream将数据写入文件来实现文件的拷贝。
import java.io.*;
public class CopyFile {
public static void main(String[] args) {
// TODO Auto-generated method stub
int tmp = 0;
long time = 0;
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream("D:\\测试20171218.png");
out = new FileOutputStream("D:\\Desktop\\test.png");
} catch (FileNotFoundException e) {
System.out.println("文件找不到:" + e.getMessage());
}
try {
long begTime = System.currentTimeMillis();
while ((tmp = in.read()) != -1) {
out.write(tmp);
}
time = System.currentTimeMillis() - begTime;
in.close();
out.close();
} catch (IOException e) {
System.out.println("文件拷贝失败:" + e.getMessage());
}
System.out.println("文件拷贝成功!用时" + time + "ms");
}
}
运行结果:
BufferedInputStream和BufferedOutputStream
- 虽然上面的程序实现了文件的拷贝,但是一个字节一个字节的读写,需要频繁的操作文件,读写硬盘,效率非常低。所以当通过流的方式拷贝文件时,为了提高效率可以定义一个字节数组作为缓冲区。在拷贝文件时,可以一次性读取多个字节的数据,并保存在字节数组中,然后将字节数组中的数据一次性写入文件。而BufferedInputStream和BufferedOutputStream这两个个缓冲流内部都定义了一个大小为8192的字节数组,当调用read()或者write()方法读写数据时,首先将读写的数据存入定义好的字节数组,然后将字节数组的数据一次性读写到文件中,这种方式对数据进行了缓冲,从而有效的提高数据的读写效率。
- 在IO包中提供两个带缓冲的字节流,分别是BufferedInputStream和BufferdOutputStream,这两个流都使用了装饰设计模式。它们的构造方法中分别接收InputStream和OutputStream类型的参数作为被包装对象,在读写数据时提供缓冲功能。应用程序、缓冲流和底层字节流之间的关系如下图所示
上面的例子采用缓冲流可以修改为:
…
BufferedInputStream in = null;
BufferedOutputStream out = null;
try {
in = new BufferedInputStream(new FileInputStream("D:\\测试20171218.png"));
out = new BufferedOutputStream(new FileOutputStream("D:\\Desktop\\test.png"));
} …
运行结果:
可见,使用字节缓冲流在效率上有了很大的提升。
ByteArrayInputStream和ByteArrayOutputStream
- ByteArrayInputStream是从缓冲区中读取数据,构造方法均是有参的
构造方法摘要 | |
ByteArrayInputStream(byte[] buf) | |
ByteArrayInputStream(byte[] buf, int offset, int length) |
- ByteArrayOutputStream类会在创建对象时就创建一个byte型数组的缓冲区,当向数组中写数据时,该对象会把所有的数据先写入缓冲区,最后一次性写入文件
ByteArrayInputStream |
| 返回可从此输入流读取(或跳过)的剩余字节数。 |
ByteArrayOutputStream |
| 返回缓冲区的当前大小。 |
| 创建一个新分配的 byte 数组。其大小是此输出流的当前大小,并且缓冲区的有效内容已复制到该数组中。 返回:以 byte 数组的形式返回此输出流的当前内容。 | |
| 使用平台默认的字符集,通过解码字节将缓冲区内容转换为字符串。 |
例,使用字节数组输入/输出流将一个文本文档中的内容复制到另一个中去
import java.io.*;
public class ByteArrStream {
public static void main(String[] args) {
// TODO Auto-generated method stub
int b = 0;
FileOutputStream out = null;
FileInputStream in = null;
//创建一个字节数组缓冲区
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
in = new FileInputStream("D:\\test.txt");
out = new FileOutputStream("D:\\target.txt");
while ((b = in.read()) != -1) {
baos.write(b);//将读取到的字节全部写入到该对象的缓冲区中
}
in.close();
baos.close();
out.write(baos.toByteArray());//将缓冲区的数据一次性写入文件
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
DataInputStream和DatOutputStream
- DataInputStream和DatOutputStream是两个与平台无关的数据操作流。是DataInput接口的实现类(DataInput 接口用于从二进制流中读取字节,并根据所有 Java 基本类型数据进行重构。同时还提供根据 UTF-8 修改版格式的数据重构 String 的工具。),它们属于处理流,需要分别“套接”在InputStream和OutputStream类型的节点流上。
- 它们不仅提供读写各种基本类型数据的方法,而且还提供了readUTF()和writeUTF()方法
| 读取一个输入字节,如果该字节不是零,则返回 true,如果是零,则返回 false。 |
| 读取并返回一个输入字节。 |
| 读取两个输入字节并返回一个 char 值。 |
| 读取八个输入字节并返回一个 double 值。 |
| 读取四个输入字节并返回一个 float 值。 |
| 从输入流中读取一些字节,并将它们存储在缓冲区数组 b 中。 |
| 从输入流中读取 |
| 读取四个输入字节并返回一个 |
| 读取八个输入字节并返回一个 |
| 读取两个输入字节并返回一个 |
| 读入一个已使用 UTF-8 修改版格式编码的字符串。 |
例
import java.io.*;
public class DataStream {
public static void main(String[] args) {
// TODO Auto-generated method stub
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(out);
try {
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("测试");
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
DataInputStream dis = new DataInputStream(in);
System.out.println(dis.available());// 输出可从此输入流读取的剩余字节数
System.out.println(dis.readDouble());
System.out.println(dis.readBoolean());
System.out.println(dis.readUTF());
System.out.println("测试".getBytes("UTF-8").length);// 输出“测试”二字在UTF-8编码中占用的字节数
dis.close();
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
需要注意的是,读取数据的顺序需与写入数据的顺序保持一致,先写入的先读取(先进先出)。
例子中,“测试”以UTF-8的格式存储占用6个字节(UTF-8编码中的汉字可能占3个或四个字节),8+1+6=15≠17,这是因为在writeUTF()时该方法会通过 writeShort 方法将两个字节写入输出流,表示实际写出的字节数。之后在 readUTF()会首先读取两个字节,并使用它们构造一个无符号 16 位整数,该整数值被称为 UTF 长度,它指定要读取的额外字节数。然后成组地将这些字节转换为字符。每组的长度根据该组第一个字节的值计算。紧跟在某个组后面的字节(如果有)是下一组的第一个字节。所以,17=8+1+(2+6)。
打印流PrintStream
- PrintStream类被称作打印流,属于处理流,它提供了一系列用于打印数据的print()和println()方法,可以将基本数据类型的数据或引用数据类型的对象格式化成字符串后再输出。与其他输出流不同,PrintStream 永远不会抛出 IOException;异常情况仅可通过 checkError 方法检测错误状态获取错误信息。
import java.io.*;
/**
* @Title TestPrintStream.java
* @Description TODO 将运行时输入的main方法参数文件名对应的文件打印在控制台上
* @Author 15643
* @Time 2018年8月11日 下午3:47:42
*/
public class TestPrintStream {
public static void main(String[] args) {
String fileName = args[0];
if (fileName != null) {
play(fileName, System.out);// out关键字是PrintStream类型
}
}
public static void play(String f, PrintStream p) {
String b = null;
try {
BufferedReader bfis = new BufferedReader(new FileReader(f));
while ((b = bfis.readLine()) != null) {
p.println(b);
}
bfis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ObjectInputStream和ObjectOutputStream
- 如果希望永久将对象转为字节数据写入到硬盘上,即对象序列化,可以使用ObjectOutputStream(对象输出流)来实现。
- 对象被序列化后会生成二进制数据保存在文件中,通过这些二进制数据可以恢复序列化之前的Java对象,此过程称为反序列化,JDK提供了ObjectInputStream类(对象输入流)可以实现对象的反序列化
- 当对象进行序列化时,必须保证该对象实现Serializable接口,否则程序会出现NotSerializableException异常
例,将类Stu的对象以二进制形式保存到文本文档中,再从该文档中读取对象内容import java.io.*; public class ObjectStream { public static void main(String[] args) { ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("D:\\test.txt")); Stu sw = new Stu(); oos.writeObject(sw); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.txt")); Stu sr = (Stu) ois.readObject(); System.out.println(sr.name+sr.sex+sr.age); ois.close(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class Stu implements Serializable { String name = "张三"; String sex = "男"; int age = 0; }
标准输入输出流
- 在System类中定义了三个常量:in、out和err,它们被习惯性地称为标准输入输出流。
- in为InputStream类型,它是标准输入流,默认情况下用于读取键盘输入的数据。
- out为PrintStream类型,它是标准输出流,默认将数据输出到命令行窗口。
- err也是PrintStream类型,它是标准错误流,它和out一样也是将数据输出到控制台,它输出的是应用程序运行时的错误信息
三、字符流Reader和Writer
- 字符输入/输出流:以16位的Unicode码表示的字符为基本处理单位。本身是抽象基类。依靠其子类实现各种功能。
- 字符数据:字符串、文本文件。
-
字符流的相关类(灰色:节点流;白色:处理流)
字符流操作文件FileReader和FileWriter
在程序开发中,经常需要对文本文件的内容进行读取,如果想从文件中直接读取字符便可以使用字符输入流FileReader,通过此流可以从关联的文件中读取一个或一组字符。若需要向文件中写入字符,可以使用Writer的一个子类FileWriter。
字符缓冲流BufferedReader和BufferedWriter
字符流提供了带缓冲区的包装流,分别是BufferedReader和BufferedWriter,其中BufferedReader用于对字符输入流进行包装,BufferedWriter用于对字符输出流进行包装。
特有方法:
BufferedReader
String
()
逐个读取字符,当读到回车"\r" 或 换行"\n" 时会将读到的字符作为一行的内容返回。返回包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null
抛出: IOException - 如果发生 I/O 错误
BufferedWriter
void
newLine()
写入一个行分隔符。行分隔符字符串由系统属性
line.separator 定义,并且不一定是单个新行 ('\n') 符。
例:使用字符缓冲流将Unicode编码在20000以内的字符写入文本文档
import java.io.*; public class BufferedReaderWriter { public static void main(String[] args) { // TODO Auto-generated method stub BufferedWriter out = null; BufferedReader in = null; String b = null; try { out = new BufferedWriter(new FileWriter("D:\\test.txt")); in = new BufferedReader(new FileReader("D:\\test.txt")); for (int i = 0; i <= 20000; i++) {//写入Unicode编码在0-20000之间的字符 out.write(i); if (i % 100 == 0) out.newLine(); } while ((b = in.readLine()) != null) { System.out.println(b); } in.close(); out.close(); } catch (IOException e) { System.out.println("文件写入失败!"); e.printStackTrace(); } } }
跟踪行号的缓冲字符输入流LineNumberReaderJDK中提供了一个可以跟踪行号的缓冲字符输入流——LineNumberReader,此类定义了方法
setLineNumber(int)
和getLineNumber()
,它们可分别用于设置和获取当前行号。
转换流InputStreamReader和OutputStreamWriter - 转换流也是一种包装流,其中OutputStreamWriter是Writer的子类,可以将一个字节流输出流包装成字符输出流,方便直接写入字符,而InputStreamReader是Reader的子类,它可以将一个字节输入流包装成字符输入流,方便直接读取字符。
例,从控制台读取一行字母,并将其转换成大写字母输出文本文档
import java.io.*; public class TransformStream { public static void main(String[] args) { // TODO Auto-generated method stub String b = null; BufferedWriter out = null; System.out.println("请输入英文字母或输入exit以退出程序"); InputStreamReader isr = new InputStreamReader(System.in); BufferedReader in = new BufferedReader(isr); try { out = new BufferedWriter(new FileWriter("D:\\test.txt")); while((b = in.readLine()) != null && !b.equalsIgnoreCase("exit")) {//输入不区分大小写的exit即退出程序 out.write(b.toUpperCase());//将英文字母转换成大写并输出 out.newLine(); } out.close(); in.close(); }catch(IOException e) { System.out.println("程序出错!"); e.printStackTrace(); } } }
运行结果: