IO流
1 字符输入流
字符流(Java IO的Reader和Writer)功能与InputStream和OutputStream非常类似,InputStream和OutputStream基于字节处理,而字符流(Reader和Writer)是基于字符处理。主要用于读写文本。
1.1 Reader类的常用方法
Reader类是Java IO中所有Reader的基类。子类包括FileReader,BufferedReader,InputStreamReader,StringReader和其他Reader。
- read() ; 读取字符输入流。读取字符输入流的下一个字符,返回一个字符,意味着这个返回值的范围在0到65535之间(当达到流末尾时,同样返回-1)。这并不意味着Reader只会从数据源中一次读取2个字节,Reader会根据文本的编码,一次读取一个或者多个字节。
- read(char cbuf[]);读取字符输入流。读取多个字符,存入字符数组cbuf,返回实际读入的字符数。
- read(char cbuf[], int off, int len); 方法,读取字符输入流。每次读取len个字符,存入字符数组cbuf,从off下标开始存储。
- close(); 关闭当前流,释放与该流相关的资源,防止资源泄露。在带资源的try语句中将被自动调用。关闭流之后还试图读取字符,会出现IOException异常。
1.2 Reader类的子类:FileReader
FileReader类从InputStreamReader类继承而来(间接继承了Reader)。该类按字符读取流中数据。
1.3 FileReader构造方法和常用方法
1.3.1 构造方法
FileReader(File file);通过打开一个到实际文件的连接来创建一个
FileReader
,该文件通过文件系统中的File
对象file
指定。FileReader(String fileName) ; 通过打开一个到实际文件的连接来创建一个
FileReader
,该文件通过文件系统中的路径名name
指定。(了解)FileReader(FileDescriptor fd) ; 在给定从中读取数据的
FileDescriptor
的情况下创建一个新 FileReader。提示:FileDescriptor 是“文件描述符”。
其中有三个属性:
- in 标准输入(键盘)的描述符(从键盘输入读取流)
- out 标准输出(屏幕)的描述符(讲流输出到控制台上)
- err 标准错误输出(屏幕)的描述符(将流以红色的字体输出到控制台上)
代码示例:
try { FileWriter fw = new FileWriter(FileDescriptor.out); fw.write("我是爱你的。"); fw.flush(); fw.close(); }...
控制台输出:
1.3.2 常用方法
test.txt 文件内容(字符长度为17)
read();读取字符输入流。读取字符输入流的下一个字符,返回一个字符。
try { File file = new File("test.txt"); FileReader fileReader = new FileReader(file); int read = fileReader.read();//默认第一次读取第一个字符 System.out.println((char)read); }...
结果:
read(char cbuf[]);读取字符输入流。读取多个字符,存入字符数组cbuf,返回实际读入的字符数。
try { File file = new File("test.txt"); FileReader fileReader = new FileReader(file); char c [] = new char[20]; int len = fileReader.read(c);// System.out.println("读取的字符长度为:"+len); for (char d : c) { System.out.print(d); } }...
结果:
read(char cbuf[], int off, int len);读取字符输入流。每次读取len个字符,存入字符数组cbuf,从off下标开始存储。
try { File file = new File("test.txt"); FileReader fileReader = new FileReader(file); char c [] = new char[20]; int len = fileReader.read(c,2,8);//读取8个字符存入c数组,从下标2开始存储 System.out.println("读取的字符长度为:"+len); for (char d : c) { System.out.print(d); } }
结果:
close();关闭当前流,释放与该流相关的资源,防止资源泄露。在带资源的try语句中将被自动调用。关闭流之后还试图读取字符,会出现IOException异常。
try { File file = new File("test.txt"); FileReader fileReader = new FileReader(file); int read = fileReader.read();// System.out.println((char)read); fileReader.close();//通过close()来关闭流,以释放系统资源。 }... //或者在这里关闭 ...finally { if(fileReader!=null) fileReader.close(); }
注意:
- 通常不使用close会导致内存泄露,垃圾回收机制会回收,但是最好自己显式关闭
- OutputStream的作用是如FileOutStream,当不调用close的时候,不会将缓存刷入文件中。
所以:一般使用完IO流之后都要通过close()来关闭,以释放系统资源
2 字符输出流
2.1 Writer类的常用方法
- write (String str); 将指定的字符串写入此输出流。
- write(char[] cbuf, int off, int len); 将指定 char 数组中从偏移量
off
开始的len
个字符写入此输出流。 - flush(); 用于清空缓存里的数据,并通知底层去进行实际的写操作。(强制把缓存区里面的数据写入到文件)
- close();关闭当前流,释放与该流相关的资源。
2.2 Writer类的子类:FileWriter
FileWriter类从OutputStreamReader类继承而来(间接继承Writer类)。该类按字符向流中写入数据。
2.3 FileWriter构造方法和常用方法
2.3.1 构造方法
- FileWriter(File file);通过打开一个到实际文件的连接来创建一个
FileWriter
,该文件通过文件系统中的File
对象file
指定。 - FileWriter(File file, boolean append);通过打开一个到实际文件的连接来创建一个
FileWriter
,该文件通过文件系统中的File
对象file
指定。 如果第二个参数为true,则将字符写入文件末尾处,而不是写入文件开始处。 - FileWriter(String fileName);通过打开一个到实际文件的连接来创建一个
FileWriter
,该文件通过文件系统中的路径名name
指定。 - FileWriter(String fileName, boolean append);通过打开一个到实际文件的连接来创建一个
FileWriter
,该文件通过文件系统中的路径名name
指定。如果第二个参数为true,则将字符写入文件末尾处,而不是写入文件开始处。 - FileWriter(FileDescriptor fd);在给定从中写入数据的
FileDescriptor
的情况下创建一个新 FileReader。(可以向控制台输出文本流)。
2.3.2 常用方法
write (String str); 将指定的字符串写入此输出流。
try { File file = new File("test.txt"); Writer fileWriter = new FileWriter(file); fileWriter.write("十年之前,我不认识你。"); fileWriter.flush(); fileWriter.close(); }...
结果:
write(int c );将指定的字符写入此输出流。
try { File file = new File("test.txt"); Writer fileWriter = new FileWriter(file); fileWriter.write('育'); fileWriter.flush(); fileWriter.close(); }...
结果:
write(char[] cbuf);将 cbuf 字符数组写入此输出流。
try { File file = new File("test.txt"); Writer fileWriter = new FileWriter(file); char[] charArray = "字符串转字符数组".toCharArray(); fileWriter.write(charArray); fileWriter.flush(); fileWriter.close(); }
结果:
write(char[] cbuf, int off, int len);将 cbuf 字符数组,按偏移量
off
开始的len
个字符写入此输出流。try { File file = new File("test.txt"); Writer fileWriter = new FileWriter(file); char[] charArray = "字符串转字符数组".toCharArray(); fileWriter.write(charArray, 1, 5);//从偏移量 1 开始,写入5个字符。 fileWriter.flush(); fileWriter.close(); }
结果:
write(String str, int off, int len);
try { File file = new File("test.txt"); Writer fileWriter = new FileWriter(file); String str ="字符串也可以制定写的内容"; fileWriter.write(str, 3, 5); fileWriter.flush(); fileWriter.close(); }
结果:
3 转换流(重点掌握)
字节流转字符流,称作转换流,包括:
- InputStreamReader—> 将字节流转换为字符流。是字节流通向字符流的桥梁。如果不指定字符集编码,该解码过程将使用平台默认的字符编码,如:GBK/UTF-8。
- OutputStreamWriter—> 将字节流转换为字符流。是字节流通向字符流的桥梁。如果不指定字符集编码,该解码过程将使用平台默认的字符编码,如:GBK/UTF-8。
3.1 InputStreamReader的构造方法
InputStreamReader(InputStream in);//构造一个默认编码集的InputStreamReader类。
InputStreamReader(InputStream in,String charsetName);构造一个指定编码集的InputStreamReader类。
3.2 InputStreamReader的使用
try {
File file = new File("test.txt");
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis);
int read = isr.read();
//char cbuf[]=new char[512];
//isr.read(cbuf);
System.out.println((char)read);
isr.close();
}...
3.3 OutputStreamWriter的构造方法
- OutputStreamWriter(OutputStream out);构造一个默认编码集的OutputStreamWriter类
- OutputStreamWriter(OutputStream out,String charsetName);构造一个指定编码集的OutputStreamWriter类。
3.4 OutputStreamWriter的使用
try {
File file = new File("test.txt");
FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter osw = new OutputStreamWriter(fos);
String str="育知同创";
osw.write(str);//直接写入字符串
//osw.write(str.toCharArray());//写入 char 数组
osw.flush();
osw.close();
}...
4 字符缓存流(BufferedReader/BufferedWriter)
(BufferedReader/BufferedWriter) 是带缓冲区的字符流,默认缓冲区大小是8Kb,能够减少访问磁盘的次数,提高文件读取性能;并且可以一次性读取一行字符。(类似管道套管道一样,不带缓冲的流只能一滴一滴流,套了管道后,可以让一滴一滴留到外面的管道后一次性流出。)
4.1 字符缓存流构造方法
4.1.1 BufferedReader
- BufferedReader(Reader in);创建一个默认缓冲区大小 8Kb 的字符缓冲输入流;
- BufferedReader(Reader in, int sz);创建一个字符缓冲输入流;并分配 sz/byte 大小的缓冲区。
4.1.1 BufferedWriter
- BufferedWriter(Writer out); 创建一个默认缓冲区大小 8Kb 的字符缓冲输出流;
- BufferedWriter(Writer out, int sz); 创建一个字符缓冲输出流;并分配 sz/byte 大小的缓冲区。
4.2 字符缓存流的常用方法:readLine(), newLine()
BufferedReader.readLine();在字符缓冲输入流读取字符的时候,可以一次性读取一行,并将游标指向下一行。
try { File file = new File("test.txt"); FileInputStream fis = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); String str; while ((str = br.readLine())!=null) { System.out.println(str); } }...
BufferedWriter.newLine();在字符串缓冲输出流写入字符的时候,默认是在一行写入,当需要换行的时候,调用 newLine() 实现文本换行。
try { File file = new File("test.txt"); FileOutputStream fos = new FileOutputStream(file); OutputStreamWriter osw = new OutputStreamWriter(fos); BufferedWriter bw = new BufferedWriter(osw); bw.write("写入一行的文本"); bw.newLine();//换行 bw.write("写入第二行的文本"); bw.flush();//刷新缓冲区,强制写入文件中 bw.close(); }...
2.1 内存流(重点掌握)
2.1.1 什么是内存流
当输出流的目的,和输入流的源是内存时,这样的流称之为内存流。(就是将数据写入RAM)
2.1.2 内存流的构造方法
- ByteArrayInputStream(byte buf[]); 创建一个 ByteArrayInputStream 并把指定该输入流的数据源buf[]。
- ByteArrayOutputStream(); 创建一个 ByteArrayOutputStream 并把分配一个32字节(默认大小)的缓冲区。
- ByteArrayOutputStream(int size); 创建一个 ByteArrayOutputStream 并分配自定 size 字节的缓冲区。
2.1.3 读取内存数据和写入到内存数据
2.1.3.1 读取内存数据
try {
String testContent = "ABCDEFG";//程序运行的时候 这数据本身就在内存,
ByteArrayInputStream bais = new ByteArrayInputStream(testContent.getBytes());//创建内存输入流,指定要读取的数据 byte[]
int read;
while ((read = bais.read()) != -1) {//和普通流读取字节是一样的(也可以嵌套管道)
System.out.println((char) read);
}
bais.close();//关闭流,释放内存资源
}...
2.1.3.2 写入数据到内存
try {
String testContent = "ABCDEFG";
ByteArrayOutputStream baos = new ByteArrayOutputStream();//创建内存输出流,把数据写入到内存中
baos.write(testContent.getBytes());//和普通的输出流写输入一样,(也可以嵌套管道)
baos.flush();
baos.close();
}...
2.1.4 ByteArrayOutputStream 常用方法:toByteArray(), toString()
- toByteArray() 方法;是将 ByteArrayOutputStream 对象所写入到内存的数据 转换成 byte[] 返回。
- toString() 方法 ;是将 ByteArrayOutputStream 对象所写入到内存的数据 转换成 String 返回。
提示:内存流 除了 ByteArrayInputStream 与 ByteArrayOutputStream 主要处理字节数据之外,对应的还有:
- CharArrayReader 与 CharArrayWriter 主要处理字符数组。
- StringReader 与 StringWriter 主要处理字符串。
使用方式大同小异。
2.3 对象流
2,3,1 对象流
ObjectInputStream ObjectOutputStream类分别是InputStream和OutputStream的子类,对象输出流使用writeObject(Object obj)方法,将一个对象obj写入到一个文件,使用readObject()读取一个对象。
构造方法:
- ObjectInputStream (InputStream in)
- ObjectOutputStream(OutputStream out)
代码示例:
将对象写入文件:
//'序列化'的对象写入文件 OutputStream outputStream = new FileOutputStream(file); ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(Object obj); objectOutputStream.close();
从文件读取对象:
//序列化读取对象 InputStream inputStream = new FileInputStream(file); ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); Object obj = objectInputStream.readObject(); objectInputStream.close();
注意:当使用对象流写入或者读取对象的时候,必须保证该对象是
序列化
的,这样是为了保证对象能够正确的写入文件,并能够把对象正确的读回程序。什么是对象序列化?
2.3.2 对象序列化
所谓的对象的序列化就是将对象转换成二进制数据流的一种实现手段,通过将对象序列化,可以方便的实现对象的传输及保存。在Java中提供了ObejctInputStream 和ObjectOutputStream这两个类用于序列化对象的操作。用于存储和读取对象的输入输出流类,要想实现对象的序列化需要实现Serializable接口,但是Serializable接口中没有定义任何的方法,仅仅被用作一种标记,以被编译器作特殊处理。
package com.yztc.main;
import java.io.Serializable;
//实现了Serializable接口。序列化
public class Student implements Serializable {
//由编译器自动生成,用来解决不同的版本之间的序列化问题。
private static final long serialVersionUID = -79485540193100816L;
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student() {
super();
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
}
2.3.3 transient
- 一旦变量被transient修饰,变量将不再是对象持久化(写到磁盘里持久保存)的一部分,该变量内容在序列化后无法获得访问。
- transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
- 被transient关键字修饰的成员变量不再能被序列化。
- 静态变量不管是否被transient修饰,均不能被序列化。
2.4 RandomAccessFile类
RandomAccessFile 类可以说是Java语言中功能最为丰富的文件访问类,它提供了众多的文件访问方法。RandomAccessFile 类支持“随机访问”方式,可以跳转到文件的任意位置处读写数据。在要访问一个文件的时候,不想把文件从头读到尾,而是希望像访问一个数据库一样地访问一个文本文件,这时,使用RandomAccessFile类就是最佳选择。
RandomAccessFile对象类有个位置指示器,指向当前读写处的位置,当读写n个字节后,文件指示器将指向这n个字节后的下一个字节处。刚打开文件时,文件指示器指向文件的开头处,可以移动文件指示器到新的位置,随后的读写操作将从新的位置开始。RandomAccessFile在数据等长记录格式文件的随机(相对顺序而言)读取时有很大的优势,但该类仅限于操作文件,不能访问其它的IO 设备,如网络、内存映像等。
以读写的方式打开一个文件时,如果文件不存在,程序会自动创建此文件。
有关RandomAccessFile类中的成员方法及使用说明请参阅JDK文档。常见API如下:
方法名 | 描述 |
---|---|
void close(); | 关闭此随机访问文件流并释放与该流关联的所有系统资源。 |
long getFilePointer(); | 返回此文件中的当前偏移量。 |
long length(); | 返回此文件的长度。 |
read函数集 | 从文件读 |
void seek(long pos); | 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。 |
int skipBytes(int n) | 尝试跳过输入的 n 个字节以丢弃跳过的字节。 |
write函数集 | 往文件写 |
2.4.1 RandomAccessFile 类的构造方法
- new RandomAccessFile(f, “rw”); // 读写方式
- new RandomAccessFile(f, “r”); // 只读方式
2.4.2 向文件中记忆写入数据
File file = new File("accessFile");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
// 以下向 raf 文件中写数据
raf.writeInt(20);// 占4个字节
raf.writeDouble(8.236598);// 占8个字节
raf.writeShort(395);// 占2个字节
raf.writeUTF("这是一个UTF字符串");// 这个长度写在当前字符串指针的前两个字节处,可用readShort()读取
raf.writeBoolean(true);// 占1个字节
raf.writeLong(2325451l);// 占8个字节
raf.writeUTF("又是一个UTF字符串哈哈");
raf.writeFloat(35.5f);// 占4个字节
raf.writeChar('a');// 占2个字节
raf.close();
2.4.3 从文件中读取随机记忆的文件内容
File file = new File("accessFile");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
System.out.println(raf.readInt());//读取Int数据,指针会往后移动4字节
System.out.println(raf.readDouble());//读取Double数据,指针会往后移动8字节
System.out.println(raf.readUTF());//读取字符串,指针会移到该字符串后
raf.skipBytes(3);//跳过3个字节,也就是跳过上面例子的 boolen 和 short 值。
System.out.println(raf.readLong());//读取long值
short readShort = raf.readShort();//读取字符串的长度
System.out.println("目前指针处的字符串长度为=" + readShort);
raf.skipBytes(readShort);//跳过该字符串
System.out.println(raf.readFloat());//读取float值
System.out.println(raf.readChar());//读取char值
//long length = raf.length();
//System.out.println("文件的总字节数为:"+length);
//long filePointer = raf.getFilePointer();//当前指针的位置,定位到哪个字节了。
//System.out.println("目前字节指针定位在:"+filePointer);
//raf.seek(4);//直接定位到第4个字节处。
2.5 装饰者模式
2.5.1 装饰者模式的定义
扩展类功能,(继承也能做到)。但是相比继承,装饰者模式是动态地将责任(扩展功能)附加到对象上,比继承更有弹性。
2.5.2装饰者模式的特点
- 装饰者和被装饰对象有相同的超类型。
- 可以用一个或多个装饰者包装一个对象。
- 因为装饰者和被装饰者具有相同的类型,所以任何需要原始对象的场合,可以用装饰过的对象代替。
- 装饰者可以在所委托被装饰者的行为之前/或之后,加上自己的行为,以达到特定的目的。
- 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
2.5.3 装饰者模式缺点
会在设计中加入大量的小类,如果过度使用,会让程序变得复杂。
2.5.4 装饰者模式在JDK中的运用
Java当中的IO是运用了装饰者模式的最典型的例子。
下面是一个简单的例子,通过BufferedReader对象来装饰InputStreamReader对象:
BufferedReader input=new BufferedReader(new InputStreamReader(System.in));
//System.in 是一个 InputStream 对象