目录
File和IO的概述
目前我们如何存储数据? 有什么弊端?
经常使用变量, 数组, 集合等
弊端是数据不能永久化存储, 只要代码运行结果, 所有数据都会丢失
计算机中有没有一个硬件, 可以支持数据永久化存储?
有的, 硬盘可以
对文件读写的前提条件?
要知道文件在哪
什么是IO流?
可以对硬盘中的数据进行读写
双击打开文件 -> 读取硬盘中的数据 -> 读
按下文件保存 -> 将数据保存在硬盘 -> 写
File类的作用?
读写数据时, 告诉虚拟机要读写的文件/文件夹在哪
也可以对文件进行常规操作
File的创建功能
File: 是文件和目录路径的抽象表示
文件和目录可以通过File封装成对象
File封装的对象仅仅是一个路径名, 可以存在, 也可以不存在
构造方法:
File(String pathname); 通过指定的字符串路径创建对象
File(String parent,String child); 通过指定的字符串路径,拼接创建file对象
File(FIle pathname,String child);通过指定的File对象和字符串路径,拼接创建File对象
代码示例:
// 1. File(String pathname); 通过指定的字符串路径创建File对象 (常用)
System.out.println(new File("C:\\itheima\\a.txt")); //可以存在也可以不存在
// 2. File(String parent, String child); 通过指定的字符串路径, 拼接创建File对象
System.out.println(new File("C:\\itheima", "a.txt"));
// 3. File(File pathname, Stringchild); 通过指定的File对象和字符串路径, 拼接创建File对象
System.out.println(new File(new File("C:\\itheima"), "a.txt"));
}
绝对路径和相对路径
绝对路径: 从盘符开始的路径
File file1 = new File("C:\\itheima\\a.txt");
相对路径: 相对当前项目
File file2 = new File("a.txt");
相对路径: 模块下的路径
File file3 = new File("模块名\\a.txt");
File的方法
public boolean createNewFile(); | 创建一个新的空文件夹 (路径要正确,文件名不能重复,只能创建文件) |
public boolean mkdir(); | 创建一个单级文件夹 (不常用,只能创建单级文件夹) |
public boolean mkdirs(); | 创建一个多级文件夹 (常用,可以创建单级和多级,只能创建文件夹) |
//3. public boolean mkdirs(); 创建一个多级文件夹
private static void method03() {
//注意一: 可以创建单级文件夹, 也可以创建多级文件夹
System.out.println(new File("C:\\itheima\\day11\\com\\itheima\\test").mkdirs()); //true
//注意二: 不管调用者有没有书写文件后缀, 只能创建文件夹
System.out.println(new File("C:\\itheima\\demo.test").mkdirs()); //true
}
//2. public boolean mkdir(); 创建一个单级文件夹
private static void method02() {
//注意一: mkdir只能创建单级文件夹, 不能创建多级
System.out.println(new File("C:\\itheima\\aaa\\bbb\\ccc").mkdir()); //false 路径错误
//注意二: 不管调用者有没有书写文件后缀, 只能创建文件夹
System.out.println(new File("C:\\itheima\\aaa.txt").mkdir()); //在itheima下创建aaa.txt文件夹
}
//1. public boolean createNewFile(); 创建一个新的空文件
private static void method01() throws IOException {
//注意一: 如果路径中的文件夹不存在, 报错, java.io.IOException: 系统找不到指定的路径
//File file = new File("C:\\itheimahaha\\a.txt");
//注意二: 如果文件存在, 创建失败返回false; 如果文件不存在, 创建成功返回true
//File file = new File("C:\\itheima\\a.txt");
//注意三: 不管调用者有没有书写文件后缀, 只能创建文件
//File file = new File("C:\\itheima\\aaa");
File file = new File("C:\\itheima\\a.txt"); //如果a.txt不存在, 创建成功返回true
System.out.println(file.createNewFile());
}
File的删除方法
public boolean delete(); 删除文件或文件夹(只能删除文件和"空"文件夹,而且不走回收站)
public static void main(String[] args) {
//注意一: 如果删除文件夹, 文件夹必须是空的
//File file = new File("C:\\itheima");
//System.out.println(file.delete()); //false
File file = new File("C:\\itheima\\demo");
System.out.println(file.delete()); //true
File file1 = new File("C:\\itheima\\a.txt");
System.out.println(file1.delete()); //true
}
File获取和判断方法
public boolean isDirectory(); | 判断路径是否为文件夹 |
public boolean isFile(); | 判断路径是否为文件 |
public boolean exists(); | 判断路径是否存在 |
public boolean getName(); | 返回路径表示的文件或文件名 |
代码示例:
public static void main(String[] args) {
//1. public boolean isDirectory(); 判断路径是否为文件夹
File file = new File("C:\\itheima\\day11");
System.out.println(file.isDirectory()); //true day11是文件夹
//2. public boolean isFile(); 判断路径是否为文件
System.out.println(file.isFile()); //false day11不是文件
//3. public boolean exists(); 判断路径是否存在
System.out.println(file.exists()); //true
File file1 = new File("C:\\itheima\\day88");
System.out.println(file1.exists()); //false day88不存在
//4. public String getName(); 返回路径表示的文件或文件夹名
System.out.println(file.getName()); //day11
//a.txt 如果是文件名会带后缀
System.out.println(new File("C:\\itheima\\a.txt").getName());
}
File的listFile方法
File的高级获取方法listFile
public File[ ] listFiles(); 返回路径表示的目录的文件夹和文件的对象数组
public static void main(String[] args) {
File file = new File("C:\\itheima");
File[] files = file.listFiles();
for (File path : files) {
System.out.println(path); //打印C盘itheima包中的所有内容包括隐藏项目
/*
注意事项:
*.调用者不存在时: 返回null
1.调用者是一个文件时: 返回null
2.调用者是一个空文件夹时: 返回一个长度为0的数组
3.调用者是一个有内容的文件夹时: 返回路径表示的目录中的文件夹,
和文件的对象数组, 包括隐藏隐藏项目 (本案例所示)
4.调用者是一个有权限才能进入的文件夹时: 返回null
*/
}
}
File练习一
练习一: 在当前模块下的aaa文件夹下创建一个a.txt
代码示例
public class Demo {
public static void main(String[] args) throws IOException {
File file = new File("day11\\aaa");
if(!file.exists()){
//如果aaa文件夹不存在, 创建该文件夹
file.mkdirs();
}
//拼接路径创建, 继续创建文件a.txt
File newFile = new File(file,"a.txt");
System.out.println(newFile.createNewFile()); //true 可以不接收
}
}
File练习二
练习二: 删除一个多级文件夹
1. 删除文件夹中所有内容
2. 删除该文件夹
解决所有文件夹和递归相结合的思路
1. 进入: 得到src文件夹里面所有内容的File对象
2. 遍历: 得到src文件夹里面每一个文件和文件夹的File对象
3. 判断1: 如果是文件, 直接删除
4. 判断2: 如果是文件夹, 递归继续判断
5. 删除该文件夹
代码示例
public class Demo {
public static void main(String[] args) {
//创建File对象指定路径
File file = new File("C:\\itheima");
//调用方法删除
deleteDor(file);
}
public static void deleteDor(File src) {
//1. 进入: 得到src文件夹里面所有内容的File对象
File[] files = src.listFiles();
//2. 遍历: 得到src文件夹里面每一个文件和文件夹的File对象
for (File file : files) {
//3. 判断1: 如果是文件,直接删除
if (file.isFile()) {
file.delete();
} else {
//4. 判断2: 如果是文件夹,递归继续判断
deleteDor(file);
}
}
//5. 删除该文件夹
src.delete();
}
}
File练习三
练习三: 统计一个文件中, 每种文件的个数并打印
打印格式如下:
txt:3个
doc:4个
jpg:6个
代码示例
public class Demo {
public static void main(String[] args) {
// 统计一个文件夹中, 每种文件出现的次数
// 如果使用变量统计, 弊端是同时只能统计一种文件
// 创建File对象指定路径
File file = new File("day11");
// 利用map集合进行数据统计, 键代表文件后缀名, 值代表出现次数
HashMap<String, Integer> map = new HashMap<>();
// 调用方法
getCount(map, file);
// 打印集合
System.out.println(map); //{txt=1, java=28, iml=1}
}
private static void getCount(HashMap<String, Integer> map, File file) {
// 1.进入,获取所有文件和文件夹对象数组
File[] files = file.listFiles();
// 2.遍历获取到所有文件和文件夹对象
for (File f : files) {
// 3.如果是文件
if (f.isFile()) {
// 4.通过.切割, 获取切割后的数组
String fileName = f.getName();
String[] arr = fileName.split("\\.");
// 5.只统计有且仅有一个后缀的文件
if (arr.length == 2) {
// 6.获取后缀
String fineEndName = arr[1];
// 7.在集合判断该后缀是否存在
if (map.containsKey(fineEndName)) {
// 8.后缀名存在, 根据key将已经出现的次数value获取
Integer count = map.get(fineEndName);
// 9.该后缀的文件又出现一次
count++;
// 10.更新出现次数
map.put(fineEndName, count);
} else {
// 11.后缀名不存在, 添加到集合, 1代表第一次出现
map.put(fineEndName, 1);
}
}
} else {
// 12.如果是文件夹, 递归
getCount(map, f);
}
}
}
}
IO的概述
目前我们如何存储数据? 有什么弊端?
经常使用变量,数组,集合等
弊端是数据不能永久化储存,只要代码运行结果,所有数据都会丢失
思考问题?
在数据传输过程中, 是谁在读? 是谁在写? 这个参照物是什么?
IO的数据传输可以看做是一种数据的流动,按照流动的方向,已内存为参照物,进行读
写操作,简单来说就是内存在读,内存在写
I 表示input,读 从硬盘到内存
O 表示output,写 从内存到硬盘
学习IO流的目的?
目标1: 将数据写到文件中,实现数据的永久化存储
目标2: 读取文件中已存在的数据
IO的分类
IO流按"流向"分类
1. 输入流
2. 输出流
IO流按"类型"分类 (一般来说按类型分类比较多)
1. 字节流 -> 操作所有类型的文件 (包括视频音频图片等)
2. 字符流 -> 只能操作纯文本 (包括java文件,txt文件等)
什么是纯文本文件?
在windows中使用记事本打开"能读懂的", 就是纯文本文件 (使用字符流操作)
在windows中使用记事本打开"读不懂的", 就是非纯文本文件 (使用字节流操作)
以下文件使用什么流操作?
office - 非纯文本文件 - 字节流
avi - 非纯文本文件 - 字节流
map3 - 非纯文本文件 - 字节流
图片 - 非纯文本文件 - 字节流
txt - 纯文本文件 - 字符流
字节流-字节输出流(写数据)
字节流写数据的步骤
1. 创建字节流输出流对象
2. 写数据
3. 释放资源
FileOutputStream构造方法
FileOutputStream(File file); 写数据到指定的File对象的文件
FileOutputStream(String name); 写数据到指定名称的文件
代码实现:
public static void main(String[] args) throws IOException {
//1. 创建字节输出流对象 (告诉JVM要往哪个文件中写数据)
// FileOutputStream fos = new FileOutputStream(new File("C:\\Demo01.txt"));
FileOutputStream fos = new FileOutputStream("C:\\itheima\\Demo01.txt");
/*
FileOutputStream带参构造源码
public FileOutputStream(String name) throws FileNotFoundException {
// 会判断传递进来的字符串参数是不是null
// 不是null就封装为File对象, 所以两种构造一个意思
this(name != null ? new File(name) : null, false);
}
*/
//2. 写数据
fos.write(97);
//3. 释放资源
fos.close();
}
字节流的注意事项
字节流写数据注意事项
1. 如果文件不存在, 则会自动创建
2. 如果文件路径不存在, 则会报错java.io.FileNotFoundException(系统找不到指定的路径。)
3. 如果文件存在, 则会先清空, 再写入数据
4. 写出的整数是在码表中对应的字母
5. 每次操作完流必须释放资源, 相当于告诉计算机这个文件使用完毕了
代码示例:
public static void main(String[] args) throws IOException {
//注意一: 如果文件不存在, 则会自动创建
//注意二: 如果文件路径不存在, java.io.FileNotFoundException(系统找不到指定的路径)
//FileOutputStream fos = new FileOutputStream("C:\\itheima11\\Demo01.txt");
//注意三: 如果文件存在, 则会先清空, 再写入数据
FileOutputStream fos = new FileOutputStream("C:\\itheima\\Demo01.txt");
//注意四: 写出的整数是在码表中对应的字母
fos.write(97); //a
fos.write(98); //b
fos.write(99); //c
//注意五: 每次操作完流必须释放资源, 相当于告诉计算机这个文件使用完毕了
fos.close();
}
字节流-一次写多个数据
字节流写数据的三种方式
void write(int b); | 一次写一个 |
void write(byte[ ] b); | 一次写一个字节数组 |
void write(byte[ ] b, int off, int len); | 一次写一个字节数组的一部分 |
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("day11\\Demo02.txt");
//1. void write(int b); 一次写一个
//2. void write(byte[] b); 一个写一个字节数组
//byte[] bytes = {55,56,57};
//fos.write(bytes); //789
//3. void write(byte[] b, int off, int len); 一个写一个字节数组的一部分
byte[] bytes = {55,56,57,58,59,60,61,62,63,64,65};
//参数(字节数组,从几索引开始,写几个);
fos.write(bytes,0,5); //789:;
//释放资源
fos.close();
}
字节流-两个问题
问题1: 字节流写数据, 如何实现换行?
windows: \r\n -> 需要转为字节数字,通过String的.getBytes()方法实现
linux: \n
max: \r
fos.write("\r\n".getBytes());
问题2: 字节流写数据,如何实现追加写入呢?
FileOutputStream fos = new FileOutputStream("...", true);
构造方法第二个参数, 不写默认为false表示不能追加, 写true表示可以追加写入, 不会再清空旧文本
字节流-trycatch捕获异常
如果出现异常, 如何让close方法一定会执行呢?
finally: 在异常处理时, 提供finally块来执行所有清除(释放)操作
特点: 被finally控制的语句一定会执行, 除非JVM退出
格式:
try {
//可能出现异常的代码块
} catch(异常) {
//处理异常的代码
} finally {
//清除(释放)操作
}
public static void main(String[] args) {
//提升fos作用域
FileOutputStream fos = null;
try {
//可能出现异常的代码块
fos = new FileOutputStream("day11\\Demo04.txt");
fos.write(97);
} catch (IOException e) {
//处理异常的代码
e.printStackTrace();
}finally {
//清除(释放)操作
//注意: 对fos做非空判断, 避免空指针异常
if(fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字节流小结
字节流写数据步骤
1. 创建字节输出流对象
FileOutputStream(File file); //写数据到指定File对象的文件
FileOutputStream(String name); //写数据到指定名称的文件
2. 写数据
(1)字节流写数据注意事项
1. 如果文件不存在, 则会自动创建
2. 如果文件路径不存在, java.io.FileNotFoundException(系统找不到指定的路径。)
3. 如果文件存在, 则会先清空, 再写入数据
4. 写出的整数是在码表中对应的字母
5. 每次操作完流必须释放资源, 相当于告诉计算机这个文件使用完毕了字节流写
数据的三种方式
6. void write(int b); 一次写一个
7. void write(byte[] b); 一个写一个字节数组
8. void write(byte[] b, int off, int len); 一个写一个字节数组的一部分
(2)换行和追加写入问题
windows: \r\n -> 需要转为字节数字,通过String的.getBytes()方法实现
构造方法第二个参数, 不写默认为false表示不能追加, 写true表示可以追加写入, 不会再清空旧文本
3. 释放资源
finally: 在异常处理时, 提供finally块来执行所有清除(释放)操作
特点: 被finally控制的语句一定会执行, 除非JVM退出
格式:
try{
//可能出现异常的代码块
}catch(异常){
//处理异常的代码
}finally{
//清除(释放)操作
//流.close();
}
字节流-字节输入流(读数据)
字节流读数据步骤
1. 创建字节输入流对象
2. 读数据
3. 释放资源
字节流写数据注意事项
1. 如果文件不存在, 报错java.io.FileNotFoundException
2. 一次读一个数据, 返回值就是字节, 如果不看字节, 需要强转为char类型
public static void main(String[] args) throws IOException {
//1. 创建字节输入流对象
//注意一: 如果文件不存在, 报错java.io.FileNotFoundException
//FileInputStream fis = new FileInputStream("day11\\Demo005.txt");
FileInputStream fis = new FileInputStream("day11\\Demo05.txt");
//2. 读数据
//System.out.println(fis.read()); //97
//注意二: 一次读一个数据, 返回值就是字节, 如果不看字节, 需要强转为char类型
System.out.println((char)fis.read()); //a
//3. 释放资源
fis.close();
}
字节流-读多个字节
public static void main(String[] args) throws IOException {
//读取模块下Demo06.txt文件中的多个数据
FileInputStream fis = new FileInputStream("day11\\Demo06.txt");
//文件中多个字节怎么读? 使用循环尝试
// while (true){
// System.out.println(fis.read());
// }
//读取完有效数据后, 一直打印-1, -1代表文件到达了结尾
//定义变量, 让变量记录读取的结果, 如果变量不等于-1满足,
// 证明文件还没有读完, 那就继续读
int b;
while ((b = fis.read()) != -1) {
//System.out.println(b); //只需要将b强转为char即可
System.out.println((char) b);
}
//国际惯例
fis.close();
}
字节流-文件复制
需求: 将目标路径下的a.avi复制到当前模块下
分析:
复制文件, 就是就是将数据从一个文件中读取出来(数据源), 然后写入到另一个文件(目的地)
数据源 -> 读数据 -> FileInputStream
目的地 -> 写数据 -> FileOutputStream
代码示例
public class Demo {
public static void main(String[] args) throws IOException {
//数据源 -> 读数据 -> FileInputStream
FileInputStream fis = new FileInputStream("C:\\itheima\\a.avi");
//目的地 -> 写数据 -> FileOutputStream
FileOutputStream fos = new FileOutputStream("day11\\a.avi");
//将数据从一个文件中读取出来(数据源), 然后写入到另一个文件(目的地)
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
//国际惯例
fis.close();
fos.close();
}
}
字节流-定义小数组拷贝
如果操作的数据过大, 那么速度会有什么影响? 慢!
硬盘(数据源) ->
创建输入流对象 ->
内存(read() -> int b -> write()) ->
创建输出流对象 ->
目的地
一个字节上述过程会走一次
如何提升拷贝速度的效率?
为了解决速度问题, 字节流通过创建字节数组, 可以"一次性读取数个数据并写多个数据"
一次读取一个字节数组的方法:
public int read(byte[] b); 从输入流读取最多b.length个字节的数据
返回的是读到的, 实际有效字节的个数
代码示例:(重点)
public static void main(String[] args) throws IOException {
//数据源 -> 读数据 -> FileInputStream
FileInputStream fis = new FileInputStream("C:\\itheima\\a.avi");
//目的地 -> 写数据 -> FileOutputStream
FileOutputStream fos = new FileOutputStream("day11\\a.avi");
// 1.创建"鸡蛋篮子", 也就是一次读取的数据集合
byte[] bytes = new byte[1024];
// 2.len代表本次读到的有效数据个数(读到了几个有效字节)
int len;
// 3.如果没到-1代表没到文件结尾, 继续读
while ((len = fis.read(bytes)) != -1) {
// 4.从0开始, 写入读到的(bytes)所有有效字节(len)
fos.write(bytes, 0, len);
}
//国际惯例
fis.close();
fos.close();
}
小数组拷贝原理:
主要理解变量len的作用
读数据时, len代表代表本次读到的有效数据个数, 也就是读到了几个有效字节
写数据时, len代表写的个数
缓冲流
目前的读写效率很低
读一个字节, 写一个字节
读一个小数组, 写一个小数组中读到的所有数据
字节缓冲流的作用 : 提升读写效率
BufferOutputStream: 字节缓冲输出流, 构造接收一个字节流对象, 而不是文件路径
BufferOutputStream(OutputStream out);
BufferedInputStream: 字节缓冲输入流, 构造接收一个字节流对象, 而不是文件路径
BufferedInputStream(InputStream in)
字节缓冲流仅仅提供了缓冲区(一个数组), 用来提升读写效率
字节缓冲流不能直接操做文件中的数据, 真正操做流中数据的, 还是基本的字节流
缓冲流一次读写一个字节
public static void main(String[] args) throws IOException {
//数据源
BufferedInputStream bis = new BufferedInputStream(new
FileInputStream("C:\\itheima\\lesson.mp4"));
//目的地
BufferedOutputStream bos = new BufferedOutputStream(new
FileOutputStream("C:\\Users\\Administrator\\Desktop\\lesson.mp4"));
//一次读一个字节
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
//国际惯例
bis.close();
bos.close();
}
缓冲流-一次读写一个字节底层原理
1. BufferedInputStream底层: 创建了默认长度为8192的字节数组, BufferedOutputStream底层一样
// 默认长度
private static int DEFAULT_BUFFER_SIZE = 8192;
public BufferedInputStream(InputStream in) {
this(in, DEFAULT_BUFFER_SIZE);
}
// this
public BufferedInputStream(InputStream in, int size) {
super(in);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size]; // size=8192
}
2. close方法底层: 会将字节流关闭
缓冲流-一次读写一个字节数组
需求: 字节缓冲流结合字节数组提升拷贝效率
代码示例
public class Demo {
public static void main(String[] args) throws IOException {
//数据源
BufferedInputStream bis = new BufferedInputStream(new
FileInputStream("C:\\itheima\\lesson.mp4"));
//目的地
BufferedOutputStream bos = new BufferedOutputStream(new
FileOutputStream("C:\\Users\\Administrator\\Desktop\\lesson.mp4"));
//读写
byte[] bytes = new byte[1024];
/*
解释一下数组长度为什么定义为1024
1. 计量存储和传输的计量是以字节为单位
2. 1024个字符的数组,这是一个缓冲,java推荐使用8K作为缓冲,也就是8192个字节
3. BufferedInputStream和BufferedOutputStream底层就是这样么做的
4. 缓冲区(数组)容量并非固定不变, 可以自行定义, 为提高计算机的运算效率, 将其定义
为计算机最为熟悉的长度1024B(或者倍数也可以), 这是程序优化的一部分
总结:
如果像之前定义int b一个一个字节读取, 在底层相当于一个一个字节"倒手", 效率肥肠
低那么通过结合字节数组, 在底层相当于一个一个数组"倒手", 效率肥肠高, 但是数组长
度最好是1024的倍数, 算是对代码的一种优化
*/
int len;
while ((len = bis.read(bytes)) != -1) {
//从bytes中获取数据写到目的地, 每次从索引0开始, 写len读到的有效字节个数这么多
bos.write(bytes, 0, len);
}
//国际惯例
bis.close();
bos.close();
}
}
小结
字节流: 可以操作(拷贝)所有类型的文件
字节缓冲流: 提升效率, 不能直接操作文件, 需要用字节流当带参构造参数
拷贝文件四种方式: 本质是底层传输时"倒手"的数据个数不同, 个数越大效率越高
1. 字节流一次读一个字节
2. 字节流一次读一个字节数组
3. 字节缓冲流一次读一个字节
4. 字节缓冲流一次读一个字节数组