day21【字符流、Properties】

主要内容

  • File类
  • 递归
  • 字节输出流

教学目标

  • 能够说出File对象的创建方式
  • 能够说出File类获取名称的方法名称
  • 能够说出File类获取绝对路径的方法名称
  • 能够说出File类获取文件大小的方法名称
  • 能够说出File类判断是否是文件的方法名称
  • 能够说出File类判断是否是文件夹的方法名称
  • 能够辨别相对路径和绝对路径
  • 能够遍历文件夹
  • 能够解释递归的含义
  • 能够使用递归的方式计算5的阶乘
  • 能够说出使用递归会内存溢出隐患的原因

第一章 字符流

当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。

1.1 字节流读取字符的问题

数据在持久设备上一定是以二进制形式保存,是以字节形式保存。但是有些字节数据合在一起才表示的是字符数据。

比如:记事本中保存的数据,最后肯定是以字节形式保存在硬盘上,但是其中需要2个字节表示一个汉字。我们真正要使用流读取记事本中的数据时,不应该一个一个字节读取,而是应该把表示汉字的那几个字节一起读取,然后把这些字节合在一起表示一个汉字。

需求:使用字节流读取D:\out.txt记事本以下的内容:

abc你好

一次读一个字节数组代码演示如下所示:

分析和步骤:

1)创建FileInputStream类的对象fis,D:\out.txt作为参数;

2)定义一个字节byte数组b,先让数组长度是1024然后读取一遍后在将数组长度变为4;

3)定义一个变量len=0记录读取字节的个数;

4)使用while循环来读取数据,并使用输出语句输出String类构造函数转换后的数据

5)关闭资源流;

//一次读取一个字节数组
	public static void method_2() throws IOException {
		// 创建读取字符数据的输入流对象
		FileInputStream fis = new FileInputStream("D:\\out.txt");
		//定义一个字节数组
//		byte[] b=new byte[1024];//数组大小是1024的整数倍
		byte[] b=new byte[4];//abc  
		//定义一个变量记录读取字节的个数
		int len=0;
		while((len=fis.read(b))!=-1)
		{
			System.out.print(new String(b,0,len));
		}
		//释放资源
		fis.close();
	}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s85yzpWW-1593434888587)(img/字节流读取文本文件乱码.jpg)]

说明:会出现上述结果的原因是字母abc各占一个字节,而汉字占两个或者三个字节。上述定义的字节数组一次只能存放4个字节,所以会将你字进行字节的拆分,这样会导致后面的汉字字符对应的字节都发生了变化,所以会出现乱码。

字节流读取字符数据的问题:

汉字等字符,往往由多个字节组成。使用字节流读取由多个字节组成字符数据,发现读取到的每个字符的对应的字节数据,而不是真正的字符内容。而我们更希望看到读取的具体的内容是什么字符数据。

我们需要把读取到的某2个或者3个字节合并在一起,拼成一个汉字。这时我们在程序中并不知道应该把哪2个或者3个字节合并成一个汉字。由于在程序中,有时一个字节就表示一个字符数据,比如英文字母,有时必须是2个字节或者3个字节表示一个汉字,那么到底应该把一个字节转成字母,还是把2个字节或者3个字节转成汉字,这时我们无法对读取的数据进行控制。

例如:记事本中的数据:abc你好

  我们如果使用字节流来读取上述数据,那么字节流不知道什么时候是几个字节组成字母,几个字节组成汉字,这样会出现我们不想要的结果。

但是Java提供的字符流就可以解决上述读取字符数据的问题

1.2 字符输入流【Reader】

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • public void close() :关闭此流并释放与此流相关联的任何系统资源。

  • public int read(): 调用一次读取一个字符,返回字符的编码值。即读取的内容存放到返回值中。如果读取到文件末尾返回-1;

  • public int read(char[] cbuf): 调用一次读取多个字符,把这些字符保存在cbuf中,返回给字符数组中存储的字符个数,如果读取到文件末尾返回-1;

1.3 FileReader类

java.io.FileReader类是读取字符文件的便利类。构造时使用当前环境默认的字符编码和默认字节缓冲区。

小贴士:

  1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。idea中UTF-8
  2. 字节缓冲区:一个字节数组,用来临时存储字节数据。

注意:在计算机中所有数据在底层都是以字节数据存在的,即使是字符数据在最底层也是以字节数据存在的,因为计算机只识别字节数据。所以在字符流底层使用的是字节缓冲区,其实就是一个字节数组。

构造方法

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。

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

读取字符数据

  1. 读取字符read方法,调用一次读取一个字符,返回字符的编码值。提升为int类型。即读取的内容存放到返回值中。如果读取到文件末尾返回-1;循环读取,代码使用演示:

    案例:使用字符输入流读取D:\out.txt上的文本文件。并将数据显示到控制台中。

    读数据–输入流–FileReader

    FileReader:

     	FileReader(String fileName):传递文件名称 
    

    输入流读文件的步骤:

     	A:创建输入流对象
    
     	B:调用输入流对象的读数据方法
    
     	C:释放资源
    
    package com.itheima_02;
    import java.io.FileReader;
    import java.io.IOException;
    
    public class FileReaderDemo {
    	public static void main(String[] args) throws IOException {
    		//创建输入流对象
    //指定文件不存在就会报异常:java.io.FileNotFoundException: D:\\test\\1111.txt (系统找不到指定的文件。)
    //		FileReader fr = new FileReader("D:\\test\\1111.txt");
    		FileReader fr = new FileReader("D:\\out.txt");
    		
    		//调用输入流对象的读数据方法
    		//int read():一次读取一个字符
    		int ch=0;
    		while((ch=fr.read())!=-1) {
    			System.out.print((char)ch);
    		}
    		//释放资源
    		fr.close();
    	}
    }
    
    

小贴士:虽然读取了一个字符,但是会自动提升为int类型。

  1. 使用字符数组读取read(char[] cbuf),调用一次读取多个字符,把这些字符保存在cbuf数组中,返回给字符数组中存储的字符个数,如果读取到文件末尾返回-1.代码使用演示:
public class FISRead {
    public static void main(String[] args) throws IOException {
      	// 使用文件名称创建流对象
       	FileReader fr = new FileReader("D:\\out.txt");
      	// 定义变量,保存有效字符个数
        int len =0;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[1024];
        // 循环读取
        while ((len = fr.read(cbuf))!=-1) {
            System.out.println(new String(cbuf,0,len));
        }
    	// 关闭资源
        fr.close();
    }
}

1.4 字符输出流【Writer】

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

  • public abstract void close() :关闭此输出流并释放与此流相关联的任何系统资源。

  • public abstract void flush() :刷新此输出流并强制任何缓冲的输出字符被写出。

    说明:字符输入和字符输出流都是自带缓冲区的。缓冲区就是在类的底层封装了一个数组,如果我们使用字符输出流向目的地文件中写数据的时候,数据不会立刻写到目的地文件中,而是写到自带的数组中,数据还在内存中,所以我们必须调用FileWriter类中的刷新方法flush将数组中的数据刷新到目的地硬盘文件中。

  • public void write(int b) :写出一个字符。

  • public void write(char[] cbuf):将 b.length字符从指定的字符数组写出此输出流。

  • public abstract void write(char[] b, int off, int len) :从指定的字符数组写出 len字符,从偏移量 off开始输出到此输出流。

  • public void write(String str) :写出一个字符串。

1.5 FileWriter类

java.io.FileWriter类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。

构造方法

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。

  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

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

  • 构造举例,代码如下:

public class FileWriterConstructor {
    public static void main(String[] args) throws IOException {
   	 	// 使用File对象创建流对象
        File file = new File("day09\\a.txt");
        FileWriter fw = new FileWriter(file);
      
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("day09\\b.txt");
    }
}

基本写出数据

写出字符write(int b) 方法,每次可以写出一个字符数据,代码使用演示:

public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("day09\\fw.txt");     
      	// 写出数据
      	fw.write(97); // 写出第1个字符
      	fw.write('b'); // 写出第2个字符
      	fw.write('C'); // 写出第3个字符
      
      	/*
        【注意】关闭资源时,与FileOutputStream不同。
      	 如果不关闭,数据只是保存到缓冲区,并未保存到文件。
        */
        // fw.close();
    }
}
输出结果:
abC

小贴士:

  1. 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
  2. 未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。

关闭和刷新

因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

  • flush :刷新缓冲区,流对象可以继续使用。
  • close :关闭流,释放系统资源。关闭前会刷新缓冲区。

代码使用演示:

public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("day09\\fw.txt");
        // 写出数据,通过flush
        fw.write('刷'); // 写出第1个字符
        fw.flush();
        fw.write('新'); // 继续写出第2个字符,写出成功
        fw.flush();
      
      	// 写出数据,通过close
        fw.write('关'); // 写出第1个字符
        fw.close();
        fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
        fw.close();
    }
}

小贴士:

1)即便是flush方法写出了数据,操作的最后还是要调用close方法,释放系统资源。

2)注意:

close()和flush()方法的区别:

flush():刷新缓冲区。流对象还可以继续使用。

close():先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

写出其他数据

void write(String str):写一个字符串数据

void write(String str,int index,int len):写一个字符串中的一部分数据

​ 说明:

​ str 表示要写的字符串

​ index 表示从字符串哪个下标开始写

​ len 表示写的字符个数。

​ 注意:字符串可以看做是一个由多个字符组成的字符数组。

void write(int ch):写一个字符数据,这里写int类型的好处是既可以写char类型的数据,也可以写char对应的int类型的值。‘a’,97

void write(char[] chs):写一个字符数组数据

void write(char[] chs,int index,int len):写一个字符数组的一部分数据

​ 参数:

​ chs 表示写的字符数组

​ index 表示从数组哪个下标开始写

​ len写的字符个数

代码使用演示:

package com.itheima_01;
import java.io.FileWriter;
import java.io.IOException;

/*
 * void write(String str):写一个字符串数据
 * void write(String str,int index,int len):写一个字符串中的一部分数据
 * void write(int ch):写一个字符数据,这里写int类型的好处是既可以写char类型的数据,也可以写char对应的int类型的值。'a',97
 * void write(char[] chs):写一个字符数组数据
 * void write(char[] chs,int index,int len):写一个字符数组的一部分数据
 */
public class FileWriterDemo3 {
	public static void main(String[] args) throws IOException {
		//创建输出流对象
		FileWriter fw = new FileWriter("day09\\b.txt");
		
		//void write(String str):写一个字符串数据
		//fw.write("abcde");
		
		//void write(String str,int index,int len):写一个字符串中的一部分数据
		//fw.write("abcde",0,5);
		//fw.write("abcde",1,3);
		
		//void write(int ch):写一个字符数据,这里写int类型的好处是既可以写char类型的数据,也可以写char对应的int类型的值。'a',97
		//fw.write('a');
		//fw.write(97);
		
		//void write(char[] chs):写一个字符数组数据
		char[] chs = {'a','b','c','d','e'};
		//fw.write(chs);
		
		//void write(char[] chs,int index,int len):写一个字符数组的一部分数据
		//fw.write(chs,0,5);
		fw.write(chs,2,3);
		
		//释放资源
		fw.close();
	}
}

FileWriter写入换行以及向文本末尾追加

说明:操作类似于FileOutputStream。

操作代码演示如下:

package com.itheima_01;

import java.io.FileWriter;
import java.io.IOException;

public class FileWriterDemo4 {
	public static void main(String[] args) throws IOException {
		//创建输出流对象
		//FileWriter fw = new FileWriter("day09\\4.txt");
		FileWriter fw = new FileWriter("day09\\4.txt",true); //表示追加写入,默认是false
		
		for(int x=0; x<10; x++) {
			fw.write("hello"+x+"\r\n");
		}
		//释放资源
		fw.close();
	}
}

小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。

第二章 IO异常的处理

JDK7前处理

我们使用Java程序,操作的是Java程序以外的其他设备上的数据,都有可能发生异常问题。

我们在书写的Java程序读写其他设备上的数据时,都要考虑异常问题。这些异常一般开发中都要开发者自己处理掉,不能直接往声明。

建议使用try...catch...finally 代码块,处理异常部分,代码使用演示:

说明:这里拿字符输出流举例。

public class HandleException1 {
    public static void main(String[] args) {
      	// 声明变量
        FileWriter fw = null;
        try {
            //创建流对象
            fw = new FileWriter("day09\\fw.txt");
            // 写出数据
            fw.write("黑马程序员"); //黑马程序员
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

JDK7的处理(掌握)

如果涉及到关闭多个流的时候,我们使用上述jdk7之前的技术虽然也可以关闭资源,但是显得比较麻烦,在finally中代码显得比较冗余,所以在jdk7对于释放资源进行了优化,即以后关闭资源不用在finally中书写释放资源的代码了。这样对于程序员关于释放资源的代码写法更为简单了。

注意:此处只是优化释放资源的代码。

那么接下来我们来学习下jdk7是如何实现的。

首先在jdk7中出现了一个接口叫做:AutoCloseable

无论是字节流还是字符流都实现了这个接口。

举例:FileWriter

在这里插入图片描述

AutoCloseable 接口说明:

在这里插入图片描述

说明:

  • 只要IO流实现了这个接口,都可以完成自动释放资源.
  • jdk7提供了新的方式来处理释放资源的问题.

使用JDK7优化后的try-with-resource 语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。

格式:

try (创建流对象语句,如果多个流对象语句,使用';'隔开) {
	// 读写数据
} catch (IOException e) {
	e.printStackTrace();
}

说明:

​ 1)try后面书写了小括号(),括号里书写了流对象的创建。

​ 2)try后面的大括号中书写读取或者书写数据的逻辑代码。

​ 3)只要使用上述格式,资源就会被自动释放。

代码使用演示:

写数据模板代码:

public class HandleException2 {
    public static void main(String[] args) {
      	// 创建流对象
        try ( FileWriter fw = new FileWriter("fw.txt"); ) {
            // 写出数据
            fw.write("黑马程序员"); //黑马程序员
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

读写数据模板代码:

public class Demo {
    public static void main(String[] args) {
        try (FileWriter fw = new FileWriter("day09\\fw1.txt"); FileReader fr = new FileReader("day09\\fw.txt")) {
            //读数据
            int len=0;
            char[] ch=new char[1024];
            while ((len=fr.read(ch))!=-1) {
                fw.write(ch,0,len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

第三章 属性集(掌握)

3.1 概述

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

3.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路径下

在这里插入图片描述

2.创建对象

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

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

3.获取方法

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

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

    配置文件如下所示:

在这里插入图片描述

代码如下:

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);

    }
}

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章