一、流的概念
- 概念:内存与存储设备之间传输数据的通道。
流是用来读写数据的,是内存与存储设备之间传输数据的通道,Java中的流实际上是一个对象,真正的文件是在硬盘上的一块空间,在这个文件里面存放着各种各样的数据。而文件的数据通过以流为载体来进行数据的读写。这里的水桶相当于硬盘的文件等,水管相当于流,接水桶相当于程序猿操作的对象或JVM,水桶和接水桶之间通过流来进行数据传输。
二、流的分类
举例:
三、 字节流
- 概念:以字节为单位,可以读写所有数据。
3.1 字节流的父类(抽象类):
- InputStream: 字节输入流:这个抽象类是表示输入字节流的所有类的超类。
- outStream: 字节输出流:这个抽象类是表示字节输出流的所有类的超类。 输出流接收输出字节并将其发送到某个接收器。
3.2 所有方法
- 流的操作需要调用底层操作系统,然后才能写入磁盘,因此流是资源,用完需要关闭;
outStream
方法 | 描述 |
---|---|
void close() |
关闭此输出流并释放与此流相关联的任何系统资源。 |
void flush() |
刷新此输出流并强制任何缓冲的输出字节被写出。 |
void write(byte[] b) |
将 b.length字节从指定的字节数组写入此输出流。 |
void write(byte[] b, int off, int len ) |
从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。 |
abstract void write(int b) |
将指定的字节写入此输出流。 |
InputStream
方法 | 描述 |
---|---|
int available() |
返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞。 |
void close() |
关闭此输入流并释放与流相关联的任何系统资源。 |
void mark(int readlimit) |
标记此输入流中的当前位置。 |
boolean markSupported() |
测试这个输入流是否支持 mark和 reset方法。 |
abstract int read() |
从输入流读取数据的下一个字节。 |
int read(byte[] b ) |
从输入流读取一些字节数,并将它们存储到缓冲区 b 。 |
int read(byte[] b, int off, int len) |
从输入流读取最多 len字节的数据到一个字节数组。 |
void reset() |
将此流重新定位到上次在此输入流上调用 mark方法时的位置。 |
long skip(long n) |
跳过并丢弃来自此输入流的 n字节数据。 |
3.3 字节流子类
FileOutputStream:
- 文件输出流是用于将数据写入到输出流
File
或一个FileDescriptor
。
//绝对路径:D:\\hello\\hello.txt
//相对路径:hello\\Target.txt
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\hello\\hello.txt",true);//绝对路径
// FileOutputStream fos = new FileOutputStream("hello\\Target.txt",true); //相对路径
fos.write(65);
fos.write(66);//一次输出一个字节
fos.write(67);
}
append
的作用:
不传append
参数的时候默认为false
,即不重复写入文件 ,第二次写的将会覆盖第一次写入的字符;如果需要不覆盖,则传true
绝对路径和相对路径:
绝对路径指的是硬盘中真实存在的路径,而相对路径则是指的是该类所在文件下的路径;相比而言,相对路径有更好的移植性;
关于
\
的写法:
在Java中,\
充当的是转义字符,而路径需要\
不被转译,因此会使用\\
表示路径;
如果需要写入的文件不存在会创建吗?文件夹呢?
在write
的时候,如果目标文件不存在,则自动创建,但是如果中间目录不存在则会抛出异常;
FileInputStream:
FileInputStream
从文件系统中的文件获取输入字节。 什么文件可用取决于主机环境。
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\hello.txt");
// 一次读一个字节
/*while (true) {
int n = fis.read();
if (n == -1) {
break;
}
System.out.println((char) n);
}*/
// 一次读一组字节
byte[] cache = new byte[4];// 创建了一个长度为4的数组,作为了读取时的缓存
while (true) {
int count = fis.read(cache);// 每读取一次,填满数组。(注意:最后一次可能无法填满)
if (count == -1)break;
for (int i = 0; i < count ; i++) {// 根据读取字节的个数,决定打印的次数
System.out.print((char) cache[i] + "\t");
}
System.out.println();
}
}
缓存的作用:
缓存可以减少对硬盘的频繁访问,提高效率;
四、字节处理流
4.1 缓冲流
- 提高IO效率,减少访问磁盘次数;
Class BufferedOutputStream:
该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用。
缓冲区类似于水桶,硬盘类似于水缸,缓冲区的大小决定读写硬盘的次数(相同次数下,水桶越大注入缸的水越多),但是水桶也不能过大,不然会拎不动(内存超负载运行);
构造方法
方法 | 描述 |
---|---|
BufferedOutputStream(OutputStream out) |
创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 |
BufferedOutputStream(OutputStream out, int size) |
创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。 |
方法
方法 | 描述 |
---|---|
void flush() |
刷新缓冲输出流。 |
void write(byte[] b, int off, int len) |
从指定的字节数组写入 len个字节,从偏移 off开始到缓冲的输出流。 |
void write(int b) |
将指定的字节写入缓冲的输出流。 |
代码举例
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("hello.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write('A');
bos.write('B');
bos.write('C');
bos.write('D');
bos.flush();
}
只有调用
flush
方法,才会将缓冲区的字符写入文件之中,否则将不会写入;虽然close()
方法也是具有级联执行flush
方法的能力,但是不能完全替代flush
方法,否则就失去了缓冲的意义;
关于BufferedInputStream
Class BufferedInputStream
: 缓冲输入和支持mark
和reset
方法的功能。 当创建BufferedInputStream
时,将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。mark
操作会记住输入流中的一点,并且reset
操作会导致从最近的mark
操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。
实现和自己创建数组进行缓冲没有任何区别:
FileInputStream fis = new FileInputStream("D:\\hello.txt");
byte[] cache = new byte[4];
while (true) {
int count = fis.read(cache);// 每读取一次,填满数组。(注意:最后一次可能无法填满)
if (count == -1)break;
for (int i = 0; i < count ; i++) {// 根据读取字节的个数,决定打印的次数
System.out.print((char) cache[i] + "\t");
}
System.out.println();
}
4.2 对象流
- 序列化:内存当中把一个对象写进文件里,我们称之为序列化;
- 反序列化:从文件当中把一个对象从文件里读回到内存中,我们称之为反序列化;
ObjectOutputStream:
将Java对象的原始数据类型和图形写入OutputStream
。 可以使用ObjectInputStream
读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。
ObjectInputStream:
反序列化先前使用ObjectOutputStream
编写的原始数据和对象。
ObjectOutputStream
和ObjectInputStream
可以分别为与FileOutputStream
和FileInputStream
一起使用的对象图提供持久性存储的应用程序。ObjectInputStream
用于恢复先前序列化的对象。 其他用途包括使用套接字流在主机之间传递对象,或者在远程通信系统中进行封送和解组参数和参数。
5.1 基本方法
方法 | 描述 |
---|---|
int read() |
读取一个字节的数据。 |
boolean readBoolean() |
读取布尔值。 |
byte readByte() |
读取一个8位字节。 |
char readChar() |
读一个16位字符。 |
double readDouble() |
读64位双倍。 |
float readFloat() |
读32位浮点数。 |
int readInt() |
读取一个32位int。 |
long readLong() |
读64位长。 |
Object readObject() |
从ObjectInputStream 读取一个对象。 |
- 写入文本java相关的数据类型
public static void main(String[] args) throws IOException {
/**
* 输出流
*/
OutputStream os = new FileOutputStream("objects.txt");
ObjectOutputStream oos = new ObjectOutputStream(os);
//TODO:调用相关写方法
oos.writeDouble(3.5);//将该数据写入文件 而非文本
oos.flush();
/**
* 输入流
*/
InputStream is = new FileInputStream("objects.txt");
ObjectInputStream ois = new ObjectInputStream(is);
//TODO:调用相关读方法
double result = ois.readDouble();
System.out.println(result);
}
5.2 序列化和反序列化
- 写入文本java相关的对象
public interface Serializable:
类的序列化由实现java.io.Serializable接口的类启用。 不实现此接口的类将不会使任何状态序列化或反序列化。 可序列化类的所有子类型都是可序列化的。 序列化接口没有方法或字段,仅用于标识可串行化的语义。读写未序列化的对象报错如下,需要将相应的对象继承
Serializable
接口;
public class TestObjectStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
/**
* 输出流
*/
OutputStream os = new FileOutputStream("objects.txt");
ObjectOutputStream oos = new ObjectOutputStream(os);
//TODO:序列化
oos.writeObject(new Student("alvin",15,"男"));
oos.flush();
/**
* 输入流
*/
InputStream is = new FileInputStream("objects.txt");
ObjectInputStream ois = new ObjectInputStream(is);
//TODO:反序列化
Object o = ois.readObject();
System.out.println((Student)o);
}
}
class Student implements Serializable {
//以下包装类型的数据类型均已实现Serializable 接口
String name;
Integer age;
transient String gender; //临时属性 不参与序列化
public Student(String name, Integer age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
'}';
}
}
- 序列化结果
- 反序列化结果
5.3 序列化注意事项
-
- 对象必须实现
Serializable
接口
- 对象必须实现
-
- 必须保证对象的所有属性均可序列化
-
transient
修饰的属性为临时属性,不参与序列化
-
- 读取文件尾部的标志:
java,io.EOFEception
- 读取文件尾部的标志:
当写入多个对象时(不知道具体对象的数目),我们使用while循环进行读,跳出循环的条件为捕获到java,io.EOFEception
,因为java,io.EOFEception
表示以读待到文件的末尾,因此根据它可以跳出循环;
public static void main(String[] args) throws IOException, ClassNotFoundException {
/**
* 输出流
*/
OutputStream os = new FileOutputStream("objects.txt");
ObjectOutputStream oos = new ObjectOutputStream(os);
//TODO:序列化
oos.writeObject(new Student("alvin",15,"男"));
oos.writeObject(new Student("tom",16,"男"));
oos.writeObject(new Student("kitty",15,"女"));
oos.flush();
/**
* 输入流
*/
InputStream is = new FileInputStream("objects.txt");
ObjectInputStream ois = new ObjectInputStream(is);
//TODO:反序列化
while(true){
try{
Object o = ois.readObject();
System.out.println((Student)o);
}catch (EOFException e){
break;
}
}
}
}
- 打印结果:
五、字符流
6.1 字节流的父类(抽象类):
-
Reader: 用于读取字符流的抽象类。 子类必须实现的唯一方法是
read(char [],int,int)
和close()
。 然而,大多数子类将覆盖这里定义的一些方法,以便提供更高的效率,附加的功能或两者。 -
Writer: 用于写入字符流的抽象类。 子类必须实现的唯一方法是
write(char [],int,int)
,flush()
和close()
。 然而,大多数子类将覆盖这里定义的一些方法,以便提供更高的效率,附加的功能或两者。
6.2 主要方法
Reader
方法 | 描述 |
---|---|
void write(char[] cbuf) |
写入一个字符数组。 |
void write(int c) |
写一个字符 |
void write(String str) |
写一个字符串 |
Writer
方法 | 描述 |
---|---|
int read() |
读一个字符 |
int read(char[] cbuf) |
将字符读入数组。 |
int read(CharBuffer target) |
尝试将字符读入指定的字符缓冲区。 |
6.3 字符节点流
FileWriter
和FileReader
FileWriter
继承自OutputStreamWriter
FileReader
继承自InputStreamReader
6.3 字符处理流——缓冲流
InputStreamReader/OutputStreamWriter
BufferedWriter/BufferedReader
- 支持输入换行符
- 可以写一行、读一行
public static void main(String[] args) throws IOException {
/**
* 输出流
*/
Writer fw = new FileWriter("a.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("A");bw.newLine();//写入并换行
bw.write("B");bw.newLine();
bw.write("C");bw.newLine();
bw.close();//关闭流
/**
* 输入流
*/
Reader fr = new FileReader("a.txt");
BufferedReader br = new BufferedReader(fr);
while (true){
String s = br.readLine();//一次读一行
if(s == null) break;
System.out.println(s);
}
}
- 查看文件:
BufferedWriter
换行需要调用另外的newLine()
方法;可以使用PriterWriter
类
PriterWriter
- 封装了
print()/println
方法,支持写入后换行。
public static void main(String[] args) throws IOException {
/**
* 输出流
*/
Writer fw = new FileWriter("a.txt");
PrintWriter pw = new PrintWriter(fw);
pw.println("A");//换行
pw.println("B");
pw.println("C");
pw.close();//关闭流
/**
* 输入流
*/
Reader fr = new FileReader("a.txt");
BufferedReader br = new BufferedReader(fr);
while (true){
String s = br.readLine();//一次读一行
if(s == null) break;
System.out.println(s);
}
}
。。。。。待更