I/O输入输出流
File类
java.io.file
表示电脑上的文件或者目录
- 无论是文件还是目录都通过File类表示
- 提供了对文件和目录的基本操作,如查看文件名,文件大小,新建或删除文件
- File不能访问文件的内容,需要I/O输入输出流访问文件的内容
构造方法
指定文件路径
File file = new File(path);
创建File文件时只是创建一个指向某个路径的对象,并不表示在硬盘中创建文件,这个路径指向的文件可以存在,也可以不存在。
- 绝对路径
以根路径开始的路径
关于路径分隔符(由于我的系统是Linux系统,故只说明Linux系统下的情况)
例:/home/Desktop/a.txt - 相对路径
不是以根开始的路径,相对于某个路径的路径,.表示当前目录,…表示上一级目录。相对路径的父目录默认的是项目根目录
指定父目录路径和文件名
File file = new File(father path, filepath);
指定父目录的对象和文件名
File file = new File(File fatherfile, filepath);
指定URI统一资源标识符
URL需要装换成URI使用
常用方法
访问文件属性方法
方法 | 作用 |
---|---|
getParent() | 获取指定了文件的父目录 |
length() | 文件大小,单位是字节 |
lastModified() | 返回long时间戳 |
canWrite() | 文件可读 |
isFile() | 是否是普通文件 |
isDirectory() | 是否是文件目录 |
操作方法
方法 | 作用 |
---|---|
createNewFile() | 成功创建文件返回true,失败返回false |
renameTo() | 文件重命名 |
delete() | 返回是否删除成功 |
mkdir() | 若父目录不存在会创建失败 |
list() | 返回文件目录下所有文件和目录的名字,返回字符串数组 |
File.separator | 返回操作系统分隔符 |
文件过滤器
需要用到文件过滤的过滤器接口,重写过滤方法。例程如下:
public static void main(String[] args) {
File file1 = new File("/data/JAVAProjects/Wangdao2022LF/File/src/com/file/test");
//String[] list = file1.list();
String[] list = file1.list(new FilenameFilter() {
@Override
public boolean accept(File file, String name) {
return name.endsWith("png") || name.endsWith("gif") || name.endsWith("jpg");
}
});
System.out.println(Arrays.toString(list));
}
递归读取文件
在一般情况下,文件的路径有可能是一般文件,也有可能是文件目录,这时可以使用递归的思想,获取文件目录及其子目录下的所有文件。
代码实现如下:
public static void getFiles(File filePath){
if(filePath.exists()){
if(filePath.isFile()){
System.out.println(filePath.getAbsolutePath());//如果目标文件是一
//般文件,直接输出文件绝对路径
}
else{
System.out.println(filePath.getAbsolutePath());//目标文件是目录
//文件,直接输出目录绝对路径
File[] files = filePath.listFiles();
for(File f : files){
if(f.isFile())
System.out.println(f.getAbsolutePath());
else
getFiles(f);//此处使用递归,当子文件中含有目录的时候再次调
//用该函数,将子目录当成新的filePath参数,实现递归
}
}
}
}
清理空文件夹
public static void removeEmptyDirectory(File file){
if(file.isDirectory()&& Objects.requireNonNull(file.listFiles()).length==0){
file.delete();
System.out.println("删除空目录" + file.getAbsolutePath());
removeEmptyDirectory(file.getParentFile());
}
else if (file.isDirectory()){
File[] files = file.listFiles();
for(File f : files != null ? files : new File[0]){
removeEmptyDirectory(f);
}
}
}
IO流
可以理解成一组有顺序的、有起点有中点的动态数据集合
文件是数据在硬盘上的静态存储
流是传输时的动态形态
文件分类
- 文本文件
可以用记事本打开的文件,由字符组成 - 二进制文件
除文本以外的文件都是二进制文件
流的分类
- 流的方向:
输入流:由InputStream,Reader作为父类
输出流:由OutputStream,Writer作为父类 - 按流中数据的单位
字节流byte:由InputStream,OutputStream作为父类
字符流char:由Reader,Writer作为父类 - 按数据的来源
节点流:直接对数据进行操作
包装流:对一个节点流进行操作
字节流
InputStream
方法 | 作用 |
---|---|
FileInputStream | 以字节为单位,读取数据,返回一个int类型的值 |
ByteArrayInputStream | 从字节中读取数据,将字节数组当做流输入的来源 |
ObjectInputStream | 反序列化,通过从磁盘或网络获取的二进制文件中读取对象 |
FileInputStream
//JDK7中提供了一种新语法,叫try-with-source,能够自动关闭外部资源,无需手动关闭
//此处只能创建对象,而且只能创建那些有实现了AutoClosable接口的对象
try(FileInputStream fis = new FileInputStream("test.txt")){
byte[] buffer = new byte[3];//buffer数组储存在文件缓冲区,而且不是实际内存而是JVM
int num = fis.read(buffer);//减少对硬盘的读取次数,返回值是实际读取的字节数,如果读取到末尾返回的是-1
}
catch (IOException e){
e.printStackTrace();
}
ByteArrayInputStream
字节数组输入输出流操作的是内存,不是外部设备因此无需关闭流
public static void byteArrayInputStream(){
byte[] bytes = "hello world".getBytes();
try{
InputStream is = new ByteArrayInputStream(bytes);
int i = -1;
while((i = is.read())!= -1){
System.out.println((char)i);
}
} catch (IOException e) {
e.printStackTrace();
}
}
OutputStream
方法 | 作用 |
---|---|
FileOutputStream | 以字节为单位,将文件写出到文件中 |
ByteArrayOutputStream | 将数据写出到内置的字节数组中,即将字节数组当做输出的目的地 |
ObjectOutputStream | 用于序列化,将对象写入二进制文件,以便通过磁盘或者网络传输 |
FileOutputStream
public static void main(String[] args) {
FileOutputStream fos = null;
try{
//如果文件不存在,会自动创建文件;如果文件存在,则会覆盖重写,但是在构造时,能够使用重载的构造方法追加写入数据
//fos = new FileOutputStream("test.txt");
fos = new FileOutputStream("test.txt", true);
byte[] data = "hello world".getBytes();
fos.write(data);//只是把数据写入到缓冲区中
fos.flush();//将缓冲区的数据刷新,写入到文件中,但是当关闭输出流时,会自动调用该方法
}catch (IOException e){
e.printStackTrace();
}
}
ByteArrayOutputStream
public static void byteArrayOutputStream(){
OutputStream os = new ByteArrayOutputStream();
try {
os.write("hello,world".getBytes());
os.flush();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(os);
}
文件复制
- 每次复制一个字节,因为涉及到多次IO操作,传输时间更长
int data = -1;
while((data = is.read()) != -1){
os.write(data);
}
//488ms
//文件复制成功!
再看看多字节,我们一次复制1MB大小的字节数组
byte[] buffer = new byte[1024 * 1024];
int num = -1;
while((num = is.read(buffer))!=-1){
os.write(buffer, 0, num);
}
//1ms
//文件复制成功!
时间就相差很大了。
序列化和反序列化
序列化:将Java对象写入到IO流中,实现将对象保存在磁盘上或者在网络中传递对象
反序列化:从IO流中读取Java对象,实现从磁盘和网络上恢复对象
要求:
对象必须实现Serializable接口才能序列化,转换成二进制流,通过网络进行传输
ObjectOutputStream 序列化操作
ObjectOutputStream和ObjectInputStream都属于包装流
- 用于对节点流功能进行扩展
- 创建包装流时,需要传入操作的节点流对象
- 关闭流时,只需要关闭包装流,节点流也会被关闭
ObjectOutputStream用于序列化,将对象写入二进制文件:
User user = new User(1001, "tom", 18);
try {
FileOutputStream fos = new FileOutputStream("user.data");//用来包装的流
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
}
ObjectInputStream 反序列化操作
注意事项:
- 对象必须实现
Serializable
接口,才能被序列化转换成二进制流通过网络传输;如果成员变量有对象属性,则对象属性也要序列化 - 通过serialVersionUID用来判断序列化版本的一致性(反序列化时将流中携带的UID和本地相应实体的UID是否一致,才能进行反序列化)
- transient和static修饰的成员不会被序列化
缓冲字节流
字符流
Reader和Writer
文件输入输出流
/字符流不能操作二进制文件,只能操作文本文件
try (FileWriter writer = new FileWriter("noun.txt");
FileReader reader = new FileReader("verb.txt")) {
char[] buffer = new char[5];
int num = -1;//每次读取一个字符,返回int类型的值,可以读汉字,不会出现乱码
while ((num = reader.read(buffer)) != -1)
writer.write(buffer, 0, num);
System.out.println("文件复制成功");
} catch (IOException e) {
e.printStackTrace();
}
缓冲输入输出流
缓冲输入输出流属于包装流,为字符添加缓冲的功能,当读取或者写出数据时,先放到缓冲区,再到缓冲区读取。
BufferedReader&BufferedWriter
try(
BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("verb.txt"))
)
{
String data;//每次读一行,读不到数据返回null
while((data = reader.readLine()) != null)
writer.write(data);
System.out.println("复制成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
out&in
System.out :标准输入输出流,默认为显示器
System.in :标准输入流,默认为键盘
转换流
只能由字节流转换成字符流,同时可以实现编码的转换
PrintWriter
打印流,可以直接打印输出;’可以只以文件名作为参数
随机读写流
随机读写流是一个字节流,可以定义到任意的文件位置进行读写操作,使用该流既能读取文件又能写入文件。
System.out.println(raf.getFilePointer());
raf.seek(3);//使指针移动到指定位置
raf.skipBytes(2);//指针往后跳过多少个字节,不能反着走
System.out.println((char)raf.read());
文件加密
思想:将文件里的字节与int类型密码异或得到加密后的字符存到密码,然后对密码文件的每个字节和密码进行异或即可还原原来数据。
public static void secret(String filePath, int password) {
String newFilePath, notice;
if(filePath.endsWith(".sec"))
{
newFilePath = filePath.substring(0, filePath.length() - 4);
notice = "解密完成!";
}
else {
newFilePath = filePath + ".sec";
notice = "加密完成!";
}
try(
FileInputStream fis = new FileInputStream(filePath);
FileOutputStream fos = new FileOutputStream(newFilePath);
) {
int data = -1;
while((data = fis.read()) != -1){
fos.write(data ^ password);
}
System.out.println(notice);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
secret("test.txt.sec", 9527);
}
加密后,由于生成的是二进制文件,显示出来是乱码:
߈�ф�ѯ�ߵ�߬�ߦ�ޏ�
解密后显示正常:
这波是肉蛋葱鸡
I/O部分到这里就结束了~😄