史上第二全的 java 文件操作和数据读写

用户头像
诸葛小猿
关注
发布于: 2020 年 09 月 02 日
史上第二全的java文件操作和数据读写

通过本文,可以详细的了解JDK关于目录和文件API操作。关注公众号,输入“java-summary”即可获得源码。



一、java.io.File

java.io.File类用于描述文件系统中的一个文件或目录

该类可以:



  • 1、访问文件或目录的属性信息



  • 2、访问一个目录中的所有子项



  • 3、操作文件或目录(创建、删除)



该不可以:



  • 访问文件的具体内容



开发时注意不同操作系统的路径的表示方法的差异(如'/'或'\\'),使用File.separator可以屏蔽不同操作系统的路径的差异。



1.读取文件属性



package com.wuxiaolong.file.directory;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Description:
* java.io.File
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FileDemo1 {
public static void main(String[] args) {
/*
* 尽量不写绝对路径。
* 常用的是相对路径:
* 1、相对于项目目录(当前目录)
* 2、相对于类加载目录(实际开发更常用)
*/
File file = new File("." + File.separator + "test.txt"); //分隔符
System.out.println(file);
/*
* 获取当前文件的属性信息
*/
//获取文件名(不包括文件路径)
String name = file.getName();
System.out.println("文件名:"+name);
//获取文件大小(字节)
long length = file.length();
System.out.println("文件大小(字节):"+length);
//最后修改时间(毫秒数)
long time = file.lastModified();
System.out.println(time);
Date date = new Date();
date.setTime(time);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
String str = sdf.format(date);
System.out.println(str);
/*
* 可读、可写、可执行、隐藏
*/
file.canRead();
file.canWrite();
file.canExecute();
file.isHidden();
System.out.println(file.getPath());
}
}



2.创建文件



package com.wuxiaolong.file.directory;
import java.io.File;
import java.io.IOException;
/**
* Description:
* 使用File创建文件
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FileDemo2 {
public static void main(String[] args) throws IOException {
/*
* 在当前目录下创建demo.txt文件 ;不能再没有的目录里创建文件,父目录不存在会报错
* 不写“./”就是默认在当前目录
*/
File file = new File("." + File.separator + "demo.txt");
/*
* 判断file表示的文件或目录是否存在
*/
if(! file.exists()){
boolean flag = file.createNewFile(); //没有判断也不会覆盖
System.out.println("创建成功" + flag);
}else{
System.out.println("创建失败");
}
}
}



3.删除文件



package com.wuxiaolong.file.directory;
import java.io.File;
/**
* Description:
* 删除文件
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FileDemo3 {
public static void main(String[] args) {
/*
* 将当前目录中的demo.txt删除; 不会放入回收站
*/
File file = new File("demo.txt");
if(file.exists()){
file.delete();
System.out.println("已删除");
}else{
System.out.println("文件不存在");
}
}
}



4.创建一个目录



package com.wuxiaolong.file.directory;
import java.io.File;
/**
* Description:
* 创建一个目录
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FileDemo4 {
public static void main(String[] args) {
/*
* 在当前目录下创建一级目录demo
*/
File file = new File("demo");
if(! file.exists()){
file.mkdir(); //mkdir只能创建一级目录,不能创建多级目录,也不报错
System.err.println("创建成功");
}else{
System.out.println("目录已经存在");
}
}
}



5.创建多级目录



package com.wuxiaolong.file.directory;
import java.io.File;
/**
* Description:
* 创建多级目录
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FileDemo5 {
public static void main(String[] args) {
/*
* 创建多级目录a/b/c/d/e
*/
File file = new File("a" + File.separator +"b" + File.separator +"c" + File.separator +"d" + File.separator +"e" );
if(! file.exists()){
file.mkdirs();
System.out.println("创建成功");
}else{
System.out.println("目录已存在");
}
}
}



6.删除目录



package com.wuxiaolong.file.directory;
import java.io.File;
/**
* Description:
* 删除目录
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FileDemo6 {
public static void main(String[] args) {
File dir = new File("demo");
if(dir.exists()){
dir.delete(); //只能删空目录,非空目录删不掉,也不报错
System.out.println("已删除");
}else{
System.out.println("不存在");
}
}
}



7.获取一个目录的所有子项



package com.wuxiaolong.file.directory;
import java.io.File;
/**
* Description:
* 获取一个目录的所有子项
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FileDemo7 {
public static void main(String[] args) {
/*
* 获取当前目录中的所有内容
*/
File file = new File(".");
/*
* boolean isDirectory()
* 判断是不是目录
*/
if(file.isDirectory()){
/*
* File[] listFiles()
* 查看当前File表示的目录中的所有子项
* 每个子项以一个File对象表示,所有子项存入一个File对象数组,并返回
*/
File[] sub = file.listFiles();
for(File f : sub){
System.out.println(f.getName());
}
}
}
}



8.过滤一个目录中的部分子项



package com.wuxiaolong.file.directory;
import java.io.File;
import java.io.FileFilter;
/**
* Description:
* 获取一个目录中的部分子项
* File支持一个重载的listFile方法,要求传入一个文件过滤器FileFilter(是接口),这样只会返回该目录中满足该过滤器要求的子项
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FileDemo8 {
public static void main(String[] args) {
/*
* 仅仅获取文件
*/
File dir = new File("." );
System.out.println(dir);
File[] sub = dir.listFiles(
new FileFilter(){//内部类
@Override
public boolean accept(File file){
System.out.println("正在过滤:" +file.getName());
//return file1.isFile(); //获取文件,不要目录
//return file1.getName().endsWith(".pom"); //获取以".xml"结尾的文件
return file.getName().startsWith("."); //获取以"."开头的文件
}
}
);
for(File tmp : sub){
System.out.println(tmp.getName());
}
}
}



二、java.io.RandomAccessFile

java.io.RandomAccessFile类用于读写文件数据。其基于指针对文件进行读写。由于是基于指针,一个字节一个字节的读写,效率较低,一般用的少。



创建RandomAccessFile有两种方式:



  • 1:“r”,即只读模式,仅仅对文件数据进行读取

  • 2:”rw“,即读写模式,对文件数据可以编辑。



1.一个字节一个字节写文件



package com.wuxiaolong.file.directory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Description:
* java.io.RandomAccessFile
* 用于读写文件数据。一个字节一个字节写文件
* @author 诸葛小猿
* @date 2020-08-30
*/
public class RandomAccessFileDemo1 {
public static void main(String[] args) throws IOException {
/*
* RandomAccessFile(File f, String mode)
* RandomAccessFile(String path, String mode)
* 其中mode是操作模式: r rw
*/
RandomAccessFile raf = new RandomAccessFile("raf.dat", "rw");
/*
* void write(int d)
* 写出1个字节,写出的是该整数d对应的2进制中的低八位(一个字节8个位)
* 00000001
*/
int a=1;
raf.write(a); //硬盘中存的是二进制 如果文件已经存在,在次运行时 内容从头开始覆盖,后面的不覆盖
a=98;
raf.write(a);
a=99;
raf.write(a);
System.out.println("写入硬盘完毕");
/*
* 读写完毕后,关闭;防止内存泄漏
*/
raf.close();
}
}



2.一个字节一个字节读文件



package com.wuxiaolong.file.directory;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Description:
* 一个字节一个字节读文件
* @author 诸葛小猿
* @date 2020-08-30
*/
public class RandomAccessFileDemo2 {
public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile("raf.dat","r");
/*
* int read()
* 从文件中指针当前的位置读取该字节,并以10进制的数字形式返回
* 若返回值为-1,表示读到了文件的末尾
*/
int d = raf.read(); //一次只能读一个字节
System.out.println(d); //1
d = raf.read();
System.out.println(d);//98
d = raf.read();
System.out.println(d);//99
d = raf.read();
System.out.println(d);//-1 存的是-1,返回的是255 存256,返回0 存的是97,98,99,记事本打开abc
raf.close();
}
}



3.一个字节一个字节复制文件



package com.wuxiaolong.file.directory;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Description: 复制文件:一次一个字节,慢
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class CopyDemo1 {
public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile("abc.exe","r"); //在硬盘上,一次只能读一个字节,所以慢,40M,三分钟
RandomAccessFile cp = new RandomAccessFile("abc.exe","rw");
long start = System.currentTimeMillis();
int d =-1; //在内存里
while((d=raf.read()) != -1 ){//cpu 判断 速度快
cp.write(d);
}
long end = System.currentTimeMillis();
System.out.println("复制完成,耗时:" + (end - start) + "ms");
}
}



4.自定义缓存复制文件



package com.wuxiaolong.file.directory;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Description: 若希望提高读写效率们需要提高每一次读写的数据量,从而减少读写次数,最终提高读写效率
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
RandomAccessFile src = new RandomAccessFile("firefox.exe","r");
RandomAccessFile aim = new RandomAccessFile("firefox_cp2.exe","rw");
int length = -1;//记录每次读取的实际字节量
byte[] buf = new byte[1024*10]; //一次读取10K
long start = System.currentTimeMillis();
/*
* int read(byte[] d)
* 一次性读取给定长度的字节量,并存入该字节数组中,返回值为实际读取到的字节量。
* 若返回值为-1,则表示读到了文件的末尾(EOF. end of file1)
*/
while((length=src.read(buf)) != -1){
/*
* void write(byte[] d)
* 将给定的字节数组一次性复制写入到文件中。
* 这种方式的缺点是:在文件的结尾,最后一次的复制,可能会使文件的大小改变。
*/
/*
* void write( byte[] d, int offset, int len)
* 将给定的字节数组中,从下标为offset处的字节开始连续len个字节一次性复制写到硬盘中。
*/
aim.write(buf,0,length);
}
long end = System.currentTimeMillis();
System.out.println("复制完成。耗时:" +(end - start) + "ms.");
}
}



5.读写基本类型数据



package com.wuxiaolong.file.file1;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Description:
* RandomAccessFile读写基本类型数据
*
* @author 诸葛小猿
* @date 2020-08-30
*/
public class RandomAccessFileDemo3 {
public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
raf.writeInt( Integer.MAX_VALUE);//查看源码 一次写四个字节 先无符号右移,在按与运算,掩码运行,截取低八位
raf.writeLong(Long.MAX_VALUE); //一次写八个字节
raf.writeDouble(123.456);//一次写八个字节
System.out.println("完成");
/*
* RAF总是在指针当前位置进行读写字节,并且无论进行了读还是写一个字节后,指针都会自动向后移动一个字节的位置。
* 默认创建出来RAF时,指针的位置为0,即:文件第一个字节的位置。
*/
long pos = raf.getFilePointer(); //获取文件指针 返回long
System.out.println("position:" + pos); //position:20(表示第21个字节所在的位置) 文件开头为0
raf.seek(0); //void seek( long pos) 将文件指针移到固定位置(开头)
int value1 = raf.readInt(); //读一个int,并返回
System.out.println(value1);
pos = raf.getFilePointer();
System.out.println("pos:" + pos);
raf.seek(4);
long value2 = raf.readLong();//读一个long,并返回
System.out.println(value2);
System.out.println(Long.MAX_VALUE);
pos = raf.getFilePointer();
System.out.println("pos:" + pos);
raf.seek(12);
double value3 = raf.readDouble();//读一个double, 返回
System.out.println(value3);
pos = raf.getFilePointer();
System.out.println("pos:" + pos);
}
}



6.读写字符串



package com.wuxiaolong.file.file1;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Description:
* 读写字符串
* @author 诸葛小猿
* @date 2020-08-30
*/
public class RandomAccessFileDemo4 {
public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
String str = "诸葛小猿"; //不同的编码,一个汉字占的字节数不一样
/*
* String提供了将当前字符串转化为字节的方法
* byte[] getBytes() 将当前字符串按默认字符集(using the platform's default charset)转换
* byte[] getBytes(String scn) 将当前字符串按指定的字符集转换,字符集不区分大小写
*/
byte[] arr = str.getBytes("utf-8");
raf.write(arr);
System.out.println("完成");
raf.seek(0);
byte[] buf = new byte[40];
int len = raf.read(buf);
/*
* 将给定字节数组中的指定范围内的字节按照给定的字符集转换为字符串。
*/
String s = new String(buf,0,len,"utf-8");
//String s = new String(buf,"GBK"); //字符串结尾有空格,
System.out.println(s);
raf.close();
}
}



三、java.io.FileInputStream和java.io.FileOutputStream



输入流都有一个抽象父类:java.io.InputStream,他是所有字节输入流的父类。FileInputStream是InputStream的子类,是文件字节输入流,是一个低级流(节点流),其使用方式和RandomAccessFile一致。InputStream及其子类只负责读文件,不负责写文件。



输出流都有一个抽象父类:java.io.OutputStream,他是所有字节输出流的父类。FileOutputStream是OutputStream的子类,是文件字节输出流,是一个低级流(节点流),其使用方式和RandomAccessFile一致。OutputStream及其子类只负责写文件,不负责读文件。



1.FileInputStream读文件



package com.wuxiaolong.file.file1;
import java.io.FileInputStream;
import java.io.IOException;
/**
* Description:
* java.io.FileInputStream
* 文件字节输入流,是一个低级流(节点流);
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FISDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("abc.txt");
byte[] data = new byte[100];
int len = fis.read(data);
String str = new String(data,0,len,"gbk"); //"gbk"可以不写,这是我的平台默认字符编码集
System.out.println(str);
fis.close();
}
}



2.FileOutputStream写文件



package com.wuxiaolong.file.file1;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Description:
* java.io.FileOutputStream
* 文件字节输出流,是一个低级流(数据源明确)(节点流)
* @author 诸葛小猿
* @date 2020-08-30
*/
public class FOSDemo1 {
public static void main(String[] args) throws IOException {
/*
* FileOutputStream fos = new FileOutputStream("abc.txt");
* 默认的构造方法是覆盖写操作,即:如果输出的文件已经存在,会将原文件中的内容清空,然后通过流,从头写入新数据
*/
/*
* 追加操作,该构造方法需要传入两个参数,后一个参数为boolean值,若该值为true,这追加写操作;该流写入到文件结尾
*/
FileOutputStream fos = new FileOutputStream("abc.txt",true);
fos.write("诸葛小猿。".getBytes());
System.out.println("完成");
fos.write("你好".getBytes());
//System.out.println("完成");
fos.close();
}
}



3.FileInputStream和FileOutputStream拷贝文件



package com.wuxiaolong.file.file1;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Description:
* 使用文件流,完成文件复制操作。方法和RandomAccessFile一致
* @author 诸葛小猿
* @date 2020-08-30
*/
public class CopyDemo {
public static void main(String[] args) throws IOException {
/*
* 使用FileInputStream读取源文件,使用FileOutputStream向目标文件中写数据。
* 依次从源文件中读取字节,然后写入目标文件,完成复制操作。
*/
FileInputStream fis = new FileInputStream("abc.exe");
FileOutputStream fos = new FileOutputStream("abc.exe",true); //再次执行时会追加,文件变大
byte[] b = new byte[1204*10];
int len=-1;
while((len=fis.read(b)) != -1){
fos.write(b,0,len);
}
fis.close();
fos.close();
System.out.println("完成复制");
}
}



四、java.io.BufferedInputStream和java.io.BufferedOutputStream



BufferedInputStream和BufferedOutputStream是一对缓冲流,属于高级流(处理流),用于处理低级流(节点流)的数据,使用它们可以提高读写的效率(先将数据写入缓冲区,在写入硬盘,减少了读写次数)。缓冲流单独存在没意义,必须和低级流一起使用。



1.使用缓冲流复制文件



package com.wuxiaolong.file.file1;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Description:
* 缓冲流 BufferedInputStream和BufferedOutputStream是一对缓冲流,属于高级流
* @author 诸葛小猿
* @date 2020-08-30
*/
public class CopyDemo2 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("abc.exe"); //低级输入流
BufferedInputStream bis = new BufferedInputStream(fis); //将低级输入流接到高级输入流上
FileOutputStream fos = new FileOutputStream("abc.cp.exe"); //低级输出流
BufferedOutputStream bos = new BufferedOutputStream(fos); //将低级输出流接到高级输出流上
int len = -1;
/*
* 缓冲流内部维护了一个缓冲区,当我们调用下面的read()方法读取一个字节时,
* 实际上缓冲流会让FileInputStream读取一组字节并存入到缓冲流自身内部的字节数组中,然后将第一个字节返回。
* 当我们再次调用read()方法读取一个字节时,缓冲流会直接将数组中的第二个字节返回,以此类推,直到该数组中所有字节都被读取
* 过后才会再次读取一组字节。所以实际上还是通过提高每次读取数据的数量减少读取的次数来提高读取效率。
*/
while((len = bis.read()) != -1){
/*
* 缓冲输出流也是类似的
*/
bos.write(len);
}
System.out.println("复制完成");
bis.close(); //只需要关高级流(内部会先关闭低级流)
bos.close();
}
}



2.手动将缓存中的数据刷到磁盘



package com.wuxiaolong.file.file1;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Description:
* 缓冲输出流写数据到硬盘中
* @author 诸葛小猿
* @date 2020-08-30
*/
public class BOSDemo {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("abc.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
/*
* 通过缓冲输出流写出的字节不会立即被写入硬盘,会先存在内存的字节数组,直到该数组满了,才会一次性写出所有的数据。
* 这样做等同于提高了一次性的写入数据量,减少了写的次数,提高效率
*/
bos.write("诸葛小猿".getBytes()); //不会及时写入硬盘,在内存中。如果不加colse(),最终被GC干掉,不会写入文件
/*
* flush方法强制将缓冲区的数据一次性输出。提高了及时性,但是频繁操作会降低操作效率。
*/
bos.flush();//强制及时写入内存,不会等到缓冲区满。 执行次数越多,效率越低
System.out.println("完成");
bos.close();//也调用了flush方法
}
}



五、java.io.ObjectInputStream和java.io.ObjectOutputStream



ObjectInputStream和ObjectOutputStream是一对对象流,属于高级流,ObjectInputStream可以读取一组字节转化为java对象,而ObjectOutputStream的作用是可以直接将java中的一个对象转化为一组字节后输出。这其实就是java对象的序列化和反序列化,因此java对象必须要实现序列化接口。



1.定义一个对象



package com.wuxiaolong.file.file1;
import lombok.*;
import java.io.Serializable;
import java.util.List;
/**
* Description: 该类用于测试作为对象进行对象流的读写操作
* @author 诸葛小猿
* @date 2020-08-30
*/
@Setter
@Getter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Person implements Serializable{
/**
* 1.实现Serializable接口。该接口中没有方法,不需要重写方法,这样的接口也叫签名接口。
*
* 2.需要有serialVersionUID,序列化的版本号(影响反序列化能否成功)。
* 当一个类实现了Serializable接口后,该类会有一个常量表示这个类的版本号,版本号影响这个对象反序列化的结果。
* 不显式定义serialVersionUID,会默认随机产生一个(根据类的结构由算法产生的,类只要改过,随机产生的就会变化)
* 建议自行维护版本号(自定义该常量并给定值)。若不指定,编译器会根据当前类的结构产生一个版本号,类的结构不变则版本号不变,但是结构变了(属性类型、名字变化等),都会导致版本号改变。
* 序列化时,这个版本号会存入到文件中。
* 反序列化对象时,会检查该对象的版本号与当前类现在的版本号是否一致,一致则可以还原,不一致则反序列化失败。
* 版本号一致时,就算反序列化的对象与当前类的结构有出入,也会采取兼容模式,即:任然有的属性就进行还原,没有的属性则被忽略。
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
/*
* 3.transient关键字的作用是修饰一个属性。那么当这个类的实例进行序列化时,该属性不会被包含在序列化后的字节中,从而达到了“瘦身”的目的
* 反序列化后是该类型的默认值。引用类型默认是null,其他类型默认是0。如果是静态变量,则映射为内存中的该变量的值。
*/
private transient List<String> otherInfo;
}



2.对象序列化



package com.wuxiaolong.file.file1;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
/**
* Description: