JAVA-第四部分-File、递归及IO流

219 阅读9分钟

File

  • 操作文件和文件夹

分隔符

//路径分隔符 win 分号; linux/os 冒号:
System.out.println(File.pathSeparator);
System.out.println(File.pathSeparatorChar);
//名称分割符 win 反斜杠\ linux/os 正斜杠/
System.out.println(File.separator);
System.out.println(File.separatorChar);

路径

  • 绝对路径,完整的路径,以盘符开始的路径
  • 相对路径,简化的路径,相对的是当前项目的根目录
  • 路径不区分大小写
  • win中,路径中的名称分隔符使用反斜杠,字符串中用两个反斜杠转义

构造

  • 不管真假
File file = new File("/Users/apple/Desktop/java/test.txt");
System.out.println(file); // 重写了toString
  • 两个参数的构造,父路径和子路径可以单独书写,使用灵活,会自动补充斜杠
File file1 = new File("User/apple/Desktop/java", "test/text.txt");
  • 两个参数,父路径是File类型,可以用File的方法,对路径进行操作,在创建对象
File parentFile = new File("User/apple/Desktop");
File file2 = new File(parentFile, "java/test/text.txt");

获取

  • 获取绝对路径,相对路径也会转换为绝对路径
System.out.println(file.getAbsolutePath());
  • 获取构造方法传递的路径,toString调用的就是getPath()
System.out.println(file.getPath());
  • 获取构造方法传递路径的结尾
System.out.println(file.getName());
  • 获取文件的大小,字节为单位,文件不存在返回0
System.out.println(file.length());

判断

  • 如果不存在都返回false
  • 是否存在
System.out.println(file.exists());
  • 是否是目录
System.out.println(file.isDirectory());

是否为文件

System.out.println(file.isFile());

创建和删除

  • 创建文件
  • 文件不存在,创建文件,返回true;文件存在,不会创建,返回false
  • 路径必须存在,不然会抛出异常,必须处理
System.out.println(file.createNewFile());
  • 创建文件夹,不会抛出异常
//单级
System.out.println(file.mkdir());
//多级
System.out.println(file.mkdirs());
  • 删除,文件和文件夹都可以删除
  • 删除成功返回true;失败返回false
  • 文件夹中有内容,不管是什么内容,都不会删除,返回false
System.out.println(file.delete());

遍历

  • list方法和listFile方法遍历的是构造方法的路径
  • 如果给出的路径不存在/如果路径不是一个目录,抛出空指针异常
  • 可以获取隐藏文件/隐藏文件夹
//字符串数组
String[] arr = file.list();
for (String s : arr) {
    System.out.println(s);
}

//返回的字符串数组转换成File数组
File[] files = file.listFiles();
for (File file1 : files) {
    System.out.println(file1);
}

文件过滤器

  • FileFilter用来过滤文件的,针对File对象;其中的accept方法,返回true,会把传递的过去的File对象保存在File数组中;返回false就不会
  • FilenameFilter针对文件名称的过滤,dir参数遍历的文件目录,name目录下的每一个文件/文件夹的名称
  • 两个过滤器接口没有实现类,需要我们自己实现,定义过滤的规则
  • listFiles方法,会对构造方法中传递的目录进行遍历,获取目录中的每一个文件/文件夹,封装为File对象;会调用参数传递的过滤器的accept方法;会把遍历得到的每一个File对象,传递给accept方法的参数pathname
  • FileFilter
public static void getAllFile(File dir) {
    //匿名内部类
    File[] files = dir.listFiles(new FileFilter() {
        @Override
        public boolean accept(File pathname) {
            //文件夹不过滤
            return pathname.getName().toLowerCase().endsWith(".png") || pathname.isDirectory();
        }
    });
    //Lambda
    File[] files1 = dir.listFiles(pathname -> pathname.getName().toLowerCase().endsWith(".png")
            || pathname.isDirectory());
    for (File file : files1) {
        if (file.isDirectory()) {
            getAllFile(file);
        } else {
            System.out.println(file);
        }
    }
}
  • FilenameFilter
File[] files2 = dir.listFiles(new FilenameFilter() {
    @Override
    public boolean accept(File dir, String name) {
        return new File(dir, name).isDirectory() || name.toLowerCase().endsWith(".png");
    }
});
File[] files3 = dir.listFiles(((d, name) -> new File(d, name).isDirectory()
        || name.toLowerCase().endsWith(".png")));

递归

  • 直接递归,自己调用自己
  • 间接递归,A调用B,B中包含A
  • 一定要有条件限定,次数不能太多,保证递归能停下来,否则会发生栈内存溢出
  • 构造方法,禁止递归
  • 前提,调用方法主体不变,每次调用方法的参数不同,可以使用递归
public static int sum(int n) {
    //中断递归
    if (n == 1) {
        return 1;
    }
    return n + sum(n - 1);
}
  • 方法调用的过程 image.png

IO

  • 流:数据(字符、字节) 1个字符=2个字节 1个字节=8个二进制位
  • 输入,把硬盘的数据,读取到内存中
  • 输出,把内存中的数据,写入到硬盘中

字节流

  • 一切文件数据,都是字节
  • 要先关闭写的,在关闭读的
fileOutputStream.close();
fileInputStream.close();
  • 使用字节流读取中文文件,一个中文,GBK占用两个字节;UTF-8占用三个字节,容易出现乱码

字节输出流

  • OutputStream
  • 写入的原理,java程序->JVM->OS(操作系统)->OS调用写数据的方法->写入文件
  • 写入
File file = new File("/Users/apple/Desktop/java/test.txt");
//自动创建
FileOutputStream fileOutputStream = new FileOutputStream(file);

//十进制整数转换成二进制,0-127查询ASCII码,其他值查询系统默认编码表(GBK中文)
fileOutputStream.write(130);

String outputStr = "hello,world";
fileOutputStream.write(outputStr.getBytes());

//写一部分,起始,长度
fileOutputStream.write(outputStr.getBytes(), 3,4);

//如果第一个字节是负数,第二个字节会和第一个字节组成一个中文
byte[] bytes = {-46,45,-12,43,-10,90,-44,-45}; //噎臬鲒杂
fileOutputStream.write(bytes);
//关闭
fileOutputStream.close();
  • 续写,append追加写开关,true创建对象不会覆盖原文件,继续在文件的末尾追加写数据
FileOutputStream fileOutputStream = new FileOutputStream(file, true);
  • 换行,win\r\n,linux\n,mac\r
fileOutputStream.write("\r".getBytes());

字节输入流

  • 读取的原理,java程序->JVM->OS(操作系统)->OS调用读数据的方法->写入文件

image.png

FileInputStream fileInputStream = new FileInputStream("day3-code/src/test.txt");
int i = 0;
//while循环读取
//读取一个字节并返回,读取到文件的末尾返回-1,指针向后一位
while ((i = fileInputStream.read()) != -1) {
    System.out.print((char) i);
}
fileInputStream.close();
  • 一次读取多个字节,返回读取到的有效字节个数,参数存放读到的字节,起到缓冲的作用,一般长度为1024
byte[] bytes = new byte[10];
int len = fileInputStream.read(bytes);
System.out.println(len);
System.out.println(Arrays.toString(bytes));
System.out.println(new String(bytes));
  • 循环读取多个字节
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fileInputStream.read(bytes)) != -1) {
    System.out.println(new String(bytes, 0,len));
    System.out.println(len);
}

image.png

  • 读取所有
byte[] bytes = fileInputStream.readAllBytes();
String s = new String(bytes);
System.out.println(s);

字符流

字符输入流

  • 把硬盘文件的数据以字符的方式读取到内存中
  • 一个个字符读取
FileReader fileReader = new FileReader("day3-code/src/test.txt");
int len = 0;
while ((len = fileReader.read()) != -1) {
    System.out.print((char) len);
}
fileReader.close();
  • 读取多个
int len = 0;
char[] chars = new char[1024];
while ((len = fileReader.read(chars)) != -1) {
    System.out.println(new String(chars,0,len));
}

字符输出流

  • 写入到内存缓存区中,调用flushclose才会写入
FileWriter fileWriter = new FileWriter("day3-code/src/test.txt",true);
fileWriter.write("hello.你好.123.abc");
fileWriter.flush();
fileWriter.close();
  • 续写
FileWriter fileWriter = new FileWriter("day3-code/src/test.txt",true);
  • 其他写入方式
fileWriter.write("hello.你好.123.abc",2,10);
fileWriter.flush();
char[] chars = {'a', 'b', 'c', 'w', 'e', '\r'};
fileWriter.write(chars);
fileWriter.flush();
fileWriter.write(chars,3,2);

close和flush

  • flush,刷新缓冲区,流对象可以继续使用
  • close,先刷新缓冲区,然后通知系统释放资源,流对象不可以再使用

try-with-resource

  • 处理IO异常
FileWriter fileWriter = null;
try {
    fileWriter = new FileWriter("dasday3-code/src/test.txt",true);
    fileWriter.write("hello.你好.123.abc\r");
    fileWriter.flush();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    //如果创建失败了,fileWriter默认值为null
    if (fileWriter != null) {
        try {
            fileWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    } else {
        System.out.println("fileWriter 为 null");
    }
}
  • JDK7新,try中后面的()可以定义流对象,流对象的作用域就在try中有效,try执行完毕自动释放close
try (FileWriter fileWriter = new FileWriter("day3-code/src/test.txt",true);){
    fileWriter.write("hello.你好.123.abc\r");
    fileWriter.flush();
} catch (IOException e) {
    e.printStackTrace();
}
  • JKD9,比较麻烦,也要throws
FileWriter fileWriter = new FileWriter("day3-code/src/test.txt",true);
try (fileWriter){
    fileWriter.write("hello.你好.123.abc\r");
    fileWriter.flush();
} catch (IOException e) {
    e.printStackTrace();
}

Properties

  • 继承Hashtable,已经被淘汰了,但是这个子类唯一和IO流相结合的集合
  • 双列结合,键值默认都是字符串
  • 基本使用
Properties properties = new Properties();
//存储
properties.setProperty("name", "zhangsan");
properties.setProperty("age","18");
properties.setProperty("address","beijing");
//stringPropertyNames 返回带有键的set集合
for (String stringPropertyName : properties.stringPropertyNames()) {
    //获取值
    System.out.println(properties.getProperty(stringPropertyName));
}
  • 把集合中的临时数据持久化写入到硬盘中存储
//字节输出流,不能写入中文,中文显示的/Unicode;字符输出流,可以写中文
//参数 comments,解释说明保存的文件,不能使用中文,一般传入空字符串
FileWriter fileWriter = new FileWriter("day3-code/src/properties.txt");
//FileOutputStream fileOutputStream = new FileOutputStream("day3-code/src/properties.txt");
properties.store(fileWriter, "properties");
  • 把硬盘中的数据(键值对)读取到集合中使用,文件中用空格和等号分隔都可以读出来
//字节输入流,不能读中文;字符输入流,可以读中文
FileReader fileReader = new FileReader("day3-code/src/properties.txt");
Properties properties = new Properties();
//读取文件
properties.load(fileReader);
for (String stringPropertyName : properties.stringPropertyNames()) {
    System.out.println(stringPropertyName + " = " + properties.getProperty(stringPropertyName));
}

缓冲流

  • 增强基本流的效率

字节缓冲输出流

  • 增加一个缓冲区,提高写入效率
  • 参数size,指定缓冲流内部缓冲区的大小
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("day3-code/src/test.txt",true),1024);
bufferedOutputStream.write("hello.world".getBytes());
//需要手动调用flush才会刷新
bufferedOutputStream.close();

字节缓冲输入流

BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("day3-code/src/test.txt"));
int len = 0;
byte[] bytes = new byte[1024];
while ((len = bufferedInputStream.read(bytes)) != -1) {
    System.out.println(new String(bytes, 0, len));
}
bufferedInputStream.close();

字符缓冲输出流

BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("day3-code/src/test.txt", true));
//行分隔符
bufferedWriter.newLine();
bufferedWriter.write("张三李四王五找刘");
bufferedWriter.newLine();
bufferedWriter.close();

字符缓冲输入流

BufferedReader bufferedReader = new BufferedReader(new FileReader("day3-code/src/test.txt"));
char[] chars = new char[1024];
int len = 0;
while ((len = bufferedReader.read(chars)) != -1) {
    System.out.println(new String(chars, 0 ,len));
    System.out.println(len);
}
bufferedReader.close();
  • 读一行文本
String s = "";
while ((s = bufferedReader.readLine()) != null) {
    System.out.println(s);
}

转换流

  • 对应规则
  • 编码 字符->字节
  • 解码 字节->字符
  • 中国 GBK
  • 国际 Unicode 常见UTF-8
  • FileReader可以读取默认格式的UTF-8,但是读取GBK报错

写入转换流

  • 参数一 OutputStream对象;参数二 指定编码,默认为utf-8
OutputStreamWriter gbk = new OutputStreamWriter(new FileOutputStream("day3-code/src/test1.txt"), "gbk");
gbk.write("你好世界");
gbk.flush();
gbk.close();

读取转换流

InputStreamReader gbk = new InputStreamReader(new FileInputStream("day3-code/src/test1.txt"), "gbk");
int len = 0;
char[] chars = new char[1024];
while ((len = gbk.read(chars)) != -1) {
    System.out.println(new String(chars,0,len));
}
gbk.close();

序列化和反序列化

  • 序列化 把对象以流的方式写入到文件中保存,对象的序列化
  • 反序列化,把文件中的字节对象读取到java程序中,对象的反序列化
  • 类中要实现Serializable序列化标记型接口
  • 被static修饰的成员变量,是不能被序列化的
  • transient关键字修饰的成员变量,不能被序列化

序列化

Person p1 = new Person("zhangsan", 18);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("day3-code/src/person.txt"));
objectOutputStream.writeObject(p1);
objectOutputStream.close();

反序列化

  • 要先关闭流,在输出
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("day3-code/src/person.txt"));
Object o = objectInputStream.readObject();
objectInputStream.close();
System.out.println(o);

序列号

  • 如果类发生改变,就会重新生成一个类序列号 image.png
  • 类中手动添加序列号
private static final long serialVersionUID = 30L;

打印流

  • 只负责数据的输出,不负责读取
  • 永远不会抛出异常
  • 如果使用父类write输出数据,那么查看数据的时候会查询编码表
  • 如果使用自己特有的方法print/println输出,数据原样输出
PrintStream printStream = new PrintStream("day3-code/src/print.txt");
printStream.write(97);
printStream.println(97);
printStream.print("3i129ndaowdjo");
printStream.print(true);
//改变 System.out.println 输出语句的目的地
System.setOut(printStream);
System.out.println("\rhello world");
printStream.close();