黑马高级班day10

这篇博客详细介绍了Java中的File类,包括构造方法、常用方法,以及如何处理文件的创建、删除。接着,讲解了IO技术的基础,包括字节流、缓冲流的概念和使用,以及Properties类和ResourceBundle工具类的应用。最后,讨论了递归的概念,通过实例展示了递归在文件路径查找中的应用。
摘要由CSDN通过智能技术生成

day10【File类 , 字节流、IO异常处理,Properties,ResourceBundle ,递归】

今日内容

  • Lambda表达式
  • Stream流

教学目标

  • 能够掌握Lambda表达式的标准格式与省略格式
  • 能够通过集合、映射或数组方式获取流
  • 能够掌握常用的流操作
  • 能够将流中的内容收集到集合和数组中

第一章 File类

1 概述

据最终保存在硬盘上,在硬盘上是以1和0 保存的。而给我们展示出来的并不是二进制数据,而是一个一个的文件,换句话说数据最终都保存在这些文件中。而这些文件又被文件夹管理。

通过上述描述我们发现在计算机硬盘中就存在两种事物:文件和文件夹事物。而在Java中使用class类来描述生活中存在的事物,那么Java中是如何描述文件和文件夹这两类事物呢?

使用File类来描述文件和文件夹事物。

java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作。

说明:

​ 1)Java中的定义的io技术的类基本都在java.io包下;

     2)使用File类来描述文件和文件夹事物的;

    3)File类它主要是对持久设备上的文件和文件夹进行操作。它不能去操作文件中的数据。将来我们只要需要操作持久设备上的文件或文件夹直接找File类完成,如果要操作文件中的数据只能使用IO流技术搞定;

结论:File类就是描述文件或文件夹的,只要我们在Java中要操作文件或文件夹,就找File类。

2 构造方法

  • public File(String pathname)

    表示根据文件或文件夹的路径名创建一个File对象。

    使用File类的构造函数可以把指定的字符串封装成File类的对象,但是这个字符串表示的路径或者文件到底是否存在,File类是不进行判断的。

    注意:File类表示文件和目录路径名的抽象表示形式。那么该路径代表的文件或文件夹不一定存在。

  • public File(String parent, String child) :从父路径名字符串和子路径名字符串创建新的 File实例。

  • public File(File parent, String child) :从父抽象路径名和子路径名字符串创建新的 File实例。

  • 构造举例,代码如下:

// 文件路径名
String pathname = "D:\\aaa.txt";
File file1 = new File(pathname); 

// 文件路径名
String pathname2 = "D:\\aaa\\bbb.txt";
File file2 = new File(pathname2); 

// 通过父路径和子路径字符串
 String parent = "D:\\aaa";
 String child = "bbb.txt";
 File file3 = new File(parent, child);

// 通过父级File对象和子路径字符串
File parentDir = new File("D:\\aaa");
String child = "bbb.txt";
File file4 = new File(parentDir, child);

小贴士:

  1. 一个File对象代表硬盘中实际存在的一个文件或者目录。
  2. 无论该路径下是否存在文件或者目录,都不影响File对象的创建。

3 常用方法

File类是描述文件和文件夹的。使用File类的对象就可以获取当前File类描述的那个文件或文件夹的信息(信息包括文件或者文件夹的名称、大小、类型、日期等)。

获取功能的方法

  • public String getAbsolutePath() :获取的当前调用这个方法的File对象的全路径(绝对路径或者真实路径) 返回的是路径的字符串;

    ​ 说明:在操作文件时,需要指定文件的路径,而路径在程序中有两种表现形式:

    ​ 1、 绝对路径(真实路径):是从盘符开始的路径,带有根目录的路

    ​ 径 例: D:\abc\test\Demo.java D就是根目录。

    ​ 2、 相对路径:是从当前路径开始的路径,或者可以理解不带根目录的路径

    例: 当前路径为D:\abc,要描述相对路径,只需输入test\Demo.java

  • public String getPath() :获取到当前File类的对象中封装的内容。

  • public String getName() :获取的是File类的对象中封装的文件或目录的最后一级名称。

  • public long length() :获取文件大小long size = file.length();

    方法演示,代码如下:

  /*
   * File类中的获取方法演示
   * 绝对路径:带根路径
   * 相对路径:不带根路径 test\\haha表示相对路径,这里表示相对于项目day19a来说
   */
  public class FileGetFunctionDemo {
  	public static void main(String[] args) {
  		// 创建File类的对象
  		File file=new File("D:\\test\\123.txt");
  		//getAbsolutePath()获取绝对路径演示 返回的是路径的字符串 D:\test\123.txt
  		String absolutePath = file.getAbsolutePath();
  		System.out.println("absolutePath="+absolutePath);
  		
  		//getName()函数  获取的是封装在File类中的文件或者文件夹的最后一级名称 123.txt
  		System.out.println("getName()="+file.getName());
  		
  		//getPath()表示获得File类中封装的所有内容 D:\test\123.txt
  		System.out.println("getPath()="+file.getPath());
  	    //获取文件大小
  		long size = file.length();
           System.out.println("size="+size);
  	}
  }

API中说明:length(),表示文件的长度。但是File对象表示目录,则返回值未指定。

绝对路径和相对路径

  • 绝对路径:从盘符开始的路径,这是一个完整的路径。
  • 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用。
public class FilePath {
    public static void main(String[] args) {
      	// D盘下的bbb.java文件
        File f = new File("D:\\bbb.java");
        System.out.println(f.getAbsolutePath());
      	
		// 项目下的bbb.java文件
        File f2 = new File("bbb.java");
        System.out.println(f2.getAbsolutePath());
    }
}
输出结果:
D:\bbb.java
D:\idea_project_test4\bbb.java

判断功能的方法

  • public boolean exists() :是否存在 如果File类对象中的文件或者文件夹在硬盘上存在 返回 true ,否则返回false;
  • public boolean isDirectory() :是否是文件夹 如果是文件夹 返回 true 否则返回false;
  • public boolean isFile() :是否是文件 如果是文件 返回 true , 否则返回false;

方法演示,代码如下:

public class FileIs {
    public static void main(String[] args) {
        File f = new File("D:\\aaa\\bbb.java");
        File f2 = new File("D:\\aaa");
      	// 判断是否存在
        System.out.println("D:\\aaa\\bbb.java 是否存在:"+f.exists());
        System.out.println("D:\\aaa 是否存在:"+f2.exists());
      	// 判断是文件还是目录
        System.out.println("D:\\aaa 文件?:"+f2.isFile());
        System.out.println("D:\\aaa 目录?:"+f2.isDirectory());
    }
}
输出结果:
d:\aaa\bbb.java 是否存在:true
d:\aaa 是否存在:true
d:\aaa 文件?:false
d:\aaa 目录?:true

创建删除功能的方法

  • public boolean createNewFile() :当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。

    ​ A:true :表示创建文件成功; false :表示创建文件失败,失败原因基本是因为当前的目录有了相同的文件;

    ​ B:如果指定的路径不存在,这时就会抛异常 Java.io.IOException:系统找不到指定的路径。这时是不会去创建文件的;

    需求:创建:D:\abc\1.txt文件。在D盘的abc文件夹下新创建一个1.txt文件。

    分析和步骤:

    1)创建File类的对象file,指定目录和文件名;

    2)使用file对象调用createNewFile()函数创建文件,并打印返回值;

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yA77wR9M-1648895990019)(img/创建文件.jpg)]

  • public boolean delete() :删除由此File表示的文件或目录。

  • public boolean mkdir() :创建由此File表示的目录。

  • public boolean mkdirs() :创建由此File表示的目录,包括任何必需但不存在的父目录。

方法演示,代码如下:

public class FileCreateDelete {
    public static void main(String[] args) throws IOException {
     	// 目录的创建
      	File f2= new File("D:\\aaa");	
        System.out.println("是否存在:"+f2.exists());// false
        System.out.println("是否创建:"+f2.mkdir());	// true
        System.out.println("是否存在:"+f2.exists());// true

		// 创建多级目录
      	File f3= new File("D:\\aaa\\bbb\\ccc");
        System.out.println(f3.mkdir());// false
        File f4= new File("D:\\aaa\\bbb\\ccc");
        System.out.println(f4.mkdirs());// true
      
      	// 目录的删除
        System.out.println(f2.delete());// true
        System.out.println(f4.delete());// false
    }
}

API中说明:

1)delete方法,如果此File表示目录,则目录必须为空才能删除。

2)函数的删除不走回收站。谨慎使用。

4 列举方法

  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录。

  • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录。

public class FileFor {
    public static void main(String[] args) {
        File dir = new File("D:\\test");
      
      	//获取当前目录下的文件以及文件夹的名称。
		String[] names = dir.list();
		for(String name : names){
			System.out.println(name);
		}
        //获取当前目录下的文件以及文件夹对象,只要拿到了文件对象,那么就可以获取更多信息
        File[] files = dir.listFiles();
        for (File file : files) {
            System.out.println(file);
        }
    }
}

小贴士:

1)调用listFiles方法的File对象,表示的必须是实际存在的目录,否则返回null,无法进行遍历。

  • 练习

    • 统计文件夹下java文件的个数

      public class Demo08_练习 {
      
          public static void main(String[] args) {
              //判断里面有几个java文件
      
              //创建对象
              File f = new File("D:\\test");
      
              //获取所有子内容的文件名
              File[] strs = f.listFiles();
              //定义计数器
              int count = 0;
      
              //遍历数组
              for (File file : strs) {
                  //如果是以.java结尾就是java文件
                  //调用获取名称
                  String name = file.getName();
                  if(name.endsWith(".java") && file.isFile()){
                      System.out.println(name);
                      count++;
                  }
              }
      
              System.out.println("一共有几个:" + count);
          }
      }
      

第二章 IO概述

1 什么是IO

简单回顾之前所学的知识:

字符串String:操作文本数据的;

字符串缓冲区:容器,可以存储很多的任意类型的数据,最后都变成字符串;

基本数据类型包装类:解决了字符串和基本数据类型之间的转换,同时给基本数据类型提供更多的操作;

集合框架:对象多了,便于存储后操作;

Date ,DateFormat,SimpleDateFormat、Calendar:解决日期和时间的问题;

在前期的学习上述知识点的过程中,我们书写的任何程序,它运行的时候都会有数据的产生,比如时间数据,而这些数据最终都保存在内存中。程序运行结束之后,数据就没有了。当程序下次启动的时候,如果还需要使用上次的结果,这时程序中是没有的。

而在开发中,在我们真正书写的程序中,往往有一些数据是需要长久保存起来的,当程序中需要这些长久保存起来的数据的时候,再从其他的地方读取到程序中。

也就是说我们以前学习的数据都是存储到内存中,而内存中只能暂时存储数据,当电脑关闭之后,数据就不见了。如果在开发中需要大量数据被长久保存,下次开电脑这些数据仍然还在,那么我们肯定不能保存到内存中,我们需要保存在能够保存持久数据的地方或者叫持久设备。

那么问题来了:什么是持久设备呢?

什么是持久设备

持久设备:可以持久保存数据的设备。硬盘、U盘、光盘、网盘、软盘等。这些设备都可以长久的保存数据。

那么还有一个问题:如何把程序中的数据保存到持久设备中?

​ Java提供了一个技术,专门来实现数据与持久设备间的交互:IO技术。

通过IO技术就可以达到程序和其他设备之间的数据交互。

什么是IO技术

IO技术:它主要的功能就是把我们程序中的数据可以保存到程序以外的其他持久设备中(如:我们电脑的硬盘),或者从其他持久设备中(如:我们电脑的硬盘)进行读取数据,把数据读取到我们的Java程序中。

IO:

​ I:Input:输入或者读取,从持久设备(硬盘)的数据放到内存中;

​ O:Output:输出或者写出,从内存中的数据放到持久设备(硬盘)上;

也可以按照如下理解:

把从持久设备(硬盘)上读取(输入)到内存中的动作,称为读操作。 I:input。

把内存中的数据输出(写出)到持久设备(硬盘)的动作,称为写操作。O:output。

2 IO的分类

1)按流向分:

  • 输入流:读取数据,把持久设备的数据读取到内存中。
  • 输出流:写出数据,把内存的数据写出到持久设备。

2)按数据类型分:

​ 计算机中一切数据都是:字节数据。

​ 字符数据:底层还是字节数据,但是可以根据某些规则,把字节变成人们认识的文字、符号等等。

  • 字节流:数据在持久设备上都是以二进制形式保存的。二进制就是字节数据。Java就给出了字节流可以直接操作字节数据。

    ​ 字节输入流:InputStream

    ​ 儿子:XxxInputStream

    ​ 字节输出流:OutputStream

    ​ 儿子:XxxOutputStream

  • 字符流:读取字符数据。数据在设备上是以二进制形式表示,但是有些二进制合并在一起可以表示一些字符数据。

    ​ 字符输入流:Reader

    ​ 儿子:XxxReader

    ​ 字符输出流:Writer

    ​ 儿子:XxxWriter

3 IO的流向说明图解请添加图片描述

说明:

1)字节流可以对任意类型的文件按照字节进行读和写的操作;

例如:图片、视频、文本文件、word文档、mp3等。

2)字符流只能对文本类型的文件进行操作;

​ 问题1:文本类型的文件是什么文件?

只要可以使用记事本打开并看得懂的文件就是文本文件。

例如:.java文件、.txt等文件。

而字符流只能操作文本类型的文件,也就是说如果一个文件可以使用记事本打开并能够看懂,那么这个文件就可以使用字符流来操作,否则其他的文件都得使用字节流进行操作。

注意:字节流也可以操作文本文件。

4 顶级父类们

输入流输出流
字节流字节输入流:InputStream字节输出流:OutputStream
字符流字符输入流:Reader字符输出流:Writer

第三章 字节流

1 一切皆为字节

一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都是一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。

2 字节输出流【OutputStream】

java.io.OutputStream 抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
  • public void write(byte[] b):把这个b字节数组中的所有数据写到关联的设备中(设备包括文件、网络或者其他任何地方)。
  • public void write(byte[] b, int off, int len) :把b字节中的数据从下标off位置开始往出写,共计写len个。
  • public abstract void write(int b) : 把这个b数据写到关联的设备中。

小贴士:

close方法,当完成流的操作时,必须调用此方法,释放系统资源。

3 FileOutputStream类

OutputStream有很多子类,我们从最简单的一个子类开始。

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件。

构造方法

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。

  • 构造举例,代码如下:
public class FileOutputStreamConstructor throws IOException {
    public static void main(String[] args) {
   	 	// 使用File对象创建流对象
        File file = new File("day10\\a.txt");//day10表示项目名
        FileOutputStream fos = new FileOutputStream(file);
      
        // 使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("day10\\b.txt");
    }
}

读取字节数据

**1.读取字节:**先使用输入流对象,从文件中读取数据,每调用一次read()方法,可以从硬盘文件中读取一个字节数据,把这个字节数据保存在一个int类型的变量中。然后判断读取到的这个数据也就是这个int类型的变量是否是-1,如果不是-1,书名当前没有读取到文件的末尾。
int类型的变量中就保存着当前读取到的那个字节数据,后续步骤可以对这个数据进行相关处理。
输入流的使用步骤:
A:创建输入流,关联源文件;
B:读取数据;
C:释放资源;
分析和步骤:
1)使用new关键字调用FilelnputStream类的构造函数创建指定路径
D:\test\1.txt的输入流对象in;
2)使用输入流对象in调用read()函数开始读取文件,返回一个int类型的整数,并输出最后返回值;
3)多次调用read()函数引起代码重复,我们可以考虑使用循环来实现;
4)循环条件是返回值-1;
代码使用演示:

/*
 * 演示:字节输入流读取数据,一次读一个字节。
 * 注意:由于输入流读取的是文件中的字节数据,所以要求输入流指定的一定是文件,否则会报异常
 * FileNotFoundException
 * 读取功能:
 * int read():表示读取下一个字节并返回
 * 输入流的使用步骤:
 * 1)创建输入流;
 * 2)读取数据;
 * 3)关闭输入流;
 */
public class FileInputStreamDemo {
	public static void main(String[] args) throws IOException {
		// 创建输入流的对象 java.io.FileNotFoundException: D:\test1 (拒绝访问。) 系统找不到指定的文件
		FileInputStream fis = new FileInputStream("D:\\test1\\1.txt");
		//使用输入流对象调用read()函数一次读一个字节数据
		//第一次读取
		int i = fis.read();//i表示读取的字节数据,如果是-1说明文件的结尾
		//输出读取的字节数据
		System.out.println((char)i);
		//第二次读取
		int i2 = fis.read();
		System.out.println((char)i2);
		//第三次读取
		int i3 = fis.read();
		System.out.println((char)i3);
		//第四次读取
		int i4 = fis.read();
		System.out.println((char)i4);
		//第五次读取
		int i5 = fis.read();
		System.out.println(i5);//-1
		//第六次读取
		int i6 = fis.read();
		System.out.println(i6);//-1
		//关闭资源
		fis.close();
	}
}

通过书写代码发现上述代码重复太多,我们可以考虑使用循环来解决上述代码重复性的问题
问题来了:循环的结束条件是什么?
读取到文件结尾,即-1则结束,所以可以让读取结果是-1结束读取文件。
改良上述代码,建华代码开发,使用循环。
循环改进读取方式,代码使用演示

		/*
		 * 通过书写代码发现上述代码重复太多,我们可以考虑使用循环来解决上述代码重复性的问题
		 * 问题:循环的循环条件是什么?
		 * 读取到文件结尾,即-1则结束,所以可以让读取的结果是-1结束读取文件
		 */
		//读取数据
		int i=fis.read();
		//循环控制读取文件数据的次数
		while(i!=-1)
		{  
		     //说明文件中还有数据,可以继续读取,输出读取的数据
		     System.out.println((char)i);
		     //修改循环条件 可以理解为一个光标,读取一次,i的值改变一次
		     i=fis.read();
		     }

通过上述代码发现,在使用输入流对象fis掉用read()函数的时候,出现多次调用的情况,这样也会导致代码重复,在开发出中尽量简化代码的书写,所以对上述代码还需进一步优化。
终极版代码单个读取数据的代码模板如下所示:

	//上述的代码读取的时候调用多次,代码也重复,我们仍然可以简化代码
		//定义一个变量接收读取的数据
		int b=0;
		//fis.read()表示使用输入流对象读取字节数据保存到变量b中,
		//如果b等于-1说明已经读取到文件末尾,否则文件中还有数据
		while((b=fis.read())!=-1)
		{
		  //输出字节数据
		  System.out.println((char)b);
		  }
		  //关闭资源
		  fis.close();

小贴士:
1.虽然读取了一个字节文件,但是会自动提升为int类型。
2.流操作完毕后,必须释放系统资源,调用close方法,千万记得。
1.使用字节数组读取:read(byte[] b), 读取数据到数组:buf中,返回的是读取到的字节的个数len.

定义的字节数组是用来存储从底层文件中读取到的多个字节数据;

在把读取的字节个数保存在len中。len中保存的是真正给字节数组中读取的字节个数,如果读取到文件末尾,也会返回-1;
一般这个数组的长度都会定义成1024的整数倍。
使用循环重复的从文件中读取数据,每次最多可以从文件中读取1024个字节数据

需求:在当前项目下新创键一个文件1.txt,然后在该文件中书写几个字符串,如下所示:

	hello
	world
	Java

字样,然后使用字节输入流一次读一个字节数组来读取上述路径中的1.txt文件中的数据,将每次读取的数据输出打印到控制台。

分析和步骤:

1)创建一个输入流对象,和D:\test\1.txt文件进行关联;

2)定义的字节byte数组b,这个字节数组的长度是5,主要是用来存储从底层文件中读取到的多个字节数据;

3)用来记录当前给byte数组中读取的字节个数的变量,int len = 0;

4)先执行fis.read()函数从底层读取数据,然后会把数据保存在我们传递的参数b数组中。返回值定义一个int类型的变量len记录着读取到字节数组中的字节数;

5)输出记录的读取到字节数len和将字节数组转换后的字符串数据,将字节转换为字符串可以使用Arrays.toString(b)或者String类的构造函数;

6)由于字节数组长度是5,所以需要多次读取,按照上述操作多次读取1.txt文件中剩余的数据,将结果输出到控制台上面
   /*
    * 演示:字节输入流,一次读一个字节数组。
    * int read(byte[] b) 表示定义一个byte数组,每次读取的字节都存储到这个数组中
    */
   public class FileInputStreamDemo1 {
   	public static void main(String[] args) throws IOException {
   		// 创建输入流对象,关联源文件
   		FileInputStream fis = new FileInputStream("D:\\test\\1.txt");
   		//第一次读取
   		//定义一个数组保存字节数据
   		byte[] b=new byte[5];
   		//读取数据 将数据保存到字节数组中
   		int len = fis.read(b);
   		//输出len
   		System.out.println("len="+len);
   		//输出字节数组中的内容
   //		System.out.println(Arrays.toString(b));
   		System.out.println(new String(b));//hello
            //第一次读取结果:
               /* 
                 len=5
   			hello
               */
             //第二次读取
   		 len = fis.read(b);
   		//输出len
   		System.out.println("len="+len);
   		//输出字节数组中的内容
   		System.out.println(new String(b));
            //第二次读取结果:
   		/*
   			len=5
   			\r\n
   			wor
   		 */

   		//第三次读取
   		len = fis.read(b);
   		//输出len
   		System.out.println("len="+len);
   		//输出字节数组中的内容
   		System.out.println(new String(b));
            //第三次读取结果:
   		/*
   		 *  len=5
   			ld\r\n
   			J
   		 */
   		//第四次读取
   		len = fis.read(b);
   		//输出len
   		System.out.println("len="+len);
   		//输出字节数组中的内容
   		System.out.println(new String(b));
            //第四次读取结果:
   	    /*
            在1.txt文件中,如果最后的数据Java有回车换行,那么会输出如下所示数据:
   		    len=5
   		    ava\r\n
            在1.txt文件中,如果最后的数据Java没有回车换行,那么会输出如下所示数据:
                len=3
   		    ava
   		    J 
   		 */
   	}
   }

说明:

1)通过上述代码发现返回值len表示读取到的字节数,而不是字节数组的长度。如果读取为5个字节数,那么返回5,即,len等于5。如果读取到的字节数是3,那么返回3,即len等于3。如果文件中没有要读取的数据,则返回-1。

2)上述代码中当第四次读取的时候有问题,如果文件中最后一个数据后面没有回车换行,那么应该只打印ava,为什么会打印:

ava

J

呢?

原因如下图所示:
请添加图片描述
为了解决上述代码出现的问题,我们更希望看到当我们读取几个字节数据,我们就输出几个字节数据,所以这里我们不能在使用new String(b)构造函数,我们应该使用new String(b.int offset,int length)构造函数,这样做就不会出现上述的问题。
说明:new String(b,0,len):
创建一个字符串对象,把b数组中的数据转换成字符串,从0位置开始,共计转len个。
从0位置开始,因为每次调用输入流的read方法的时候,把数据给byte数组中保存
这时真正是从byte数组的0位置开始存储读取的每个字节数据
len是byte数组的0位置开始存储读取的每个字节个数。
这样做就可以做到我们读取几个字节数据,我们就输出几个字节数据的目的。
代码如下所示:

   public class FileInputStreamDemo1 {
   	public static void main(String[] args) throws IOException {
   		// 创建输入流对象,关联源文件
   		FileInputStream fis = new FileInputStream("D:\\test\\1.txt");
   		//第一次读取
   		//定义一个数组保存字节数据
   		byte[] b=new byte[5];
   		//读取数据 将数据保存到字节数组中
   		int len = fis.read(b);
   //		System.out.println(new String(b,0,len));//hello
   		System.out.print(new String(b,0,len));//hello
   		//第二次读取
   		 len = fis.read(b);
   		//输出len
   //		System.out.println("len="+len);
   		//输出字节数组中的内容
   //		System.out.println(new String(b,0,len));
   		System.out.print(new String(b,0,len));
   		//第三次读取
   		len = fis.read(b);
   		//输出len
   //		System.out.println("len="+len);
   		//输出字节数组中的内容
   //		System.out.println(new String(b,0,len));
   		System.out.print(new String(b,0,len));
   		//第四次读取
   		len = fis.read(b);
   		//输出len
   //		System.out.println("len="+len);
   		//输出字节数组中的内容
   //		System.out.println(new String(b,0,len));
   		System.out.print(new String(b,0,len));
            //关闭资源
   		fis.close();
   }
   }

上面得到代码重复太多了,考虑用循环。
问题来了:循环的接受条件是什么呢?
结束条件:末尾1返回-1
终极版代码如下所示:

		//定义一个数组
//		byte[] b=new byte[5];
		//终极版代码模板
		byte[] b=new byte[1024];//数组长度一般是1024的整数倍
		//定义一个变量保存读取字节的个数
		int len=0;
		//fis.read(b)表示读取的数据都存放到byte数组中了,len表示读取字节数
		while((len=fis.read(b))!=-1)//一定要传递参数数组b
		{
			System.out.print(new String(b,0,len));
		}
         //关闭资源
		fis.close();

小贴士:
使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使用

6.字节流练习:文件复制

赋值原理图解

请添加图片描述

案例实现

需求:
演示复制文件:把D盘test文件夹下的1.mp3拷贝到E盘下。
分析和步骤:每次读字节数组来完成赋值
1)步骤和上述步骤大致相同,只是需要定义一个整数变量len来记录当前给byte数组中读取的字节个数的;

2)定义的字节数组buf是用来存储从底层文件中读取到的多个字节数据;

3)使用read(buf)函数读取文件的时候需要传入一个数组参数buf;

4)每读取一次使用输出流对象调用write()函数向文件中写数据;

复制图片文件,代码使用演示:

//演示一次读取一个字节数组
	public static void method_2() throws IOException {
		// 创建输入流对象 关联源文件
		FileInputStream fis = new FileInputStream("D:\\test\\1.mp3");
		//创建输出流对象 关联目标文件
		FileOutputStream fos = new FileOutputStream("E:\\1.mp3");
		//获取开始复制的时间
		long start = System.currentTimeMillis();
		//定义数组  读数据
//		byte[] b=new byte[8192];
		byte[] b=new byte[1024];
		int len=0;
		while((len=fis.read(b))!=-1)
		{
			//写数据
			fos.write(b, 0, len);
		}
		//获取结束复制的时间
		long end = System.currentTimeMillis();
		System.out.println("复制时间是:"+(end-start));//复制时间是:15
		//关闭资源
		fis.close();
		fos.close();
		
	}

第四章 缓冲流

前面学习了基本的一些流,作为IO流的入门,今天我们要见识一些更强大的流。比如能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等等。这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强。

1.概述

缓冲流是对4个基本的FileXxx流的增强,所以也是四个流,按照流操作数据的数据类型分类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter
    缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

2.字节缓冲流

构造方法

  • public BufferedInputStream(InputStream in) :创建一个 新的字节缓冲输入流。

  • public BufferedOutputStream(OutputStream out): 创建一个新的字节缓冲输出流。
    说明:我们发现上述缓冲流的参数依然需要对应的字节流对象。
    这是因为缓冲区流是为了高效而设计的,缓冲区流本身仅仅是维护了一个数组。不具备读取和写的功能。真正的读写还是要依赖普通的字节流。
    构造举例,代码如下:

// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("day09\\bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("day09\\bos.txt"));

效率测试

查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件,测试它的效率。

  1. 基本流,代码如下:
public class BufferedDemo {
    public static void main(String[] args) {
        // 记录开始时间
      	long start = System.currentTimeMillis();
		// 创建流对象
        try (
        	FileInputStream fis = new FileInputStream("D:\\test\\制服诱惑2.mp4");
        	FileOutputStream fos = new FileOutputStream("F:\\制服诱惑2.mp4")
        ){
        	// 读写数据
            int b;
            while ((b = fis.read()) != -1) {
                fos.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("普通流复制时间:"+(end - start)+" 毫秒");
    }
}
普通流复制时间:288242毫秒

2.缓冲流,代码如下:

public class BufferedDemo {
    public static void main(String[] args){
        // 记录开始时间
      	long start = System.currentTimeMillis();
		// 创建流对象
        try (
        	BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\test\\制服诱惑2.mp4"));
	     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("F:\\制服诱惑2.mp4"));
        ){
        // 读写数据
            int b;
            while ((b = bis.read()) != -1) {
                bos.write(b);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("缓冲流复制时间:"+(end - start)+" 毫秒");
    }
}

缓冲流复制时间:2108 毫秒

如何更快呢?

使用数组的方式,代码如下:

public class BufferedDemo {
    public static void main(String[] args) throws FileNotFoundException {
      	// 记录开始时间
        long start = System.currentTimeMillis();
		// 创建流对象
        try (
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\test\\制服诱惑2.mp4"));
		 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("F:\\制服诱惑2.mp4"));
        ){
          	// 读写数据
            int len;
            byte[] bytes = new byte[8*1024];
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0 , len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
		// 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
    }
}
缓冲流使用数组复制时间:115 毫秒

第五章 属性集(掌握)

1.概述

1 概述

java.util.Properties 继承于 Hashtable ,来表示一个持久的属性集。它使用键值结构存储数据,每个键及其对应值都是一个字符串。

2 Properties类

Properties类:在之前讲解Map集合时,提过一个集合类:Hashtable集合类,该类现已经被HashMap代替。而Hashtable集合类有一个子类对象:Properties。

​ 1)该类是一个Map集合。之前学习的集合都是将数据存储到内存中,但是这个集合类它可以和IO流结合,直接把集合中的数据保存在硬盘的文件中,或者直接从硬盘的文件中加载数据保存在集合中;

​ 2)这个集合中的键和值都是String类型的字符串;

​ 3)这个集合类没有泛型;

构造方法

  • public Properties() :创建一个空的属性列表。

基本的存储方法

  • public Object setProperty(String key, String value) : 保存一对属性。 等同于Map中的put功能

  • public String getProperty(String key) :使用此属性列表中指定的键搜索属性值。等同于Map中的 get(Object key)

  • public Set<String> stringPropertyNames() :所有键的名称的集合。等同于Map中的keySet方法

public class ProDemo {
    public static void main(String[] args) throws FileNotFoundException {
        //创建集合对象
		Properties prop = new Properties();
		//向集合中添加数据
		prop.setProperty("张三","北京");
		prop.setProperty("李四","上海");
		prop.setProperty("王五","南京");
		prop.setProperty("田七","杭州");
		//使用对象调用stringPropertyNames()函数获取所有的键集合
		Set<String> keys = prop.stringPropertyNames();
		//遍历集合
		for (Iterator<String> it = keys.iterator(); it.hasNext();) {
			String key = it.next();
			//根据key获得value
			String value = prop.getProperty(key);
			System.out.println(key+"---"+value);
		}
    }
}

与流相关的方法

  • public void load(InputStream inStream): 从字节输入流中读取键值对。

参数中使用了字节输入流,通过流对象,可以关联到某文件上,这样就能够加载文本中的数据了。文本数据格式要求必须是如下格式: key=value

张三=北京
李四=上海
王五=南京
田七=杭州

加载代码演示:

public class ProDemo2 {
    public static void main(String[] args) throws FileNotFoundException {
        //创建集合对象
		Properties prop = new Properties();
		//使用集合对象prop调用load()函数从硬盘上加载数据
//		prop.load(new FileInputStream("D:\\person.txt"));
		prop.load(new FileReader("D:\\person.txt"));
		//遍历集合
		Set<String> keys = prop.stringPropertyNames();
		for (Iterator<String> it = keys.iterator(); it.hasNext();) {
			String key = it.next();
			//根据key键获得value
			String value = prop.getProperty(key);
			System.out.println(key+"---"+value);
          }
     }
}

小贴士:文本中的数据,必须是键值对形式。

第六章 ResourceBundle工具类

前面我们学习了Properties工具类,它能够读取资源文件,当资源文件是以.properties结尾的文件时,我们可以使用JDK提供的另外一个工具类java.util.ResourceBundle来对文件进行读取,使得操作更加简单。它是一个抽象类,我们可以使用它的子类PropertyResourceBundle来读取以.properties结尾的配置文件。

1.作用

​ 能够更方便的读取文件。代替Properties的load方法的。

​ 但是使用这个工具类有前提条件:

​ 1.文件需要是properties类型

​ 2.文件需要保存在src路径下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F22j4gly-1648899642797)(img\image-20200501235546979.png)]

2.创建对象

在ResourceBundle类中提供了一个静态方法,用于获得它的子类对象(抽象类不能创建对象!)。

// 使用指定的基本名称,默认语言环境和调用者的类加载器获取资源包。   
static ResourceBundle getBundle(String baseName);
	参数:baseName:给定参数只需要配置文件的名称,不要扩展名。

3.获取方法

ResourceBundle类提供了一个getString(String key)方法用于读取配置文件中指定key的值

String getString(String key)  //根据键获取值
  • 需求:使用ResourceBundle读取如下配置文件中的value值

    配置文件如下所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eiAMQgWQ-1648899642798)(img\image-20200502000619545.png)]

    代码如下:

    public class Test01 {
        public static void main(String[] args) throws Exception {
            //获取对象
            //static ResourceBundle getBundle(String baseName);
            //注意:getBundle()方法的参数只书写文件名,不要书写后缀名
            /*
                带后缀名会报错:
                Exception in thread "main" java.util.MissingResourceException:
                Can't find bundle for base name 123.properties, locale zh_CN
             */
            ResourceBundle bundle = ResourceBundle.getBundle("123");
    
            //根据键获取值
            //String getString(String key)  //根据键获取值
            String s = bundle.getString("username");
            System.out.println(s);//root
    
            //如果键不存在 就报错了
            /*
                Exception in thread "main" java.util.MissingResourceException:
                Can't find resource for
                bundle java.util.PropertyResourceBundle, key pwd
             */
    //        String s2 = bundle.getString("pwd");
    //        System.out.println(s2);
    
        }
    }
    
  • 中文乱码问题

    如果在使用ResourceBundle读取配置文件中的中文时,出现乱码问题,可以按照如下方式去做。但是注意我们在开发中不会在配置文件中书写中文,所以这里如果课下遇到了则按照下面做法做就可以了。

    1.修改idea中的编码,我们统一修改idea中的编码为UTF-8.

请添加图片描述

改完编码之后删除创建好的配置文件,重新创建配置文件。

2.如果按照如上方式还会出现乱码,则可以按照如下做法:

public class Demo06 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        //获取对象
        ResourceBundle bundle = ResourceBundle.getBundle("123");

        //根据键获取值
        String s = bundle.getString("abc");

        //把s从字符串变回到字节
        byte[] bytes = s.getBytes("ISO8859-1");

        //把字节数组通过正确的编码转回汉字
        String ss = new String(bytes,"UTF-8");

        System.out.println(ss);

    }
}

请添加图片描述

4.修改idea开发环境码表

1.请添加图片描述

2.请添加图片描述

第七章 递归

1 概述

需求:扫描D:\test所有子文件夹及子子文件夹下的.jpg文件。

我们如果用循环来做这件事,我们不知道循环的结束条件,也不知道到底有多少层,所以比较麻烦。

我们可以用一种新的思想:递归。

递归举例:

​ 从前有一座山,山里有座庙,庙里有个老和尚,老和尚在给小和尚讲故事:

​ 从前有一座山,山里有座庙,庙里有个老和尚,老和尚在给小和尚讲故事:

​ 从前有一座山,山里有座庙,庙里有个老和尚,老和尚在给小和尚讲故事:

。。。。。。。

故事如何才能结束:小和尚还俗了。庙塌了。山崩了。

Java中的递归:

​ 在方法的函数体中又调用了方法自己本身。

递归调用的细节:必须要求递归中有可以让函数调用的结束条件。否则函数一直调用,就会导致内存溢出。

2 递归累和

计算1 ~ 3的和

练习1:需求:计算1~3的和值,不许使用循环。

实现代码

public class DiGuiDemo {
	public static void main(String[] args) {
		//计算1~3的和,使用递归完成
		int n = 3;
      	// 调用求和的方法
		int sum = getSum(n);
      	// 输出结果
		System.out.println(sum);
		
	}
  	/*
  	  通过递归算法实现.
  	  参数列表:int 
  	  返回值类型: int 
  	*/
	public static int getSum(int n) {
      	/* 
      	   n为1时,方法返回1,
      	   相当于是方法的出口,n总有是1的情况
      	*/
		if(n == 1){
			return 1;
		}
      	/*
          n不为1时,方法返回 n +(n-1)的累和
          递归调用getSum方法
        */
		return n + getSum(n-1);
	}
}

代码执行图解

请添加图片描述

小贴士:递归一定要有条件限定,保证递归能够停止下来,次数不要太多,否则会发生栈内存溢出。

3 递归求阶乘

练习2:需求:求5的阶乘!!

分析:

普及一下数学知识:

使用递归思想来完成

5! = 5 * 4!

			   4! = 4 * 3!

					      3! = 3 * 2!

							         2! = 2 * 1!

									            1! = 1*0!

找规律:n! = n * (n-1)!

补充:0!等于1

结束条件:if(n <= 1) return 1;

递归方式:

代码如下所示:

步骤:

1)定义一个DiGuiDemo3测试类;

2)在这个类中的main函数中调用自定义函数jc2(),5作为函数的参数,使用一个变量result来接收返回的阶乘的值,并输出结果result;

3)自定义jc2()函数接收传递的参数5;

4)在自定义函数中书写if语句判断n是否小于等于1,如果小于等于1,则使用return返回1;

5)否则n>1时,使用return返回n * jc2(n - 1);

package cn.itcast.sh.digui.demo;
/*
 * 方式2:使用递归思想来解决5的阶乘
 *方式一: 5!=5*4*3*2*1;
 *方式二:5!=5*4!
 *			  4!=4*3!
 *				    3!=3*2!
 *						  2!=2*1!
 *								1!=1*0!
 *找规律:n!=n*(n-1)!
 *找结束条件:
 *	if(n<=1) return 1;
 */
public class DiGuiDemo3 {
	public static void main(String[] args) {
		// 调用函数求5的阶乘
		int result=jc2(5);
		System.out.println(result);//120
	}
	//自定义函数求5的阶乘
	public static int jc2(int n) {
		// 结束条件
		if(n<=1)
		{
			return 1;
		}
		return n*jc2(n-1);
	}
}

上述代码内存图解如下所示:

请添加图片描述

4 递归注意事项

1)递归必须有结束条件,否则栈内存会溢出,称为死递归!栈炸了。

请添加图片描述

栈内存溢出报的异常如下所示:
请添加图片描述

2)递归次数不能太多,否则栈溢出。炸了

请添加图片描述

栈内存溢出报的异常如下所示:

请添加图片描述

总结:递归容器容易导致内存溢出。即使递归调用中有结束条件,但是如果递归的次数太多,也会发生内存溢出。

所以在开发中使用函数的递归调用时需谨慎。

5 递归打印所有子目录中的文件路径

需求:扫描D:\test所有子文件夹及子子文件夹下的.jpg文件,输出其绝对路径。

分析:

首先我们可以拿到D:\\test下的所有儿子,我们判断儿子是不是文件夹,如果是,再次扫描儿子的文件夹,然后获取儿子下面的所有子文件和子文件夹,即就是D:\\test的孙子,然后我们再判断孙子是不是文件夹等等,以此类推,最后我们输出其绝对路径;

思路:

A:封装父目录的File对象;

B:获取父目录下的所有儿子的File数组;

C:循环遍历,获取每个儿子;

D:判断是否是文件夹  

	是:回到步骤B 继续获取孙子

	否:判断是否是.jpg  结束递归的条件

		是:打印

		否:不管

我们发现:B到D的过程是不断重复。我们可以封装成递归的函数。

步骤:

1)创建测试类FileTest1;

2)在FileTest1类的main函数中封装父目录D:\test的对象parent;

3)调用递归扫描的函数scanFolders(parent);

4)自定义函数scanFolders(),使用父目录对象parent调用listFiles()函数获得父目录下所有儿子的File数组files;

5)循环遍历,获取每个儿子对象file;

6)使用file对象调用isDirectory()函数判断是否是文件夹;

7)如果是文件夹,则递归调用scanFolders(file)函数;

8)如果不是文件夹,肯定是文件,使用file对象调用getName()函数获得文件的全名,调用endsWith()函数判断后缀名是否是.jpg,如果是输出文件所属的绝对路径;

package cn.itcast.sh.file.test;
import java.io.File;
/*
 * 需求:扫描D:\\test所有子文件及子子文件下的.jpg文件,输出其绝对路径。
 * 思路:
 * A:创建父目录
 * B:调用函数查找文件或者文件夹
 * C:通过父目录对象调用函数获取所有儿子的File数组
 * D:循环遍历所有的儿子,dir表示父目录D:\\test的每一个儿子
 * 		a:判断获取的儿子dir是否是文件夹 如果是 执行步骤B
 * 		b:不是,判断后缀名是否是.jpg,是,输出绝对路径
 * 
 * 我们发现:B到D的过程是不断重复。我们可以封装成递归的函数。
 */
public class FileTest1 {
	public static void main(String[] args) {
		//封装父目录的对象
		File parent = new File("D:\\test");
		//调用函数查找文件或者文件夹
		scanFolderAndFile(parent);
	}
	//自定义函数扫描文件或者文件夹
	public static void scanFolderAndFile(File parent) {
		//通过父目录对象调用函数获取所有儿子的File数组
		File[] dirs = parent.listFiles();
		//循环遍历所有的儿子,dir表示父目录D:\\test的每一个儿子
		for (File dir : dirs) {
			/*
			 * 判断获取的儿子dir是否是文件夹
			 * 如果是文件夹,那么继续扫描或者查找儿子下面的所有文件或者文件夹
			 * 以此类推
			 * 如果不是文件夹,那么肯定是文件,判断后缀名是否是.jpg
			 * 		如果是.jpg 则输出其绝对路径
			 */
			if(dir.isDirectory())
			{
				//说明是文件夹  继续找儿子下面的文件或者文件夹 执行扫描函数
				scanFolderAndFile(dir);
			}else
			{
				/*
				 * 说明不是文件夹,是文件,我们判断是否是.jpg
				 * dir.getName()表示获取文件的名字  mm.jpg
				 */
				if(dir.getName().endsWith(".jpg"))
				{
					//说明文件的后缀名是.jpg 输出其绝对路径
					System.out.println(dir.getAbsolutePath());
				}
			}
		}
	}
}

请添加图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值