IO
IO流的类型
大概来说,对应IO流来说可以分为两大类,字节流和字符流。其中又可以分为:字节缓冲流,字符缓冲流。
按流向分可以分为输入流和输出流;
输入流:以抽象类InputStream和Reader为基类;
输出流:以抽象类OutputStream和Writer为基类。
那么我们要如何使用IO流来实现文件的读写呢!
首先我们要明确工作流程:
读文件:
- 获得文件路径Path,
- 利用Path创建文件对象
- 使用文件对象创建输入流
- 读取输入流中的数据
- 关闭输入流
写文件:
- 获得文件要写入的目标路径Path
- 判断要写入的文件是否存在,以及是要覆盖写还是在文件末尾添加
- 创建输出流,将内存中的数据放入输入流中
- 将输入流中的数据写入文件
- 关闭输出流
以上就是整个输入输出的流程。
接下来就是要明确要使用什么样的输入流从硬盘中读取文件数据,有以下这些流可以选取
字节流可以选用
抽象类:InputStream
- FileInputStream
- BufferedInputStream
- DataInputStream
- ObjectInputStream
字符流可以选用
抽象类:Reader
- InputStreamReader
- FileReader
- BufferedReader
不管是什么类型的输入流,想要输出到硬盘中直接选用相对应的输出流既可,如果是想要將程序中处理结果输出到硬盘中,那么就什么输出流都可以选用。
注意:
-
除非遇到对象的序列化:使用对象序列化和反序列化时,那个对象所对应的类就必须实现Serializable接口。输入输出流则必须使用 ObjectInputStream(反序列化) 和 ObjectOutputStream(序列化)。
-
关于使用IO流后出现字符乱码现象,我们可以事先规定要使用的编码方式(encoding),来防止输出文件乱码,和输入文件读取乱码
-
关于文件的路径,由于不同操作系统对路径分隔符和文件分隔符使用不同的形式:
windows:路径分隔符 ; 文件分隔符 \ Linux: 路径分隔符 : 文件分隔符 /
所以可以使用Java提供的File类中专门用来表示路径分隔符 和 文件分隔符的变量来避免,由于不同操作系统而造成的读写错误
路径分隔符:File.pathSeparator
文件分隔符:File.separator
关于文件的绝对路径和相对路径:绝对路径从盘符开始写,
相对路径:从当前项目的根目录开始写,当使用的文件在当前项目的根目录时就可以使用相对路径来简写:直接写文件名称。关于项目的根目录CLASSPATH在以后再完整细述,这边不在展开。
-
在java中我们可以使用java.io包下的File类对计算机中的文件和目录进行以下操作
- 创建文件;createNewFile()
- 删除文件或目录;delete()
- 创建目录;mkdir()和mkdirs()
- 详细内容会在File类中详述。
那么接下来就是利用这些输入输出流对文件进行操作的演示
字节流(Stream)
字节输入流
首先,对于入流来说:就是将一个文件从磁盘读到内存中,关于读文件一般都是依靠FileInputStream,或者FileReader;前者比较适合读取图像文件,后者比较适合读取字符文件。
父
InputStream:这是一个抽象类,其他是输入流都继承自该类
子
FileInputStream:主要用于读取原始字节的图像数据流,
创建方式:File对象 或者 一个文件路径Path来创建
//使用File对象来创建
File file = new File("filePath");
InputStream fis = new FileInputStream(file);
//使用文件路径创建
InputStream fis = new FileInputStream("filePath");
BufferedInputStream:字节缓冲输入流,可以利用在内存中构建一个缓冲区,来提高对文件的读取速度
创建方式:创建时只能传入一个 InputStream 来使用(一般为:FileInputStream)
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("filePath"));
DataInputStream:从底层输入流中读取原始的java数据类型。可以直接将 数据输入流中读取的数据,放入DataOutputStream中。
创建方式:创建时只能传入一个InputStream 来创建
DataInputStream dis = new DataInputStream(new FileInputStream("filePath"));
ObjectInputStream:对象的反序列化流,将一个实现Serializable接口的对象从文件中读取出来
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("filePath"));
以上就是基本可以用到到的字节输入流了,从创建方式可以看出来,要将一个文件从磁盘中读取到内存中,必须要用到FileInputStream。
字节输出流
输出流就是将内存中的数据,写入磁盘文件中,一般来说也是依靠FileOutputStream或者FileWriter。
对于输出流来说创建方法和输入流基本一致,所以就不赘述了,直接上代码
父
OutputStream:所有输出流的父类
子
FileOutputStream
//使用filePath来创建
FileOutputStream fos = new FileOutputStream("filePath");
//直接使用file对象来创建
File file = new File("Path");
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream
BufferedOutputStream bos = new BufferedOutputStream(OutputStream os);
DataOutputStream
DataOutputStream dos = new DataOutputStream(OutputStream os);
ObjectOutputStream
ObjectOutputStream oos = new ObjectOutputStream(OutputStream os);
字符流(Reader&Writer)
字符流相对于字节流,只是用来读写字符文件的。在读写文件需要对内容进行处理,比如比较特定的字符,处理某一行数据时会使用字符流。
字符输入流
父
Reader
子
InputStreamReader:将字节输入流转为字符输入流,是字节流通向字符流的桥梁。
即在创建时传入一个字节输入流,按照 encoding 将其转为字符输入流
InputStreamReader isr = new InputStreamReader(inputStream is,"encoding");
FileReader:文件字符输入流,是InputStreamReader的子类
创建方法:使用文件对象 或者 文件路径直接创建
//使用文件对象进行创建
File file = new File("filePath");
FileReader fr = new FileReader(file);
//使用文件路径创建
FileReader fr = new FileReader("filePath");
BufferedReader :字符换缓冲输入流,提供了readLine方法,可以逐行读取字符
创建方法:传入一个字符输入流
BufferedReader rr = new BufferedReader(Reader r);
//指定缓冲区大小
BufferedReader rr = new BufferedReader(Reader r,int size);
字符输出流
字符输出流就是将内存中的字符串按照字符输出至磁盘文件中
父
Writer
子
OutputStreamWriter:将字节输出流转换为字符输出流,是字符流通向字节流的桥梁,可使用指定的 encoding 要写入流中的字符编码成字节
OutputStreamWriter osw = new OutputStreamWriter(OutputStream os);
//指定encoding
OutputStreamWriter osw = new OutputStreamWriter(OutputStream os,"enconding");
FileWriter:文件字符输出流,是OutputStreamWriter的子类
创建方法:使用文件对象创建 或者 文件路径创建
//文件对象创建
File file = new File("filePath");
FileWriter fw = new FileWriter(file);
//文件路径进行创建
FileWriter fw = new FileWriter("filePath");
BufferedWriter:字符缓冲输出流
创建方法:传入一个字符输出流对象创建
BufferedWriter bw = new BufferedWriter(Reader r);
实际开发使用
以上我们说了创建各种输入输出流的方法和要注意的事项,这也是使用IO流的基础知识;但是要真正去使用它,还是需要到具体的开发环境中去讨论。
具体开发环境
对于具体开发环境来说,我们使用IO流需要注意一下几个点
- 要读入的文件是否存在,要输出的文件目录和文件是否存在;如果存在是要覆盖写,还是在文件末位添加。
- 文件的所在的位置——决定是否是使用相对路径还是绝对路径
- IO流的释放
- 保证数据完全读入,数据完全写入
- 线程安全
编程案例
- 将一个文件从计算机的文件系统中加载进内存并将该文件保存到另一个文件夹中。
- 将一串字符串保存到一个文件中;
- 将一个对象保存到文件中,再从文件中加载使用;
以下是代码演示:
案例一
package com.tool.io_demo;
import java.io.*;
/**
* 用于测试IO功能的入口
* 因为涉及对计算机文件系统的操作,所以不管是输入流还是输出流都需要在创建时检测文件是否存在
*
*
* @auther Stiles-JKY
* @date 2020/3/8-21:47
*/
public class Application {
//声明输入输出流
private static InputStream inputStream = null;
private static OutputStream outputStream = null;
//主方法
public static void main(String[] args) {
//将一个文件从计算机的文件系统中加载进内存并将该文件保存到另一个文件夹中,使用字符流和字节流的方式;
String sourcePath = "D:\\IOtestS\\jky.txt";//源文件路径
String targetPath = "D:\\IOtestT\\jky2.txt";//目标文件路径
File sFile = new File(sourcePath);//获取文件对象
File tFile = new File(targetPath);//目标文件
//判断源文件是否存在
if (!sFile.exists()) {
//如果不存在就停止程序,并打印源文件不存在
System.out.println("源文件不存在");
System.exit(0);
}
//判断目标文件是否存在
if (!tFile.exists()) {
//如果目标文件不存在则创建目标文件
tFile.getParentFile().mkdirs();//创建目录
try {
tFile.createNewFile();
System.out.println("没有文件,创建了一个空文件");
} catch (IOException e) {
e.printStackTrace();
}
}
//创建输入输出流
try {
inputStream = new FileInputStream(sFile);
outputStream = new FileOutputStream(tFile);
//读取输入流中的数据,并写入输出流
byte[] bytes = new byte[inputStream.available()];
int len = 0;
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes,0,len);
}
//将输出流中的数据刷入磁盘文件
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭流
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
案例二
package com.tool.io_demo2;
import java.io.*;
import java.util.Scanner;
/**
* @auther Stiles-JKY
* @date 2020/3/8-22:37
*/
public class Application {
//声明字符输出流
private static BufferedWriter Bwriter = null;
public static void main(String[] args) {
//当前编程环境是utf-8编码,从控制台输入一句话,写入 charIO.txt 文件中
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要写入文件中的字符串");
String str = scanner.next();
//文件路径
String tPath = "D:\\IOtestT\\charIO.txt";
//创建文件
File tFile = new File(tPath);
//判断目标路径中文件是否存在,不存在就创建
if (!tFile.exists()) {
tFile.getParentFile().mkdirs();//创建文件多级目录
try {
tFile.createNewFile();
System.out.println("文件不存在,自动创建了一个空文件");
} catch (IOException e) {
e.printStackTrace();
}
}
try {
//创建输出流,使用BufferedWriter,这边的true就是为了实现续写
Bwriter = new BufferedWriter(new FileWriter(tFile,true),1024);
//将输出输出写入输出流
Bwriter.write(str);
Bwriter.newLine();
Bwriter.flush();//写入磁盘文件
} catch (Exception e) {
e.printStackTrace();
} finally {
if (Bwriter != null) {
try {
Bwriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
案例三
package com.tool.io_demo3;
import java.io.*;
/**
* 实现对象序列化
* @auther Stiles-JKY
* @date 2020/3/8-23:08
*/
public class Application {
//声明输入流
private static ObjectInputStream ois = null;
//声明输出流
private static ObjectOutputStream oos = null;
public static void main(String[] args) {
//先要将Car输出到文件中(序列化),作为下一次反序列化的源文件
String sPath = "D:\\IOtestT\\Car.txt";
//创建文件对象,并检查目标文件是否存在
File sFile = new File(sPath);
if (!sFile.exists()) {
sFile.getParentFile().mkdirs();
try {
sFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//创建输出流
try {
oos = new ObjectOutputStream(new FileOutputStream(sFile));
//写入对象输出流
oos.writeObject(new Car("大叔,我开的AE86"));
//刷入文件
oos.flush();
Car car = new Car("思域");
car.drivor1();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("执行了反序列化");
answer();
}
public static void answer() {
String tPath = "D:\\IOtestT\\Car.txt";
//创建文件对象,并检查目标文件是否存在
File sFile = new File(tPath);
if (!sFile.exists()) {
System.out.println("被弯道超车了,找不见车了");
System.exit(0);
}
//实现反序列化
try {
ois = new ObjectInputStream(new FileInputStream(sFile));
Car car = (Car)ois.readObject();
car.drivor2();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package com.tool.io_demo3;
import java.io.Serializable;
/**
* 要被序列化的对象
* @auther Stiles-JKY
* @date 2020/3/8-23:08
*/
public class Car implements Serializable {
private String carName = null;
public Car(String carName) {
this.carName = carName;
}
public void drivor1() {
System.out.println("我都开"+ this.carName +"了,还比我快,小伙子你开的什么车这么快?");
}
public void drivor2() {
System.out.println(carName);
}
}
运行结果
我都开思域了,还比我快,小伙子你开的什么车这么快?
执行了反序列化
大叔,我开的AE86