Java基础IO流学习笔记
一.什么是IO流
I : 指的是input(输入):读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
O:指的是output(输出):将程序(内存)数据输出到磁盘、光盘等存储设备中
所以IO流就是输入输出流,流以程序为中心,往程序里面的就是输入流,往程序外的就是输出流(比如人喝水)
IO流最最重要的地方就是知道在什么时候选用什么流!!!
二.File类
在了解IO流之前先了解一些常用的File类API
1.创建文件
1.绝对路径直接创建:
String filePath = "e:\\new1.txt";
File file = new File(filePath); //创建文件对象,相当于只在内存里面创建,还跟磁盘没有发生关系
file.createNewFile(); //然后调用创建文件的方法(写入到磁盘)
2.父目录文件加子路径创建:
File parentFile = new File("e:\\");
String fileName = "new2.txt";
File file = new File(parentFile,fileName);
file.createNewFile();
3.父目录加子路径构建:
String parentFile = new File("e:\\");
String fileName = "new2.txt";
File file = new File(parentFile,fileName);
file.createNewFile();
2.File类常用API
File file = new File("e:\\new1.txt"); 创建文件对象
调用相应方法得到对应信息
file.getName(); //得到文件名字
file.getAbsolutePath(); //文件绝对路径
file.getParent(); //文件父级目录
file.length(); //文件大小(字节)
file.exists(); //文件是否存在
file.isFile(); //是不是一个文件
file.isDirectory(); //是不是一个文件夹
创建删除文件夹:
if(file.exists){file.mkdir();} //只能创建一级目录("e:\\ddd")
if(file.exists){file.mkdirs();} //可以在多级目录下创建,所以一般都用这个
if(file.exists){file.delete();} //如果文件存在,删除文件(在java中目录也是特殊文件)
三.流的分类
1.按操作数据单位不同分为:字节流(8 bit)二进制文件,字符流(按字符)文本文件
2.按数据流的流向不同分为:输入流,输出流
3.按流的角色的不同分为:节点流,处理流/包装流
IO流由以下四部分顶级基类组成
输入流:字符输入流(Reader),字节输入流(InputStream)
输出流:字符输出流(Writer),字节输出流(OutputStream)
选用什么流的话具体看传输的是什么东西,比如二进制文件(音频,视频
,docx,pdf等等用字节流,因为用字符流的话文件很容易损坏,像文本文档之类的用字符流效率更高)
四.常用的一些流
节点流和处理流的区别:
流按功能又分为节点流和处理流:
- 节点流:可以从一个特定的数据源读写数据,如FileReader、FileWriter,是底层流/低级流,直接跟数据源相接。
- 处理流(也叫包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,也更加灵活,如BufferedReader、BufferedWriter。节点流既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入输出,对节点流进行包装,使用了修饰器设计模式,不会直接与数据源相连(相当于给节点流包了一层)
处理流的功能主要体现在以下两个方面:
- 性能的提高:主要以增加缓冲的方式来提高输入输出的效率。
- 操作的便捷:处理流可能提供了一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
下面是所有节点流和处理流的直观图
InputStream常用子类(字节输入流)
1.FilelnputStream:
示例:
1.第一种读取文件的方式:read()无参,一个字节一个字节地读,效率低,一般不用
String filePath = "e:\\hello.txt";
int readDate = 0 ; //临时存放数据
FileInputStream fileInputStream = null; //这样定义是为了提升作用域,不然finally里面拿不到fileInputStream对象
try {
fileInputStream = new FileInputStream(filePath); //创建FileInputStream对象,用于读取文件
while ((readDate=fileInputStream.read())!=-1){
//每读取一个字节输出一个字节 当没有下一个值时返回-1
System.out.print((char)readDate); //如果文本里面有中文会出现乱码,因为一个中文占3个字节,而一次只读取一个字节
}
} catch (IOException e) {
e.printStackTrace();
}finally {
fileInputStream.close(); //读取完一定要关闭流,不然很占用资源
}
2.第二种读取方式:read(byte [ ] b) 一次最多读取b.length长度的字节,读不到返回-1(通常用这种!!!!)
String filePath = "e:\\hello.txt";
int readLen = 0 ; //存放字节长度
byte[] buf = new byte[8]; //一次读取八个字节
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(filePath);
while ((readLen=fileInputStream.read(buf))!=-1){
//每读取8个字节,返回整数8,最后一次返回最后一次读取了多少字节的整数,没读取到返回负1
System.out.print(new String(buf,0,readLen)); //String(字节数组,从哪个索引开始读取,读取字节的长度)
}
} catch (IOException e) {
e.printStackTrace();
}finally {
fileInputStream.close();
}
2.BufferedInputStream:
缓冲流,构造器里面可以放任意节点流,外边包了一层,效率更高。
使用案例:
@Test
public void bufferedInputStream() throws IOException {
String path = "f:\\s.txt";
int readLen = 0;
byte[] buf = new byte[1024];
BufferedInputStream bi = new BufferedInputStream(new FileInputStream(path));
while ((readLen=bi.read(buf))!= -1){
System.out.println(new String(buf,0,readLen));
}
bi.close(); //这里底层会去实现FileReader.close()方法
}
3.ObjectInputStream:
看一个需求
1.将int num = 100这个int数据保存到文件中,注意不是10数字,而是int 100,并且,能够 从文件中直接恢复int 100
2.将Dog dog = new Dog(“小黄”,3)这个dog对象保存到文件中,并且能够从文件恢复.
3.上面的要求,就是能够将基本数据类型或者对象进行序列化和反序列化操作
序列化和反序列化
1.序列化就是在保存数据时,保存数据的值和数据类型
2.反序列化就是在恢复数据时,恢复数据的值和数据类型
3.需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该
类必须实现如下两个接口之一:
Serializable 这是一个标记接口(通常都是实现这个)
Externalizable
对象处理流注意事项和细节说明
1)读写顺序要一致(怎么写进去的就按那个顺序去序列化读取)
2)要求序列化或反序列化对象,需要实现Serializable(基本类型会自动装箱(基本类型封装类已经实现了Serializable接口),对象的话需要实现Serializable接口)
3)序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性(private static final long serialVersionUID = 1L;)
4)序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
5)序列化对象时,要求里面属性的类型也需要实现序列化接口
6)序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
使用案例:
@Test
public void objectInputStream() throws IOException, ClassNotFoundException {
String filePath ="e:\\data.txt";
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filePath));
//读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致否则会出现异常
System.out.println(objectInputStream.readInt());
System.out.println(objectInputStream.readBoolean());
System.out.println(objectInputStream.readChar());
System.out.println(objectInputStream.readDouble());
System.out.println(objectInputStream.readUTF());
//dog的编译类型是 0bject , dog的运行类型是 Dog
Object dog = objectInputStream.readObject();
System.out.println("运行类型="+dog.getClass());
System.out.println("dog信息=" + dog);
//这里是特别重要的细节:
//1.如果我们希望调用Dog的方法,需要向下转型
//2.需要我们将Dog类的定义,拷贝到可以引用的位置(!!!因为序列化的时候会把具体包名什么都写进去,可以把Dog类做成一个public类)
Dog dog2 = (Dog) dog;
System.out.println(dog2.getName());
objectInputStream.close();
}
public class Dog implements Serializable {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
OutputStream常用子类(字节输出流)
1.FileOutputStream:
文件输入流,按字节方式
示例代码:
@Test
public void fileOutputStream() throws IOException {
String path = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(path,true); //这个构造器后面加个true,表示续写而不是覆盖文件原有内容
fileOutputStream.write('s'); //第一种方式,写入一个字节
String str = "asdf,sdffff";
fileOutputStream.write(str.getBytes()); //第二种方式,把一个字符串装换成字节数组输入 str.getBytes()-->可以把字符串变成字节数组
// fileOutputStream.write(byte[] b,int 0,int len); //第三种方式,可以从自定位置输入自定字节数 off表示从下标几,len表示写入前几个字节
} catch (IOException e) {
e.printStackTrace();
}finally {
fileOutputStream.close();
}
}
文件的拷贝(普通拷贝)
@Test
public void copyfile(){
String InputFath = "f:\\cjy.png";
String OutputFath = "f:\\cjy2.png";
FileInputStream fileInputStream =null;
FileOutputStream fileOutputStream =null;
int readLen = 0; //存一次读取了几个字节(因为最后一次可能没有读完,或者说没有读到数据会返回-1)
byte[] data = new byte[2]; //设置一个读取几个字节
try {
fileInputStream = new FileInputStream(InputFath);
fileOutputStream = new FileOutputStream(OutputFath);
while((readLen=fileInputStream.read(data))!=-1){
fileOutputStream.write(data,0,readLen);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
fileOutputStream.close();
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.BufferedOutputStream:
使用案例:
@Test
public void bufferedOutputStream() throws IOException {
String path = "f:\\s.txt";
BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream(path));
bo.write("这是一个字符串===".getBytes());
bo.close(); //这里底层会去实现FileReader.close()方法
}
3.ObjectOutputStream:
使用案例:
@Test
public void objectOutputStream() throws IOException {
String filePath ="e:\\data.txt"; //序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeInt(100); //int(自动装箱)--> Integer(实现了 Serializable)
oos.writeBoolean(true); // boolean -> Boolean(实现了Serializable)
oos.writeChar( 'a'); //Char --> Character(实现了Serializable)
oos.writeDouble(9.5); // double -> Double(实现了Serializable)
oos.writeUTF("韩顺平教育"); //String -->实现了Serializable
//保存一个dog对象
oos.writeObject(new Dog("asd",10)); //对象的类需要实现序列化接口
oos.close();
System.out.println("数据保存成功");
}
public class Dog implements Serializable {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Reader常用子类(字符输入流)
1.FileReader:
一次只读取一个字符或字符数组,效率很低
使用案例:
@Test
public void fileReader() throws IOException {
String path = "f:\\s.txt";
int readChar = 0;
char[] buf = new char[8];
FileReader fileReader = new FileReader(path);
while ((readChar=fileReader.read(buf))!=-1){
System.out.print(new String(buf,0,readChar));
}
fileReader.close();
}
2.BufferedReader:
文本文档之类的用这个,效率高!
使用案例:
@Test
public void bufferedReader() throws IOException {
String path = "f:\\s.txt";
String readLine = null;
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
while ((readLine=bufferedReader.readLine())!=null){
//readLine()是按行读取,读不到的话返回null
System.out.println(readLine);
}
bufferedReader.close(); //这里底层会去实现FileReader.close()方法
}
3.InputStreamReader(转换流)
Reader子类
转换流的必要性:
文件的读取默认是UTF-8编码的,如果读取的文件不是UTF-8编码的,那读取出来的就会是乱码,
而装换流可以把字节流装换成字符流并设置指定的读取编码(InputStreamReader 构造器自带的)
其实也相当于给字节流又包装了一层
使用案例:
@Test
public void InputStreamReader() throws IOException {
String filePath = "e:\\a.txt";
//指定gbk(看文件是什么编码的就用什么编码方式)的编码方式把FileInputStream转换成InputStreamReader
InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
BufferedReader br = new BufferedReader(isr); //读取还是用缓冲流读取效率高
String reader = null;
while ((reader = br.readLine())!=null){
System.out.println(reader);
}
br.close();
}
Writer常用子类(字符输出流)
1.FileWriter:
使用案例:
@Test
public void fileWriter() throws IOException {
String path = "f:\\s.txt";
char[] chars = {
'a','b','c'};
FileWriter fw = new FileWriter(path);
fw.write('d'); //写入一个字符
fw.write(chars); //写入一个字符数组
fw.write("字符串转数组".toCharArray(),0,3); //写入前三个数据
fw.write("这是一个案例"); //写入整个字符串
System.out.println("写入文件成功!");
fw.close();
}
2.BufferedWriter:
使用案例:
@Test
public void bufferedWriter() throws IOException {
String path = "f:\\s2.txt";
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(path,true)); //追加的话是在节点流有这个属性,BufferedWriter构造器没有这个属性
String str = "sfsdfSDFDASFsdfasdf收待发放";
bufferedWriter.write(str);
bufferedWriter.newLine(); //插入一个换行符号
bufferedWriter.write(str);
bufferedWriter.close(); //这里底层会去实现FileReader.close()方法
}
文件的拷贝(缓冲流拷贝)
这种方式效率高,通常用这种
使用案例:
@Test
public void bufferedCopy() throws IOException {
String Readerpath = "f:\\s.txt";
String Writerpath = "f:\\s2.txt";
String readLine = null;
BufferedReader bufferedReader = new BufferedReader(new FileReader(Readerpath));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(Writerpath));
while ((readLine=bufferedReader.readLine())!=null){
bufferedWriter.write(readLine);
bufferedWriter.newLine();
}
if (bufferedReader!=null){
bufferedReader.close();
}
if (bufferedWriter!=null){
bufferedWriter.close();
}
bufferedWriter.close();
}
3.OutputStreamWriter:(转换流)
Writer子类
使用案例:
@Test
public void outputStreamWriter() throws IOException{
String filePath = "e:\\a2.txt";
String charSet = "utf-8";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
osw.write("这是一个案例");
osw.close();
System.out.println("按照"+charSet+"写入文件成功");
}
五.标准输入输出流
- System.in 标准输入流 类型:InputStream 可以从键盘进行输入
- System.out 标准输出流 类型: OutpuStream 平常控制台输出的(默认输出位置)
使用案例:
Scanner scanner = new Scanner(System.in); //Scanner会一直等待控制台输入并回车才会执行接下去的程序(获取控制台数据)
System.out.println("输入内容");
String next = scanner.next();
System.out.print1n("next=" +next);
六.打印流
打印流只有输入流没有输出流
- PrintStream(字节流): OutputStream子类
使用案例:
@Test
public void printStream() throws IOException {
PrintStream out = System.out; //System.out是OutpuStream类型的输出流
//普通方式打印
out.print("demo===");
out.write("demo2".getBytes()); //因为print底层使用的是write ,所以我们可以直接调用write进行打印/输出,默认输出位置是控制台
out.close();
//我们可以去修改打印流输出的位置/设备//1.输出修改成到“e:\\f1.txt"
System.setOut(new PrintStream("e:\\fi.txt"));
System.out.println("hello,韩顺平教育"); //这样这个打印的位置就不再是控制台而是e:\\f1.txt里了
}
- PrintWriter (字符流): Writer子类
使用案例:
@Test
public void printWriter() throws IOException {
PrintWriter printWriter = new PrintWriter(System.out); //控制台输出
PrintWriter printWriter2 = new PrintWriter(new FileWriter("e:\\f2.txt")); //重定向输出位置到某个文件
printWriter.print("哈哈"); //控制台输出
printWriter2.print("哈哈2"); //指定文件输出
printWriter.close();
printWriter2.close();
}
七.个人小结
流的用法都大同小异,主要是必须清楚什么时候使用什么流合适!还有就是数据读取完一定要关闭流,处理流的话关闭外层流就行,不关闭流的话资源消耗比较大。