主要内容
- 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
类是读取字符文件的便利类。构造时使用当前环境默认的字符编码和默认字节缓冲区。
小贴士:
- 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。idea中UTF-8
- 字节缓冲区:一个字节数组,用来临时存储字节数据。
注意:在计算机中所有数据在底层都是以字节数据存在的,即使是字符数据在最底层也是以字节数据存在的,因为计算机只识别字节数据。所以在字符流底层使用的是字节缓冲区,其实就是一个字节数组。
构造方法
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");
}
}
读取字符数据
-
读取字符:
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类型。
- 使用字符数组读取:
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
小贴士:
- 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。
- 未调用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);
}
}