目前我们是在内存存储数据的,在程序运行中进行处理,修改,运算等操作,这样的存储方式不能长久保存数据
想要长久保存数据我们可以将数据存储到文件中,磁盘中数据的形式就是文件,文件是数据的载体
关于File,IO流,我们需要学会什么
File类创建对象:
| 方法名称 | 说明 |
|---|---|
| public File(String pathname) | 根据文件路径创建文件对象 |
| public File(String parent, String child) | 从父路径名字符串和子路径名字符串创建文件对象 |
| public File(File parent, String child) | 根据父路径对应文件对象和子路径名字符串创建文件对象 |
public class FileDemo {
public static void main(String[] args) {
//1.创建文件对象(依据绝对路径)
//依据绝对路径有三种方式:
//方式一:反斜杠前面都再加一个反斜杠是因为可能文件夹名或文件名是n开头,而\n代表换行,就读取不到后续的路径了
File f = new File("D:\\study\\java\\Markdown\\javaSE\\images\\nscreen.png");
//方式二:(File.separator代表系统的分隔符)
File f22 = new File("D:" + File.separator + "study" +
File.separator + "java" + File.separator + "Markdown" +
File.separator + "javaSE" + File.separator + "images" +
File.separator + "nscreen.png");
//方式三:
File f33 = new File("D:/study/java/Markdown/javaSE/images/nscreen.png");
long size = f.length();//计算机的一切都是都字节组成的,所以这里拿到的是文件的字节大小
System.out.println(size);
System.out.println(f22.length());
System.out.println(f33.length());
//2.创建文件对象(依据相对路径,相对路径一般用来定位模块中的文件)
File f2 = new File("Demo19-file-io/src/data.txt");
System.out.println(f2.length());
//3.File创建对象,可以是文件也可以是文件夹,下面用创建一个文件夹对象
File f3 = new File("D:\\study\\java\\Markdown\\javaSE\\images");
System.out.println(f3.length());
System.out.println(f3.exists());//判断该路径下的文件夹是否存在
}
}
注意点:
| 方法名称 | 说明 |
|---|---|
| public boolean isDirectory() | 测试此抽象路径名表示的File是否为文件夹 |
| public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
| public boolean exists() | 测试此抽象路径名表示的File是否存在 |
| public String getAbsolutePath() | 返回此抽象路径名的绝对路径字符串 |
| public String getPath() | 将此抽象路径名转换为路径名字符串 |
| public String getName() | 返回由此抽象路径名表示的文件或文件夹名称(文件的话带后缀) |
| public long lastModified() | 返回文件最后修改的时间毫秒值 |
public class FileDemo02 {
public static void main(String[] args) {
// 1.绝对路径创建一个文件对象
File f1 = new File("Demo19-file-io/src/data.txt");
// a.获取它的绝对路径。
System.out.println(f1.getAbsolutePath());
// b.获取文件定义的时候使用的路径。
System.out.println(f1.getPath());
// c.获取文件的名称:带后缀。
System.out.println(f1.getName());
// d.获取文件的大小:字节个数。
System.out.println(f1.length()); // 字节大小
// e.获取文件的最后修改时间
long time = f1.lastModified();//获取的是毫秒值
System.out.println("最后修改时间:" + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(time));
// f、判断文件是文件还是文件夹
System.out.println(f1.isFile()); // true
System.out.println(f1.isDirectory()); // false
File file = new File("D:/");
System.out.println(file.isFile()); // false
System.out.println(file.isDirectory()); // true
System.out.println(file.exists()); // true
File file1 = new File("D:/aaa");
System.out.println(file1.isFile()); // false
System.out.println(file1.isDirectory()); // false
System.out.println(file1.exists()); // false
}
}
File类创建文件的功能
| 方法名称 | 说明 |
|---|---|
| public boolean createNewFile() | 创建一个新的空的文件 |
| public boolean mkdir() | 只能创建一级文件夹 |
| public boolean mkdirs() | 可以创建多级文件夹 |
File类删除文件的功能
| 方法名称 | 说明 |
|---|---|
| public boolean delete() | 删除由此抽象路径名表示的文件或空文件夹 |
//以下代码运行后我的计算机中
//1.创建了D:\study\java\Markdown\javaSE下的ccc文件夹及其子文件夹
//2.删除了D:\study\java\Markdown\javaSE\images下的idea.png
//3.其他无改变
public class FileDemo03 {
public static void main(String[] args) throws IOException {
File f = new File("Demo19-file-io\\src\\data.txt");
// a.创建新文件,创建成功返回true(几乎不用这个API,因为以后通过IO流写入数据时如果文件不存在会自动创建新文件)
System.out.println(f.createNewFile());//false(该文件已存在)
File f1 = new File("Demo19-file-io\\src\\data02.txt");
System.out.println(f1.createNewFile());//true
// b.mkdir创建一级目录
File f2 = new File("D:\\study\\java\\Markdown\\javaSE\\aaa");
System.out.println(f2.mkdir());//true
// c.mkdirs创建多级目录(重点)
File f3 = new File("D:\\study\\java\\Markdown\\javaSE\\ccc\\ddd\\eee\\ffff");
System.out.println(f3.mkdir());//false,因为mkdir不支持多级创建
System.out.println(f3.mkdirs());//true
// d.删除文件或者空文件夹
System.out.println(f1.delete());//true
File f4 = new File("D:\\study\\java\\Markdown\\javaSE\\images\\idea.png");
System.out.println(f4.delete());//true,占用一样可以删除
// 只能删除空文件夹,不能删除非空文件夹.
File f5 = new File("D:\\study\\java\\Markdown\\javaSE\\aaa");
System.out.println(f5.delete());//true
File f6 = new File("D:\\study\\java\\Markdown\\javaSE\\ccc");
System.out.println(f6.delete());//false
}
}
| 方法名称 | 说明 |
|---|---|
| public String[] list() | 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回 |
| public File[] listFiles() | 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回 |
listFiles方法注意事项
public class FileDemo04 {
public static void main(String[] args) {
File f1 = new File("D:\\study\\java\\Markdown\\javaSE\\images");
//1.使用list方法遍历
String[] names = f1.list();
for (String name : names) {
System.out.println(name);
}
//2.使用listFiles遍历
File[] files = f1.listFiles();
for (File f : files) {
System.out.println(f.getAbsolutePath());
}
//注意事项
//1.调用者不存在
File f2 = new File("D:/ddd");
File[] files2 = f2.listFiles();
System.out.println(files2);//null
//2.调用者是一个文件
File f3 = new File("D:\\study\\java\\Markdown\\javaSE\\images\\出师表.txt");
File[] files3 = f3.listFiles();
System.out.println(files3);//null
//3.调用者是一个空文件夹
File f4 = new File("D:\\study\\java\\Markdown\\javaSE\\images\\aaa");
File[] files4 = f4.listFiles();
System.out.println(Arrays.toString(files4));//[]
}
}
1.方法直接调用自己或者间接调用自己的形式称为方法递归
2.递归的形式:
3.递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象
public class RecursionDemo02 {
public static void main(String[] args) {
System.out.println(f(5));
}
public static int f(int n){
if(n == 1){
return 1;
}else {
return f(n - 1) * n;
}
}
}
递归算法三要素:

猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个,第二天又吃了前一天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个,以后每天都是吃前一天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个,等到第10天的时候发现桃子只有1个了
问:猴子第一天摘了多少个桃子
推理公式:
f(x) - f(x)/2 - 1 = f(x+1)—>f(x) = 2f(x+1) + 2
也就是求f(1)
我们已经知道f(10) = 1
所以满足三要素:有递归公式,有终点,递归的方向是走向终结点f(10)的,所以可以用递归
public class RecursionDemo04 {
public static void main(String[] args) {
System.out.println(f(1));
}
public static int f(int n){
if(n == 10){
return 1;
}else {
return 2 * f(n + 1) + 2;
}
}
}
在上述的案例中递归算法都是针对存在规律化的递归问题,但实际场景中有很多问题是非规律化的递归问题,这种就只能自己看着办,需要流程化的编程思维
需求:从D:盘中,搜索出某个文件名称并输出绝对路径
分析:
- 先定位出的应该是一级文件对象
- 遍历全部一级文件对象,判断是否是文件
- 如果是文件,判断是否是自己想要的
- 如果是文件夹,需要继续递归进去重复上述过程
//目标:去E盘搜索WeChat.exe
public class RecursionDemo05 {
public static void main(String[] args) {
// 2、传入目录 和 文件名称
searchFile(new File("E:/") , "WeChat.exe");
}
/**
* 1、搜索某个目录下的全部文件,找到我们想要的文件。
* @param dir 被搜索的源目录
* @param fileName 被搜索的文件名称
*/
public static void searchFile(File dir,String fileName){
// 3、判断dir是否是目录(用户初次传时,可能给文件对象
// 赋null,或者传一个不存在的路径,或者传入的路径不是文件夹)
if(dir != null && dir.isDirectory()){
// 4、提取当前目录下的一级文件对象
File[] files = dir.listFiles();//files为null三种情况:dir的路径不存
// 在,dir是一个文件,dir需要权限才能进入,前两个已经在外层if语句判断过了所以
// 不再可能出现,目前引起files为null的原因只有第三种,将在下一下if语句判断
// 5、进行判断
if(files != null && files.length > 0) {//可能为null或空数组
for (File file : files) {
// 6、判断当前遍历的一级文件对象是文件 还是 目录
if(file.isFile()){
// 7、是不是咱们要找的,是把其路径输出即可
if(file.getName().contains(fileName)){//这是模糊查找,也就是即使我传的fileName是eChat.exe也一样可以找到
System.out.println("找到了:" + file.getAbsolutePath());
// 启动它。
try {
Runtime r = Runtime.getRuntime();
r.exec(file.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}else {
// 8、是文件夹,需要继续递归寻找
searchFile(file, fileName);
}
}
}
}else {
System.out.println("对不起,当前搜索的位置不是文件夹!");
}
}
}
啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子
public class RecursionDemo06 {
// 定义一个静态的成员变量用于存储可以买的酒数量
public static int totalNumber; // 总数量
public static int lastBottleNumber; // 记录每次剩余的瓶子个数
public static int lastCoverNumber; // 记录每次剩余的盖子个数
public static void main(String[] args) {
// 1、拿钱买酒
buy(10);
System.out.println("总数:" + totalNumber);
System.out.println("剩余盖子数:" + lastCoverNumber);
System.out.println("剩余瓶子数:" + lastBottleNumber);
}
public static void buy(int money){
// 2、看可以立马买多少瓶
int buyNumber = money / 2; // 5
totalNumber += buyNumber;
// 3、把盖子 和瓶子换算成钱
// 统计本轮总的盖子数 和 瓶子数
int coverNumber = lastCoverNumber + buyNumber;
int bottleNumber = lastBottleNumber + buyNumber;
// 统计可以换算的钱
int allMoney = 0;
if(coverNumber >= 4){
allMoney += (coverNumber / 4) * 2;
}
lastCoverNumber = coverNumber % 4;
if(bottleNumber >= 2){
allMoney += (bottleNumber / 2) * 2;
}
lastBottleNumber = bottleNumber % 2;
if(allMoney >= 2){
buy(allMoney);
}
Integer[] arr2 = new Integer[]{11, 22, 33};
Arrays.sort(arr2);
}
}
本来我想的是传入11和传入10结果一样以为有bug,后来一想,如果传入11,买酒话10元,剩下的1元是花不出去的,实际上就和传入10元是一样的
由上两点可得结论:计算机底层可以将二进制转换为十进制进而表示十进制编号,所以计算机可以给人类字符进行编号存储,这套编号规则就是字符集
1.ASCII字符集:
2.GBK
3.Unicode码表:
Unicode(又称统一码,万国码,单一码):是计算机科学领域里的一项业界字符编码标准
容纳上世界上大多数国家的所有常见文字和符号
Unicode会先通过UTF-8,UTF-16,以及UTF-32的方式编码成二进制再存储到计算机,其中最常见的是UTF-8

先从Unicode中找到每一个字符对应的编号
通过UTF-8的方式将编号编码成二进制
将二级制存储到计算机中
UTF-8不是固定字长编码的,而是一种变长的编码方式,它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度
字符集注意点:
String编码
| 方法名称 | 说明 |
|---|---|
| byte[] getBytes() | 使用平台默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中 |
| byte[] getBytes(String charsetName) | 使用指定字符集将该String编码为一系列字节,将结果存储到新的字节数组中 |
String解码
| 构造器 | 说明 |
|---|---|
| String(byte[] bytes) | 通过使用平台默认字符集解码指定的字符数组来构造新的String |
| String(byte[] bytes, String charsetName) | 通过指定默认字符集解码指定的字符数组来构造新的String |
public class Test {
public static void main(String[] args) throws Exception {
String name = "abc我爱你中国";//18
byte[] bytes = name.getBytes();//idea右下角是UTF-8,所以这里默认使用UTF-8编码
System.out.println(bytes.length);
System.out.println(Arrays.toString(bytes));
String rs = new String(bytes);
System.out.println(rs);
}
}
IO流也称为输入,输出流,就是用来读写数据的
IO流的分类:
总结流的四大类:
IO流体系:
| 构造器 | 说明 |
|---|---|
| public FileInputStream(File file) | 创建直接输入流管道与源文件对象接通 |
| public FileInputStream(String pathname) | 创建字节输入流管道与源文件路径接通 |
| 方法名称 | 说明 |
|---|---|
| public int read() | 每次读取一个字节返回,如果字节已经没有可读的返回-1 |
| public int read(byte[] buffer) | 每次读取一个字节数组返回,如果字节已经没有可读的返回-1 |
1.看一下public FileInputStream(String pathname)方法源码:
public FileInputStream(String name) throws FileNotFoundException {
this(name != null ? new File(name) : null);
}
可以看出这其实就是方法内部new了一个File对象后传给public FileInputStream(File file)方法,所以我们以后就用public FileInputStream(String pathname)方法,不需要再自己new File了,方便!
2.为什么public int read()方法返回值是int呢
3.关于public int read(byte[] buffer)方法:
先看一下源码:
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
为什么public int read()返回值是int类型,而public int read(byte[] buffer)传参不是int类型的数组呢
调用read()方法没读取到字节分(返回-1)两种情况:
综上所述,给这个方法传入一个byte类型数组,每次读取到的n个字节(n是数组长度)都放到这个数组中,然后返回这个数组中字节的个数,当然,如果已经没有字节可读的话返回-1,并且,如果最后一次读取到的字节个数m是小于n的,则数组后n-m个字节与倒数第二次的字节相同

1.每次读取一个字节:
public class FileInputStreamDemo01 {
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream("Demo19-file-io\\src\\data.txt");
int b;
while (( b = is.read() ) != -1){
System.out.print((char) b);
}
}
}
2.每次读取一个字节数组:
public class FileInputStreamDemo02 {
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream("Demo19-file-io\\src\\data.txt");
byte[] buffer = new byte[3];
int len;
while ((len = is.read(buffer)) != -1) {
//String(byte[] bytes, int offset, int length)
System.out.print(new String(buffer, 0 , len));//以默认字符集进行解码
}
}
}
如何使用字节输入流读取中文内容输出不乱码呢:
3.一次将文件中字节读取完:
方式一:自己定义一个字节数组与文件的大小一样大,然后使用读取字节数组的方法,一次性读取完成
方式二:官方为字节输入流InputStream提供了如下API可以直接把文件的全部数据读取到一个字节数组中
| 方法名称 | 说明 |
|---|---|
| pubic byte[] readAllByes() throws IOException | 直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回 |
public class FileInputStreamDemo03 {
public static void main(String[] args) throws Exception {
File f = new File("Demo19-file-io\\src\\data.txt");
InputStream is = new FileInputStream(f);
byte[] buffer = new byte[(int) f.length()];//创建数组时长度必须是int型,所以f.length()需要强转
int len = is.read(buffer);
System.out.println("读取了多少个字节:" + len);
System.out.println("文件大小:" + f.length());
System.out.println(new String(buffer));
// 读取全部字节数组
byte[] buffer2 = is.readAllBytes();
System.out.println(new String(buffer2));
}
}
| 构造器 | 说明 |
|---|---|
| public FileOutputStream(File file) | 创建一个字节输出流管道通向目标文件对象 |
| public FileOutputStream(String file) | 创建一个字节输出流管道通向目标文件路径 |
| public FileOutputStream(File file , boolean append) | 创建一个追加数据的字节输出流管道通向目标文件对象 |
| public FileOutputStream(String file , boolean append) | 创建一个追加数据的字节输出流管道通向目标文件路径 |
| 方法名称 | 说明 |
|---|---|
| public void write(int a) | 写一个字节出去 |
| public void write(byte[] buffer) | 写一个字节数组出去 |
| public void write(byte[] buffer, int pos, int len) | 写一个字节数组的一部分出去 |
为什么public void write(int a)方法参数是int类型而public void write(byte[] buffer)参数不是int类型的数组:
等我回过头来发现因为多态我看的源码是OutputStream的源码啊,而FileOutputStream的源码调用的是其他语言的,没有方法体,下面这些可以不看了,但留着是以后的思路,我这里就不删掉了
大致看一下public void write(byte[] buffer)源码
public void write(byte b[]) throws IOException {
write(b, 0, b.length);
}
public void write(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
可以看出来实际底层还是通过第15行write(b[off + i]);向文件中写入数据,而write(int a)方法的形参是int类型,所以会将b[off + i]由byte类型自动强制转换为int类型并传给write(int a)方法
还有一个比较难懂的点:为什么public void write(int a)方法参数类型不是byte?目前不太动能
流的关闭与刷新:
| 方法 | 说明 |
|---|---|
| flush() | 刷新流,还可以继续写数据 |
| close() | 关闭流,释放资源,在关闭前会先刷新.一旦关闭,就不能再写数据 |
java中为何要关闭流:
若是你再堆区new一个对象的时候,若是等号左边是对端口、显存、文件进行操作的时候,超出了虚拟机可以释放资源的界限,虚拟机就没法利用垃圾回收机制对堆区占用的资源进行释放
public class OutputStreamDemo04 {
public static void main(String[] args) throws Exception {
// 1、创建一个文件字节输出流管道与目标文件接通
// OutputStream os = new FileOutputStream("Demo19-file-io\\src\\data04.txt" , true);// 追加数据管道
FileOutputStream os = new FileOutputStream("Demo19-file-io\\src\\data04.txt"); //先清空之前的数据,写新数据进入
//这行代码与案例无关,只是想告诉:创建输出流管道时如果文件不存在,会自己创建)
System.out.println(new File("Demo19-file-io\\src\\data04.txt").exists());//true
// 2、写数据出去
// a.public void write(int a):写一个字节出去
os.write('a');
os.write(98);
os.write("\r\n".getBytes()); // 换行(getBytes()方法以默认字符集进行编码)
// b.public void write(byte[] buffer):写一个字节数组出去。
byte[] buffer = {'a' , 97, 98, 99};
os.write(buffer);
os.write("\r\n".getBytes());
byte[] buffer2 = "我是中国人".getBytes();
os.write(buffer2);
os.write("\r\n".getBytes()); // 换行
// c. public void write(byte[] buffer , int pos , int len):写一个字节数组的一部分出去。
byte[] buffer3 = {'a',97, 98, 99};
os.write(buffer3, 0 , 3);
os.write("\r\n".getBytes()); // 换行
// os.flush();
os.close();
}
}
字节流可以进行任何文件的拷贝,因为文件拷贝就是将文件一的字节读取出来再写入文件二,期间不涉及到我们操作编码和解码(没有new String()和"".getBytes()),只要保证前后文件格式,编码一致就没问题了,即使是拷贝全是中文的txt文件仍不会乱码
public class CopyDemo05 {
public static void main(String[] args) {
try {
// 1、创建一个字节输入流管道与原视频接通
InputStream is = new FileInputStream("Demo19-file-io\\src\\data.txt");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("Demo19-file-io\\src\\data05.txt");
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[5];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
System.out.println("复制完成了!");
// 4、关闭流(后开的先关)
os.close();
is.close();
} catch (Exception e){
e.printStackTrace();
}
}
}
以前我们是在程序基本业务逻辑执行完才进行资源释放的,那如果业务逻辑出了问题,就不会进行资源释放了呀,所以需要进行改进
public class TryCatchFinallyDemo1 {
public static void main(String[] args) {
InputStream is = null;//需要扩大变量的作用域
OutputStream os = null;//同上
try {
//System.out.println( 10 / 0);
// 1、创建一个字节输入流管道与原视频接通
is = new FileInputStream("Demo19-file-io\\src\\data.txt");
// 2、创建一个字节输出流管道与目标文件接通
os = new FileOutputStream("Demo19-file-io\\src\\data05.txt");
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
System.out.println("复制完成了!");
} catch (Exception e){
e.printStackTrace();
} finally {
System.out.println("========finally=========");
try {
// 4、关闭流。
if(os!=null)os.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(is != null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println(test(10, 2));//没有输出5而是输出100
}
public static int test(int a , int b){
try {
int c = a / b;
return c;
}catch (Exception e){
e.printStackTrace();
return -111111; // 计算出现bug.
}finally {
System.out.println("--finally--");
return 100;
}
}
}
注意点:
JDK7和JDK9都简化了资源释放操作
JDK7改进方案:
try(定义流对象){
可能出现异常的代码
}catch(异常类名 变量名){
异常的处理代码
}资源用完最终自动释放
JDK9改进方案(很鸡肋,唯一作用就是如果流对象是别人传过来的可以用这种方法,但用JDK7的也可以:InputStream is = xingcan_liu):
定义输入流对象;
定义输出流对象;
try(输入流对象 ; 输出流对象){
可能出现异常的代码
}catch(异常类名 变量名){
异常的处理代码
}资源用完最终自动释放
注意:JDK7及JDK9的()中只能放置资源对象,否则报错,什么是资源对象呢:
资源都是实现了Closeable/AutoCloseable接口的对象
public abstract class InputStream implements Closeable{}
public abstract class OutputStream implements Closeable, Flushable{}
public class TryCatchResouceDemo2 {
public static void main(String[] args) {
try (
//这里面只能放置资源对象,用完自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
// 1、创建一个字节输入流管道与原视频接通
InputStream is = new FileInputStream("Demo19-file-io\\src\\data.txt");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("Demo19-file-io\\src\\data05.txt");
// int age = 23; // 报错,这里只能放资源
MyConnection connection = new MyConnection(); //最终会自动调用资源的close方法
) {
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = is.read(buffer)) != -1){
os.write(buffer, 0 , len);
}
System.out.println("复制完成了!");
} catch (Exception e){
e.printStackTrace();
}
}
}
class MyConnection implements AutoCloseable{
@Override
public void close() throws IOException {
System.out.println("连接资源被成功释放了!");
}
}
| 构造器 | 说明 |
|---|---|
| public FileReader(File file) | 创建字符输入流管道与源文件对象接通 |
| public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
| 方法名称 | 说明 |
|---|---|
| public int read()返回的是字符编号 | 每次读取一个字符返回,如果字符已经没有可读的返回-1 |
| public int read(char[] buffer) | 每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回-1 |
1.一次读取一个字符
public class FileReaderDemo01 {
public static void main(String[] args) throws Exception {
FileReader fr = new FileReader("Demo19-file-io\\src\\data.txt");
int code;
while ((code = fr.read()) != -1){
System.out.print((char) code);//不需要输出时换行,因为读取文件字符时如果文本中有换行符,那么也会读取出来换行符(以前学的字节流同理)
}
}
}
public class FileReaderDemo02 {
public static void main(String[] args) throws Exception {
Reader fr = new FileReader("Demo19-file-io\\src\\data.txt");
char[] buffer = new char[1024]; // 1K字符
int len;
while ((len = fr.read(buffer)) != -1) {
String rs = new String(buffer, 0, len);
System.out.print(rs);
}
}
}
| 构造器 | 说明 |
|---|---|
| public FileWriter(File file) | 创建一个字符输出流管道通向目标文件对象 |
| public FileWriter(String filePath) | 创建一个字符输出流管道通向目标文件路径 |
| public FileWriter(File file,boolean append) | 创建一个追加数据的字符输出流管道通向目标文件对象 |
| public FileWriter(String filePath,boolean append) | 创建一个追加数据的字符输出流管道通向目标文件路径 |
| 方法名称 | 说明 |
|---|---|
| void write(int c) | 写一个字符 |
| void write(char[] cbuf) | 写入一个字符数组 |
| void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
| void write(String str) | 写一个字符串 |
| void write(String str, int off, int len) | 写一个字符串的一部分 |
| 方法 | 说明 |
|---|---|
| flush() | 刷新流 |
| close() | 关闭流 |
public class FileWriterDemo03 {
public static void main(String[] args) throws Exception {
// 1、创建一个字符输出流管道与目标文件接通
// Writer fw = new FileWriter("file-io-app/src/out08.txt"); // 覆盖管道,每次启动都会清空文件之前的数据
Writer fw = new FileWriter("Demo19-file-io\\src\\data06.txt"); // 覆盖管道,每次启动都会清空文件之前的数据
// a.public void write(int c):写一个字符出去
fw.write(98);
fw.write('a');
fw.write('徐');
fw.write("\r\n"); // 换行
// b.public void write(String c)写一个字符串出去
fw.write("abc我是中国人");
fw.write("\r\n");
// c.public void write(char[] buffer):写一个字符数组出去
char[] chars = "abc我是中国人".toCharArray();
fw.write(chars);
fw.write("\r\n");
// d.public void write(String c ,int pos ,int len):写字符串的一部分出去
fw.write("abc我是中国人", 0, 5);
fw.write("\r\n");
// e.public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去
fw.write(chars, 3, 5);
fw.write("\r\n");
// fw.flush();// 刷新后流可以继续使用
fw.close(); // 关闭包含刷新,关闭后流不能使用
}
}
缓冲流也称为高效流或高级流,之前学习的字节流,字符流可以称为原始流
作用:缓冲流自带缓冲区,可以提高原始字节流,字符流读写数据的性能
缓冲流的基本原理:创建流对象时候,会创建一个内置的默认大小的缓冲区,通过缓冲区读写,减少系统IO次数,从而提高读写的效率
字节缓冲流性能优化原理:
| 构造器 | 说明 |
|---|---|
| public BufferedInputStream(InputStream is) | 可以把低级的字节输入流包装成一个高级的缓冲字节输入流管道,从而提高字节输入流读数据的性能 |
| public BufferedOutputStream(OutputStream os) | 可以把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能 |
public class ByteBufferDemo {
public static void main(String[] args) {
try (
// 这里面只能放置资源对象,用完会自动关闭:自动调用资源对象的close方法关闭资源(即使出现异常也会做关闭操作)
// 1、创建一个字节输入流管道与原视频接通
InputStream is = new FileInputStream("Demo20-io2\\src\\data.txt");
// a.把原始的字节输入流包装成高级的缓冲字节输入流
InputStream bis = new BufferedInputStream(is);
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("Demo20-io2\\src\\data01.txt");
// b.把字节输出流管道包装成高级的缓冲字节输出流管道
OutputStream bos = new BufferedOutputStream(os);
) {
// 3、定义一个字节数组转移数据
byte[] buffer = new byte[1024];
int len; // 记录每次读取的字节数。
while ((len = bis.read(buffer)) != -1){
bos.write(buffer, 0 , len);
}
System.out.println("复制完成了!");
} catch (Exception e){
e.printStackTrace();
}
}
}
字符缓冲输入流:BufferedReader,提高字符输入流读取数据的性能,除拥有其祖宗类Reader的方法外还多了按照行读取数据的功能
字符缓冲输出流:BufferedWriter,提高字符写入数据的性能,除拥有其祖宗类Writer的方法外还多了换行功能
优化原理与字节缓冲流类似,不一样的点就是字节缓冲流知道8KB(8K个字节)缓冲区,而字符缓冲流自带8K(8K个字符)缓冲区
| 构造器 | 说明 |
|---|---|
| pubic BufferedReader(Reader r) | 可以把低级的字符输入流包装成一个高级的缓冲字符输入流管道,从而提高字符输入流读数据的性能 |
| public BufferedWriter(Writer w) | 可以把低级的字符输出流包装成一个高级的缓冲字符输出流管道,从而提高字符输出流读数据的性能 |
字符缓冲输入流新增功能:
| 方法 | 说明 |
|---|---|
| public String readLine() | 读取一行数据返回,无行可读返回null |
字符缓冲输出流新增功能:
| 方法 | 说明 |
|---|---|
| public void newLine() | 换行操作 |
newLine()源码:实际上就是内部写了一个换行符
public void newLine() throws IOException {
write(lineSeparator);
}
案例需求:把《出师表》的文章顺序进行恢复到一个新的文件中
分析:
1.定义一个缓冲字符输入流管道与源文件接通
2.定义一个List集合存储读取的每行数据
3.定义一个循环按照行读取数据,存到List集合中
4.对List集合中的每行数据按照首字母编号升序排序
5.定义一个缓冲字符输出管道与目标文件接通
6.遍历List集合中的每个元素,用缓冲输出管道写出并换行
public class BufferedCharTest3 {
public static void main(String[] args) {
try(
// 1、创建缓冲字符输入流管道与源文件接通
BufferedReader br = new BufferedReader(new FileReader("Demo20-io2\\src\\csb.txt"));
// 5、定义缓冲字符输出管道与目标文件接通
BufferedWriter bw = new BufferedWriter(new FileWriter("Demo20-io2/src/new.txt"));
) {
// 2、定义一个List集合存储每行内容
List<String> data = new ArrayList<>();
// 3、定义循环,按照行读取文章
String line;
while ((line = br.readLine()) != null){
data.add(line);
}
// System.out.println(data);
// 4、排序
// 自定义排序规则
List<String> sizes = new ArrayList<>();
Collections.addAll(sizes, "一","二","三","四","五","陆","柒","八","九","十","十一");
Collections.sort(data, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// o1 八.,....
// o2 柒.,....
return sizes.indexOf(o1.substring(0, o1.indexOf(".")))
- sizes.indexOf(o2.substring(0, o2.indexOf(".")));
}
});
// System.out.println(data);
// 6、遍历集合中的每行文章写出去,且要换行
for (String datum : data) {
bw.write(datum);
bw.newLine(); // 换行
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
之前我们使用字符流读取中文是否有乱码
如果代码编码和文件编码不一致,使用字符流直接读取还能不乱码吗?
如何解决呢:
Reader
Writer
字符输入转换流:InputStreamReader,可以把原始的字节流按照指定编码转换成字符输入流
作用:以指定编码将原始字节流转换成字符输入流,如此字符流中的字符不会乱码
| 构造器 | 说明 |
|---|---|
| public InputStreamReader(InputStream is) | 可以把原始的字节流按照代码默认编码转换成字符输入流.几乎不用,因为这个与默认的FileReader一样 |
| public InputStreamReader(InputStream is, String charset) | 可以把原始的字节流按照指定编码转换成字符输入流.(重点) |
为什么说public InputStreamReader(InputStream is)与默认的FileReader方法结果一样呢,看FileReader源码:
public FileReader(String fileName) throws FileNotFoundException {
super(new FileInputStream(fileName));
}
public InputStreamReader(InputStream in) {......}
可以知道FileReader方法最后实际上就是套娃式调用了public InputStreamReader(InputStream is)方法
执行的流程:
1.从文件中读取字节输入流
2.将字节输入流转换为指定编码格式的字符输入流并写入缓冲区
3.从缓冲流读取字符
public class InputStreamReaderDemo01 {
public static void main(String[] args) throws Exception {
//1.从文件中读取原始字节流
InputStream is = new FileInputStream("D:\\study\\java\\Markdown\\javaSE\\images\\luanma.txt");
//2.把原始字节流转换成字符输入流
Reader isr = new InputStreamReader(is , "GBK"); //以指定的GBK编码转将原始字节流换成字符输入流
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
}
}
字符输出转换流:OutputStreamReader,可以把字节输出流按照指定编码转换成字符输出流
作用:以指定编码把字节输出流转换成字符输出流,从而可以指定写出去的字符编码
| 构造器 | 说明 |
|---|---|
| public OutputStreamReader(OutputStream os) | 可以把原始的字节输出流按照代码默认编码转换成字符输出流.几乎不用 |
| public OutputStreamReader(OutputStream os, String charset) | 可以把原始的字节输出流按照指定编码转换成字符输出流.(重点) |
public class OutputStreamWriterDemo02 {
public static void main(String[] args) throws Exception {
// 1、定义一个字节输出流
OutputStream os = new FileOutputStream("Demo20-io2\\src\\data02.txt");
// 2、把原始的字节输出流转换成字符输出流
// Writer osw = new OutputStreamWriter(os); // 以默认的UTF-8写字符出去 跟直接写FileWriter一样
Writer osw = new OutputStreamWriter(os, "GBK"); // 指定GBK的方式写字符出去
// 3、把低级的字符输出流包装成高级的缓冲字符输出流。
BufferedWriter bw = new BufferedWriter(osw);
bw.write("我爱中国1~~");
bw.write("我爱中国2~~");
bw.write("我爱中国3~~");
bw.close();//因为os和osw管道是交给bw管理的,只要关了bw,伴随着也会关os和osw
}
}
1.对象序列化:
| 构造器 | 说明 |
|---|---|
| public ObjectOutputStream(OutputStream out) | 把低级字节输出流包装成高级的对象字节输出流 |
ObjectOutputStream序列化方法:
| 方法名称 | 说明 |
|---|---|
| public final void writeObject(Object object) | 把对象写出去到对象序列化流的文件中去 |
2.对象反序列化:
| 构造器 | 说明 |
|---|---|
| public ObjectInputStream(InputStream out) | 把低级字节输入流包装成高级的对象字节输入流 |
ObjectInputStream反序列化方法:
| 方法名称 | 说明 |
|---|---|
| public Object readObject() | 把存储到磁盘文件中去的对象数据恢复成内存中的对象返回 |
public class ObjectOutputStreamDemo1 {
public static void main(String[] args) throws Exception {
// 1、创建学生对象
Student s = new Student("陈磊","1314520");
// 2、对象序列化:使用对象字节输出流包装字节输出流管道
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Demo20-io2/src/obj.txt"));
// 3、直接调用序列化方法
oos.writeObject(s);
// 4、释放资源
oos.close();
System.out.println("序列化完成了~~");
// 1、创建对象字节输入流管道包装低级的字节输入流管道
ObjectInputStream is = new ObjectInputStream(new FileInputStream("Demo20-io2/src/obj.txt"));
// 2、调用对象字节输入流的反序列化方法
Student s2 = (Student) is.readObject();
System.out.println(s2);
}
}
//对象如果要序列化,必须实现Serializable序列化接口
public class Student implements Serializable {
// 申明序列化的版本号码
// 序列化的版本号与反序列化的版本号必须一致才不会出错!
private static final long serialVersionUID = 1;
private String name;
// transient修饰的成员变量不参与序列化了
private transient String passWord;
public Student(String name, String passWord) {
this.name = name;
this.passWord = passWord;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", passWord='" + passWord + '\'' +
'}';
}
}
注意:
打印流可以实现打印什么数据就是什么数据,例如打印97写出去就是97,打印boolean的true,写出去就是true
打印流一般是指:PrintStream,PrintWriter两个类
打印流是目前为止最方便,最高效的打印数据到文件中的流,为什么说最方便,最高效呢,看public PrintStream(String filepath)的源码(PrintWriter也是同理):
public PrintStream(String fileName) throws FileNotFoundException {
this(false, new FileOutputStream(fileName));
}
private PrintStream(boolean autoFlush, OutputStream out) {
super(out);
this.autoFlush = autoFlush;
this.charOut = new OutputStreamWriter(this);
this.textOut = new BufferedWriter(charOut);
}
先说高效:
再说方便:
总结一下:我们以前学的其他高级流(缓冲流,转换流,对象字节输入/出流)都只能通向低级流,且没有自己搞缓冲区,而打印流这种高级流则可以直接通向文件对象或文件路径,且自己搞了缓冲区,方便,高效!
1.PrintStream(OutputStream的孙子类)
| 构造器 | 说明 |
|---|---|
| public PrintStream(OutputStream os) | 打印流直接通向字节输出流管道 |
| public PrintStream(File f) | 打印流直接通向文件对象 |
| public PrintStream(String filepath) | 打印流直接通向文件伦路径 |
| 方法 | 说明 |
|---|---|
| public void print(Xxx xx) | 打印任意类型的数据出去 |
public class PrintDemo1 {
public static void main(String[] args) throws Exception {
// 创建一个打印流对象
// PrintStream ps = new PrintStream(new FileOutputStream("Demo20-io2/src/ps.txt"));
// PrintStream ps = new PrintStream(new FileOutputStream("Demo20-io2/src/ps.txt" , true)); // 追加数据,在低级管道后面加True
PrintStream ps1 = new PrintStream("Demo20-io2/src/ps.txt");
ps1.println(97);
ps1.println('a');
ps1.println(23.3);
ps1.println(true);
ps1.println("我是打印流ps1输出的,我是啥就打印啥");
//相比于PrintWriter的独特点:
ps1.write(97);//写一个字节
ps1.write("我爱中国".getBytes());//写一个字节数组
ps1.close();
}
}
2.PrintWriter(Writer的子类)
| 构造器 | 说明 |
|---|---|
| public PrintWriter(OutputStream os) | 打印流直接通向字节输出流管道 |
| public PrintWriter(Writer w) | 打印流直接通向字符输出流管道 |
| public PrintWriter(File f) | 打印流直接通向文件对象 |
| public PrintWriter(String filepath) | 打印流直接通向文件伦路径 |
| 方法 | 说明 |
|---|---|
| public void print(Xxx xx) | 打印任意类型的数据出去 |
PrintStream和PrintWriter的区别:
public class PrintDemo11 {
public static void main(String[] args) throws Exception {
PrintWriter ps2 = new PrintWriter("Demo20-io2/src/ps2.txt"); //打印功能上与PrintStream的使用没有区别
ps2.println(97);
ps2.println('a');
ps2.println(23.3);
ps2.println(true);
ps2.println("我是打印流ps2输出的,我是啥就打印啥");
//相比于PrintStream的独特点:
ps2.write(97);//写一个字符
ps2.write('中');//写一个字符
ps2.write("我爱你中国");//写一个字符串
ps2.write("我是中国人".toCharArray());//写一个字符数组
ps2.close();
}
}
输出语句重定向属于打印流的一种应用,可以把输出语句打印位置改到文件
public class PrintDemo2 {
public static void main(String[] args) throws Exception {
System.out.println("锦瑟无端五十弦");
// 改变输出语句的位置(重定向)
PrintStream ps = new PrintStream("Demo20-io2/src/log.txt");
System.setOut(ps); //给out对象重新赋值
System.out.println("庄生晓梦迷蝴蝶");
}
}

Properties是HashTable的子类,其实就是一个Map集合,但是我们一般不会当集合使用,因为HashMap更好用
Properties核心作用:
Properties和IO流结合的API:
| 构造器 | 说明 |
|---|---|
| void load(InputStream inStream) | 从输入字节流读取属性列表(键元素对) |
| void load(Reader reader) | 从输入字符流读取属性列表(键元素对) |
| void store(OutputStream out, String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(InputStream inStream)方法的格式写入输出字节流 |
| void store(Writer writer, String comments) | 将此属性列表(键和元素对)写入此Properties表中,以适合使用load(Reader reader)方法的格式写入输出字符流 |
| public Object setProperty(String key, String value) | 保存键值对 |
| public String getProperty(String key) | 使用此属性列表中指定的键搜索属性值 |
| public Set setProperty() | 所有键的名称的集合 |
public class PropertiesDemo01 {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.setProperty("admin", "123456");
properties.setProperty("dlei", "003197");
properties.setProperty("zhognguo", "china");
System.out.println(properties);
properties.store(new FileWriter("Demo20-io2/src/users.properties"), "zhu_shi,mei_sha_yong");
}
1.store方法的第二个参数就相当于一个注释,无实际意义
2.new的这个流FileWriter不用手动关闭:
public class PropertiesDemo02 {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
// 加载属性文件中的键值对数据到属性对象properties中去
properties.load(new FileReader("Demo20-io2/src/users.properties"));
System.out.println(properties);
String rs = properties.getProperty("dlei");
System.out.println(rs);
}
}
注意:第9行如果用Map集合的get方法得到的是Object需要强转,而如果用getProperty返回的类型就是String类型(因为properties中的键值对都一定是String类型,所以这个API直接就返回String类型的数据)所以我们用getProperty而不用get

FileUtils主要有如下用法:
| 方法名 | 说明 |
|---|---|
| String readFileToString(File file, String encoding) | 读取文件中的数据,返回字符串 |
| void copyFile(File srcFile, File destFile) | 复制文件 |
| void copyDirectoryToDirectory(File srcDir, File destDir) | 复制文件夹 |
使用comments-io简化io读写的步骤:
public class CommonsIODemo01 {
public static void main(String[] args) throws Exception {
// 1.完成文件复制(读取源文件的字节,然后将字节写入目标文件)
IOUtils.copy(new FileInputStream("Demo20-io2\\src\\data.txt"),
new FileOutputStream("Demo20-io2\\src\\data01.txt"));
// 2.完成文件复制到某个文件夹下(这个我们以前实现不了,并且人家这个即使文件夹不存在也会自动创建,无论几级文件夹)
FileUtils.copyFileToDirectory(new File("Demo20-io2\\src\\data.txt"), new File("Demo20-io2\\src\\copy\\copy\\copy\\copy"));
// 3.完成文件夹复制到某个文件夹下!(文件夹中的文件或文件夹也会被复制)
FileUtils.copyDirectoryToDirectory(new File("Demo20-io2\\src\\copy") , new File("Demo20-io2\\src\\copy2"));
FileUtils.copyDirectoryToDirectory(new File("Demo20-io2\\src\\copy") , new File("Demo20-io2\\src\\copy3"));
// 4.删除文件夹(即使非空也可以删)
FileUtils.deleteDirectory(new File("Demo20-io2\\src\\copy3"));
}
}