Java中的流以流向来分的话可以分为两类:输入流和输出流,当然输入和输出都应该是站在程序的角度上的,同时还存在另外一种分法,那就是以数据保存在磁盘中的格式来分,比如像文本文件,就是字符保存在磁盘之中,而像图片、音频和视频则是以字节保存在磁盘之中,因此就可以将流分为字符流和字节流这两类,前面有一篇文章已经介绍了使用字符流对文本文件进行读写了,所以这里就主要介绍字节流了,除了字节流之外,这里还会介绍几种特殊的流,比如打印流、对象操作流、将字节流转换为字符流的转换流。
1.字节流
字节流分为两类:字节输入流(InputStream)和字节输出流(OutputStream)
字节流
字节输入流(InputStream)
字节输出流(OutputStream)
那为什么需要使用字节流呢?不是已经有字符流了吗,字符流不是一样可以把图片写到我们指定的目录吗,确实是这样,通过字符流一样可以将一个图片文件写到我们指定的目录,但是程序运行完了之后,我们用图片查看器是打开不了文件的,因为字符流在操作图片文件的过程中是将图片数据看做是字符的,而图片文件是以字节来保存的,因此我们必须使用字节流来操作图片、音频和视频这些文件,使用字节流来复制视频不仅比我们直接用鼠标复制速度来得快,而且还可以根据我们在程序中指定的格式进行转换,比如rmvb格式的视频很快就可以转换成mkv的,再也不需要找视频格式转换工具了,直接将rmvb格式的视频文件修改后缀为mkv后缀的,播放器可是识别不了的,有了一点知识运用到生活中的感觉。
使用字符流复制图片:
public static void main(String[] args) throws IOException {
//创建字符输入流对象
FileReader fr = new FileReader(".jpg");
//创建字符输出流对象
FileWriter fw = new FileWriter("b.jpg");
//一次读写一个字符数组
int len;//用于存储读到的字符个数
char[] chs = new char[1024];
while((len = fr.read(chs)) != -1) {
fw.write(chs,0,len);
fw.flush();
}
//释放资源
fw.close();
fr.close();
}
}
上面这种方式使用字符流复制图片,虽然可以将文件写到指定目录,但是却不能用图片查看器打开。
字节流复制图片:
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("I:\\Java\\a.PNG");
FileOutputStream fos = new FileOutputStream("b.jpg");
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fis.close();
fos.close();
}
其实这上面也进行了格式的转换,将png格式的文件转换为jpg格式的了。
字节流复制视频:
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("H:\\视频\\变形金刚1");
FileOutputStream fos = new FileOutputStream("H:\\视频\\变形金1.rmvb");
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
fis.close();
fos.close();
}
上面复制的《变形金刚》视频没有后缀名,但是可以在播放器里面播放,这里在复制的时候,将要复制的目标地址中加上了后缀名,也成功了。以后复制文件并且想修改文件格式的就用这种方法吧。
使用字节流可以复制图片、视频,那文本文件呢?当然也是可以的。
public static void main(String[] args) throws IOException {
// 创建字节输入流对象
FileInputStream fis = new FileInputStream("a.txt");
// 创建字节输出流对象
FileOutputStream fos = new FileOutputStream("b.txt");
// 一次读写一个字节数组
int len;// 存储读到的字节个数
byte[] bys = new byte[1024];
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
所以说字节流是可以读写一切文件的,包括文本文件,那为什么还要使用字符流呢?那就是因为当读取文本文件时,字符流比字节流效率更高。
2.将字节流转换为字符流的转换流
转换流一种是分为两种,将字节输入流(InputStream)转换为字符输入流(Reader)的InputStreamReader,还有就是将字节输出流(OutputStream)转换为字符输出流(Writer)的OutputStreamWriter.
转换流:
InputStreamReader(字节输入流转为字符输入流)
的OutputStreamWriter(字节输出流转换为字符输出流)
很明显就会有一个疑问,为什么都是字节流转为字符流,而没有字符流转换为字节流呢?我自己的感觉是,大概是因为没有那个必要吧,因为字节流本来可以读写一切文件,但是字符流在操作文本文件上效率更高,所以只需要在操作文本文件时使用字符流,而其它情况使用字节流就可以了,并且系统的System.in
和System.out
也是字节流。
下面的三个方法则是展示使用转换流的一个过程,当然我们可以不使用转换流,但是使用了之后确实可以提高效率。
方法1:
public static void method() throws IOException {
System.out.println("请输入:");
InputStream in = System.in;
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
int len;
byte[] bytes = new byte[1024];
while ((len = in.read(bytes)) != -1) {
bw.write(new String(bytes, 0, len));
bw.flush();
}
in.close();
bw.close();
}
方法2:
public static void method2() throws IOException {
System.out.println("请输入:");
Reader r = new InputStreamReader(System.in);
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
int len;
char[] chs = new char[1024];
while ((len = r.read(chs)) != -1) {
bw.write(chs, 0, len);
bw.flush();
}
r.close();
bw.close();
}
方法3:
public static void method3() throws IOException {
System.out.println("请输入:");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new FileWriter("a.txt"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
上面三个方法实现的功能都是将控制台输入的字符存储到文本文件中,不过很明显可以看出,最后一种方法使用BufferedReader读取一行字符效率会更高。
3.打印流(PrintWriter)
打印流是一个输出流,它的特殊之处可以在构造器看出:
PrintWriter(OutputStream out)
PrintWriter(Writer out)
没错,传入的构造参数既可以是字节流,也可以是字符流,当然它还有很多构造器,包括File对象,还有字符串的,它所具有的特殊方法就是拥有和系统的System.out
有一样的print()
以及println()
方法,而且同样,一个输出不换行,一个输出换行。
下面是一个例子:
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("ByteStreamDemo.java"));
PrintWriter pw = new PrintWriter(new FileWriter("D:\\ByteStreamDemo.java"), true);
String line;
while ((line = br.readLine()) != null) {
pw.println(line);
}
br.close();
pw.close();
}
4.对象操作流
对象流就是可以直接将对象写入到文本文件当中,也可以从文本文件之中直接读取对象,当然这有一个前提,那就是对象流所操作对象的所属类必须实现了序列化,其实实现序列化也只需要在类声明上加一个implements Serializable
,这样就实现了序列化。
其中有一个需要注意的点就是,当将一个对象写入到文本文件之中后,接着修改了对象所属类的成员变量,比如增加或者删除了某些成员变量,这时再使用对象流读取文本文件中的对象就会报错,这是因为实现了序列化接口的类会根据成员变量生成一个serialVersionUID
,而如果写出时和读取时这个serialVersionUID
是不一样的,那就会报错了,其实解决这个问题也很简单,那就是在类中声明一个serialVersionUID
,当将对象写入到文本文件之前就固定了一个serialVersionUID
,即使在读取之前修改了类的成员变量,也不会报错,如果增加了成员变量,当读取的时候新加的成员变量也会直接赋值为null
,而如果删除了部分成员变量,读取的时候会直接忽略掉那些成员变量。
下面先给出一个实现了序列化的类:
import java.io.Serializable;
public class Student implements Serializable {
private static final long serialVersionUID = -5695890819109824065L;
private String name;
private String gender;
private int age;
public Student() {
super();
}
public Student(String name, String gender, int age) {
super();
this.name = name;
this.gender = gender;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", gender=" + gender + ", age=" + age
+ "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
下面是使用对象流进行读写对象:
public static void writeStudent() throws FileNotFoundException, IOException {
FileOutputStream fos = new FileOutputStream("student.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Student s = new Student("樱木花道",16);
Student s2 = new Student("流川枫",17);
oos.writeObject(s);
oos.writeObject(s2);
fos.close();
oos.close();
}
public static void readStudent() throws FileNotFoundException, IOException,ClassNotFoundException {
FileInputStream fis = new FileInputStream("student.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
try {
while(true){
Object obj = ois.readObject();
System.out.println(obj);
}
} catch (EOFException e) {
System.out.println("读取完了");
}
fis.close();
ois.close();
}
下面则是直接读写一个集合,不过集合中的对象所属的类必须实现序列化接口
public static void writeStudent2() throws IOException, FileNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
"student.txt"));
ArrayList<Student> list = new ArrayList<Student>();
Student s = new Student("樱木花道", 15);
Student s2 = new Student("流川枫", 16);
list.add(s);
list.add(s2);
oos.writeObject(list);
oos.close();
}
public static void readStudent2() throws IOException, FileNotFoundException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"student.txt"));
Object obj = ois.readObject();
ArrayList<Student> list = (ArrayList<Student>) obj;
for (Student student : list) {
System.out.println(student);
}
ois.close();
}
5.总结
关于流的使用,其实熟练使用了一种流之后,再使用其它的流就会感觉不是很难,因为方法基本上都差不多,而且如果有比较特殊的方法的话,通过查询API也能很快地找到。在使用IO流的过程中,通过字节流去复制图片或者视频,还能改变文件格式,确实感觉蛮好用的。