流(Stream)
流,看起来好像很抽象的一个概念 确实如此,但其实我们写的Java第一个程序就用到了流。我们最开始写用来hello world的System.out.println()
就是一种输出流(OutputStraem),从控制读输入时用的System.in.read()
则是一种输入流(InputStream)。它们俩就是Java里IO流中的字节流(还有一个字符流)。
提到流我们很难不想像到溪流,流确实也像溪流那样,单方向流动,流一般也还有长度。
字节流
字节流是Java IO流中的一种,字节流下面还有OutputStream
和InputStream
这两个抽象类。我们尝试一下从控制台读入流:
byte[] chars = new byte[1024];
try {
//read方法会返回它读到的字节数
int len = System.in.read(chars);
String s = new String(chars,0,len);
System.out.println("读到了"+len+"个字节");
System.out.println(s);
System.out.println("s的长度为"+s.length() );
} catch (IOException e) {
e.printStackTrace();
}
我们输入"aaa111"然后看看结果:
读到了7个字节
aaa111
s的长度为7
它居然说读到了7个字节,但我们只输了了6个字符。这是因为我们输入完成后,还按了一下回车,回车也算一个字节。 我们看到"aaa111"后面还有一个空行,因为我们使用的是printnl()
带一个回车,然后我们的输入里面还有一个回车,所以才会有那个空行。
我们输入"你好,世界"试试:
读到了16个字节
你好,世界
s的长度为6
这次它读到了16个字节,因为unicode编码中一个中文字符算3个字节。我们输入了5个中文字符和一个回车,刚好16个字节。s的长度是按照字符个数来算的,不受不同编码不同字节数的影响,所以还是为6。
文件中的字节流
这次我们要用到FileOuputStream
来测试一下文件输出流,看得出来它肯定和OutputStream
有点关系:
byte[] bytes = new byte[10];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) i;
}
try {
FileOutputStream out = new FileOutputStream("new.dat");
out.write(bytes);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
运行完后,在项目的根目录多了发现多了一个 new.dat 文件,用二进制格式打开它发现:
00 01 02 03 04 05 06 07 08 09
我们成功往 new.dat 文件里放了10组数字,OutPutStream
对象完完整整地把循环变量以二进制的方式从内存中输出到了文件里面。
DataOutputStream
outputStream
对象只能做最基本的8位二进制输出,如果我们想要输出基本数据类型,就需要借助DataOutputStream
对象(这叫过滤流)
// 通过FileOutputStream来构建DataOutputStraem对象
DataOutputStream out = new DataOutputStream(
new FileOutputStream("new.dat") );
out.writeInt(0xcafebabe);
// out.writeUTF("helloworld");
out.close();
DataInputStream in = new DataInputStream(
new FileInputStream("new.dat") );
System.out.println(in.readInt() );
in.close();
} catch (IOException e) {
e.printStackTrace();
}
我们通过流往文件里写了一个十六进制的数据cafebabe
,我们用打开该文件,里面同样还是显示着cafebabe
。好吧,文件是以十六进制格式展示的。我们再用输入流读入该文件,我们发现读入的数是682085
。
字符流
前面我们都是通过二进制流的方式读写文件,写入文件中的实际上是二进制文件,作为人的我们阅读起来很困难啊。尽管FileOutputStream
有writeUTF()
方法允许我们写入uft字符,但是,但是我们打开文件查看发现还是uft字符在二进制中的编码。好吧。我们还是使用字符流来干这事吧。
字符流提供了两个对象,Reader和Writer,分别对应读和写。
由于Reader和Writer是两个抽象类无法直接使用,使用我们使用它们的子类 BufferedReader
和BufferedWriter
。
让我们想让写段代码,然后当它读取自己吧😅:
BufferedReader in = new BufferedReader(
new InputStreamReader(
new FileInputStream("Day1/src/stream/Test.java")
)
);
String line;
//BufferedReader的Readline()方法每次读取一行,在读到文件末尾时返回一个null,所以我们让它没读到null时循环读取每一行
while( (line = in.readLine() )!= null)
{
System.out.println(line);
}
in.close();
输出结果:
package stream;
import java.io.*;
public class Test {
public static void main(String[] args) {
try {
// BufferedWriter out = new BufferedWriter(
// new OutputStreamWriter(
// new FileOutputStream("out.txt")
// )
// );
// out.write("输出流一段中文");
BufferedReader in = new BufferedReader(
new InputStreamReader(
new FileInputStream("Day1/src/stream/Test.java")
)
);
String line;
while( (line = in.readLine() )!= null)
{
System.out.println(line);
}
// out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Process finished with exit code 0
成功了!成功的让代码读到了自己。
这是个成功的字符流使用案例,但其实看代码发现字符流的底层其实还是字节流。我们用了InputStreamReader
对象来建立字节流与字符流的转换桥梁。可以说InputStreamReader
是字节流和字符流之前的桥梁。
字符流的输出操作也基本和之前的类似,已经注释在代码中了。
Scanner
Scanner同样也能作为读入文件的对象,只要给也给他它一个输入流就行了。
Scanner in = new Scanner( new BufferedInputStream(
new FileInputStream("Day1/src/stream/Test.java") ) );
String line;
while( in.hasNextLine())
{
System.out.println(in.nextLine());
}
in.close();
这几种方式该如何选择呢?
if(二进制数据)
{
useInputStream();
}
else if(数据是文本类型)
{
userReader();
}
else
{
userScanner();
}
got it?
对象串行化
既然能通过流往文件里面写入各种类型的数据,那我们能直接将对象写进文件里面嘛?虽然可以直接把对象保存的数据通过字节流(eg DataOutputStream
)写入文件。但写入一个完整的对象,听起来是不是就更🐂🍺一点呢?这种将对象以二进制存储的形式,叫对象串行化。
我们要用到ObjectOutputStream
,很直观的名字是吧。
我们先把对象做出来:
///对象需要实现Serializable接口才能顺利串行化
class Student implements Serializable{
String name;
int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
我们怎么把对象变成字节:
Student s1 = new Student("Lee",12);
// ObjectOutputStream对象需要建立在一个FileOutputStream的基础上
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("stu.dat")
);
out.writeObject(s1);
out.close();
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("stu.dat")
);
System.out.println( (Student) in.readObject() );
in.close();
成功获得输出:
Student{name='Lee', age=12}
学完这一节,终于能将对象保存进文件里面了。