一:IO流的分类:
按照方向:输入流和输出流
按照流的大小:字节流和字符流
按照流的角色:节点流和处理流
二:先谈谈字符流和字节流的区别:字符流其实是基于字节流的操作,只是字符流中的字节流操作被封装起来了,所以当我们要对文档进行流操作时选用字符流操作比较合适,因为它提供了更快捷方便的方法来让我们对文档进行操作,但是当我们要对其它文件(如视频文件,图片文件)进行操作时就得使用字节流操作流。
1:字节流的FileInputStream和FileOutputStream:实现文件的拷贝
public class FileIoCopyDemo01 {
static void doCopy01(File src, File dest) throws IOException {
// 1.构建流文件
// 2.读写数据
// 3.释放资源
FileInputStream in = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(dest);
// 读写数据
do {
int len = in.read();
if (len == -1) {// 判断是否读到了文件尾
break;
}
fos.write(len);
} while (true);
// 释放资源
in.close();
fos.close();
}
public static void main(String[] args) throws IOException {
src = new File("/Users/xuyunqi/Desktop/堆栈内存图.png");
dest = new File("/Users/xuyunqi/Desktop/堆栈内存图copy.png");
doCopy01(src, dest);
}
}
值得一提的是FileInputStream中read()和read(byte[] arr)的区别,无参的read返回值为int类型,返回的是文件的内容。以byte[]为参数的read方法返回值也为int,但是这里返回的却是读入的字节数,文件的内容保存在byte[]里面。根据FileInputStream的不同read方法决定调用FileOutputStream中什么样的Write方法。
2:BufferedReader:将当前源代码中的所有内容输出到控制台
public class BufferedReader_readLine {
public static void main(String[] args) throws IOException{
FileInputStream fis = new FileInputStream("."+File.separator+"src/day2/BufferedReader_readLine.java");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
/*
* BufferedReader提供了读取一行字符串的方法:String readLine()
* 该方法会顺序读取若干字符,直到读取了换行符为止,然后将换行符之前的字符组成一个字符串返回,
* 若返回值为null,说明读到了末尾
*
*/
String line = null;
while((line = br.readLine())!=null) {
System.out.println(line);
}
br.close();
}
}
3:OutputStreamWriter和InputStreamReader:
先用OutputStreamWriter写出内容到指定文件
**
* java中的流按照读写单位分为两类:字节流,字符流
* 字符流的读写最小单位是一个字符,所以字符流值用来读写文本数据
* 字符流底层本质还是读写字节,只是字符与字节之间的转换
* 工作交由字符流完成了,方便了我们读写文本数据的读写工作
*
* java.io.Reader是所有字符输入流的父类
* java.io.Writer是所有字符输出流的父类
*
* 常见的实现类之一:转换流
* java.io.InputStreamReader
* java.io.OutputStreamWriter
* @author xuyunqi
*
*/
public class OutputStreamWriter_write {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("osw.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
String str = "我知道,那些夏天,就像青春一样回不来";
/*
* osw会将给定的字符串按照指定的字符串集转换为一组字节,
* 然后再通过其连接的流(这里是fos)将这组字节写出。
*
*/
osw.write(str);
System.out.println("写出完毕");
osw.close();
}
}
再用InputStreamReader读入
public class InputStreamReader_read {
/*
* 转换流:java.io.InputStreamReader可以将字节转换为字符
*/
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("osw.txt");
InputStreamReader isr = new InputStreamReader(fis,"utf-8");
//读一个字符
int i = -1;
/*
* 字符输入流读read方法是一次读取一个字符,但是由于不同读字符集中字符对应的字节长度不同,
* 所以read方法实际读取多少个字节由指定的字符集决定,但是读取了该字符后在java内部都是用unicode编码保存
* 所有字符均2个字节
*/
//返回的int值的“低16位”有效,unicode编码的字符
while ((i = isr.read())!=-1) {
char c = (char)i;
System.out.print(c);
}
isr.close();
}
}
4:ObjectOutputStream和ObjectInputStream:对象输入输出流
在此之前我们先来聊聊序列化的问题:
当一个类实现了Serializable接口后,应当添加一个常量:serialVersionUID,序列化版本号
序列化版本号影响对象输入流在反序列化对象时的结果当反序列化当对象与所属类现在当版本号不一致时反序列化会失败,抛出:InvalidClassException
若版本号没有发生变化,是可以反序列化成功的,哪怕当前类的结果与待发序列化的对象结构不符,但若不符则采取但原则是还原已有的属性,没有的则忽略了。
如果不加序列化版本号,会默认生成一个,只要类当内容发生了变化,其序列化版本号也会发生改变,所以不推荐不加。
接下来我们创一个Person类用来测试对象读写操作:
public class Person implementsSerializable{private static final long serialVersionUID = -2022551023508890593L;privateString name;private intage;privateString gender;/** 当一个属性被transient关键字修饰时,这个对象在序列化时该属性值会被忽略,忽略不需要序列化当属性可以达到“对象瘦身”当效果*/
private transientString otherInfo;publicPerson() {
}publicString getName() {returnname;
}public voidsetName(String name) {this.name =name;
}public intgetAge() {returnage;
}public void setAge(intage) {this.age =age;
}publicString getGender() {returngender;
}public voidsetGender(String gender) {this.gender =gender;
}publicString getOtherInfo() {returnotherInfo;
}public voidsetOtherInfo(String otherInfo) {this.otherInfo =otherInfo;
}public Person(String name, intage, String gender, String otherInfo) {super();this.name =name;this.age =age;this.gender =gender;this.otherInfo =otherInfo;
}publicString toString() {return name+","+age+","+gender+"."+otherInfo;
}
}
用ObjectOutputStream将对象写出到文件 :
/*** 对象流
* 对象流是一对高级流,作用是可以方便地读写java中的任何对象
*
* 若将对象流与文件流进行连接,则可以在文件中读写java中的对象
*
* java.io.objectOutputStream
* 对象输出流,用于将java对象转换为一组字节后写出
*
* java.io.objectInputStream
* 对象输入流,用于读取一组字节后转换为对应的java对象
* 前提是该组字节应当是对象输入流将一个对象转换的字节
*@authorxuyunqi
**/
public classObjectOutputStream_writeObject {public static void main(String[] args) throwsIOException {
Person person= new Person("徐运齐",21,"男","性情温和");
System.out.println(person);/** 将person对象写入文件*/FileOutputStream fos= new FileOutputStream("person.obj");
ObjectOutputStream oos= newObjectOutputStream(fos);/** 对象输入流提供的WriteObject方法可以将给定的对象转化为一组字节,这里对象流连接了文件流,
* 所以转换的这组字节就通过文件流最终写入了文件
*
* oos将对象转换为一组字节的过程称为:对象序列化
*
* 将这组字节通过fos写入文件做长久保存的过程称为:持久化*/oos.writeObject(person);
System.out.println("写出完毕");
oos.close();
}
}
用ObjectInputStream读取对象:
public classObjectInputStream_readObject {public static void main(String[] args) throwsIOException, ClassNotFoundException {
FileInputStream fis= new FileInputStream("person.obj");
ObjectInputStream ois= newObjectInputStream(fis);
Object person=ois.readObject();
System.out.println("反序列化完成");
System.out.println(person);
ois.close();
}
}
5:再来看一种高端操作:PrintWriter:
缓冲字符流 java.io.BufferedWriter java.io.BufferedReader 缓冲字符流读写字符串效率高,并且可以按行读写字符串
java.io.PrintWriter:具有自动行刷新的缓冲字符输出流, 其内部的缓冲操作就是靠BufferedWriter完成的,所以PW总是会连接在BW上
以下代码实现在控制台一行一行地输入内容到文件中并具备自动行刷新功能,即控制台输入一次写入一次,而不是写完后一次性存到文件中,大家可以边写边对应文件内容存查看:
public classPrintWriter_autoFlush {public static void main(String[] args) throwsIOException {
Scanner scanner= newScanner(System.in);
System.out.println("请输入字符串,输入exit退出");
PrintWriter pw= newPrintWriter(newBufferedWriter(newOutputStreamWriter(new FileOutputStream("note.txt",true)//true为了下次写出时追加内容,不会覆盖
)
),true //默认为false,若该值为true,则pw具有自动行刷新功能,即:每次使用println方法写出一行字符串后会自动flush
);while (true) {
String str=scanner.nextLine();if ("exit".equals(str)) {//这里用exit调用equals方法是为了避免输入为空报空指针错误
break;
}
pw.println(str);
}
scanner.close();
pw.close();
}
}
6:看了这么多流操作,最后强调一点,流操作记得用完后要调用close方法,至于close的顺序,这里给出的理解是:
一般情况下是:先打开的后关闭,后打开的先关闭
另一种情况:看依赖关系,如果流a依赖流b,应该先关闭流a,再关闭流b。例如,处理流a依赖节点流b,应该先关闭处理流a,再关闭节点流b
可以只关闭处理流,不用关闭节点流。处理流关闭的时候,会调用其处理的节点流的关闭方法。
7:最后来谈谈RandomAccessFile这个类:这里就不上代码了,我个人对于这个类和流操作的理解是:RandomAccessFile不属于流操作,这个类对于文档的读写更加灵活,因为可以调用它的seek方法来控制指针的位置进行读写,但是流操作的话就比较难实现这点了。所以一般需要灵活操作文档就用RandomAccessFile吧,但是如果操作的量很大,还是流更适合,毕竟人家有缓冲区。