File、IO流(一)
存储数据的方案
以上都是内存中的数据容器(记住程序正在处理的数据,能快速运算),它们记住的数据,在断电,或者程序终止时会丢失
- 程序在内存中处理后的数据想长久的保存起来
- 文件是非常重要的存储方式,在计算机硬盘中
- 即便断电了,或者程序终止了,存储在硬盘文件中的数据也不会丢失
File
- File是java.io包下的类,File类的对象,用于代表当前操作系统的文件(可以是文件,或文件夹)
- 获取文件信息(大小、文件名、修改时间)
- 判断文件的类型
- 创建文件 / 文件夹
- 删除文件 / 文件夹…
注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据
IO流
- 用于读写数据的(可以读写文件,或网络中的数据…)
目录
- 创建对象
- 常用方法1:判断文件类型,获取文件信息
- 常用方法2:创建文件、删除文件
- 常用方法3:遍历文件夹
- 前置知识:方法递归
- 前置知识:字符集
- IO流
- IO流 —— 字节流
- IO流 —— 资源释放的方法
1、创建File类的对象
构造器 | 说明 |
---|---|
Public File(String pathname) | 根据文件路径创建文件对象 |
Public File(String parent,String child) | 根据父路径和子路径的名字创建对象 |
Public File(File parent,String child) | 根据父路径对应文件对象和子路径的名字创建对象 |
**
* 目标:掌握File创建对象,代表具体文件的方案
*/
public class FileTest1 {
public static void main(String[] args) {
// 1.创建一个File对象,指代某个具体的文件
// 路径分隔符
File f1 = new File("D:/reSource/a.txt");
// File f2 = new File("D:\\reSource\\a.txt"); // 要用'\'转义
// File f3 = new File("D:" + File.separator + "reSource" + File.separator + "a.txt"); // 系统自带的分隔符
System.out.println(f1.length()); // 文件大小(字节)
File f2 = new File("D:/reSource");
System.out.println(f2.length()); // 文件夹本身大小(字节)
// 注意:File对象可以指代一个不存在的文件路径
File f3 = new File("D:/reSource/aa.txt");
System.out.println(f3.length()); // 0
System.out.println(f3.exists()); // false
// 现在要定位的文件是在模块中,应该怎么定位呢?
// 绝对路径 :带盘符的
// File f4 = new File("D:\\code\\ATM-system\\src\\File1.txt");
// 相对路径(重点):不带盘符,默认是直接去工程下寻找文件的
File f4 = new File("src\\File1.txt");
System.out.println(f4.length());
}
}
注意:File对象既可以代表文件,也可以代表文件夹,File封装的对象仅仅是一个路径名,这个路径可以是存在的,也允许是不存在的
绝对路径、相对路径
- 绝对路径:从盘符开始
File file1 = new File(“D:\reSource\a.txt”);
- 相对路径:不带盘符,默认直接到当前工程下的目录寻找文件
File file2 = new File(“src\a.txt”);
常用方法1:File提供的判断文件类型,获取文件信息功能
方法名称 | 说明 |
---|---|
public boolean exisit() | 判断当前文件对象,对应的文件路径是否存在,存在返回true |
public boolean isFile() | 判断当前文件对象指代的是否是文件,是文件返回true,反之 |
public boolean isDirectory() | 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之 |
public String getName() | 获取文件的名称(包含后缀) |
public long length() | 获取文件的大小,返回字节个数 |
public long lastModified() | 获取文件的最后修改时间(返回的是时间毫秒值) |
public String getPath() | 获取创建文件对象时,使用的路径 |
public String getAbsolutePath() | 获取绝对路径 |
public class FileTest2 {
public static void main(String[] args) {
// 1.创建文件对象,指代某个文件
File f1 = new File("D:/reSource/a.txt");
// 2、public boolean exisit() : 判断当前文件对象,对应的文件路径是否存在,存在返回true
System.out.println(f1.exists()); // true
// 3、public boolean isFile() : 判断当前文件对象指代的是否是文件,是文件返回true,反之
System.out.println(f1.isFile()); // true
// 4、public boolean isDirectory() : 判断当前文件对象指代的是否是文件夹,是文件夹返回true,反之
System.out.println(f1.isDirectory()); // false
// 5、public String getName() : 获取文件的名称(包含后缀)
System.out.println(f1.getName()); // a.txt
// 6、public long length() : 获取文件的大小,返回字节个数
System.out.println(f1.length());
// 7、public long lastModified() : 获取文件的最后修改时间(返回的是时间毫秒值)
long time = f1.lastModified();
System.out.println(time); // 1717432910058
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println(sdf.format(time)); //2024/06/04 00:41:50
// 8、public String getPath() : 获取创建文件对象时,使用的路径
File f2 = new File("D:/reSource/a.txt");
File f3 = new File("src\\File1.txt");
System.out.println(f2.getPath()); // D:\reSource\a.txt
System.out.println(f3.getPath()); // src\File1.txt
// 9、public String getAbsolutePath() : 获取绝对路径
System.out.println(f2.getAbsolutePath()); // D:\reSource\a.txt
System.out.println(f3.getAbsolutePath()); // D:\code\javasepro\src\File1.txt (把相对路径转成绝对路径的形式)
}
}
常用方法2:创建文件、删除文件
File类创建文件的功能
方法名 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新文件(文件内容为空),创建成功返回true,反之 |
public boolean mkdir() | 用于创建文件夹,注意:只能创建一级文件夹 |
public boolean mkdirs() | 用于创建文件夹,注意:可以创建多级文件夹 |
File类删除文件的功能
方法名 | 说明 |
---|---|
public boolean delete() | 可以删除文件,或者空文件夹,注意:不能删除非空文件夹 |
public class FileTest3 {
public static void main(String[] args) throws IOException {
// 1、public boolean createNewFile() : 创建一个新文件(文件内容为空),创建成功返回true,反之
File f1 = new File("D:/reSource/abc.txt");
System.out.println(f1.createNewFile()); // 第一次创建是返回true,如果文件已经存在,则返回false
// 2、public boolean mkdir() : 用于创建文件夹,注意:只能创建一级文件夹
File f2 = new File("D:/reSource/aaa");
System.out.println(f2.mkdir()); // true
// 3、public boolean mkdirs() : 用于创建文件夹,注意:可以创建多级文件夹
File f3 = new File("D:/reSource/bbb/ccc/ddd");
System.out.println(f3.mkdirs()); // true
// 4、public boolean delete() : 可以删除文件,或者空文件夹,注意:不能删除非空文件夹
System.out.println(f1.delete()); // true
System.out.println(f2.delete()); // true
File f4 = new File("D:/reSource");
System.out.println(f4.delete()); // false
}
}
常用方法3:遍历文件夹
File类提供的遍历文件夹的功能
方法名 | 说明 |
---|---|
public String[] list() | 获取当前目录下的所有的“一级文件名称”到一个字符串数组中去返回 |
public File[] listFiles() | 获取当前目录下所有的“一级文件对象”到一个文件数组中去返回(重点) |
public class FileTest4 {
public static void main(String[] args) {
// 1、public String[] list() : 获取当前目录下的所有的“一级文件名称”到一个字符串数组中去返回
File f1 = new File("D:\\course\\javase");
String[] names = f1.list();
for (String name : names) {
System.out.println(name);
}
// 2、public File[] listFiles() : (重点)获取当前目录下所有的“一级文件对象”到一个文件数组中去返回(重点)
File[] files = f1.listFiles();
for (File file : files) {
System.out.println(file.getAbsolutePath()); // 打印遍历到的每个文件的绝对路径
}
// 当路径不存在时,返回 null
File f2 = new File("D:\\course\\javase");
File[] files2 = f2.listFiles();
System.out.println(Arrays.toString(files2)); // null
// 当主调是空文件夹时,返回一个长度为0的数组
File f3 = new File("D:\\reSource\\aaa");
File[] files3 = f3.listFiles();
System.out.println(Arrays.toString(files3)); // []
}
}
使用listFiles方法时的注意事项
- 当主调是文件,或者路径不存在时,返回null
- 当主调是空文件夹时,返回一个长度为0的数组
- 当主调是一个有内容的文件夹时,将里面所有的一级文件和文件夹的路径放在File数组中返回
- 当主调是一个文件夹时,且里面有隐藏文件时,将里面所有的一级文件和文件夹的路径放在File数组中返回,包含隐藏文件
- 当主调是一个文件夹,但是没有权限访问该文件夹时,返回null
前置知识:方法递归
什么是方法递归?
- 方法递归是一种算法,在程序设计语言中广泛应用
- 从形式上说:方法调用自身的形式称为方法递归(recursion)
使用方法递归时需要注意的问题
- 如果递归没有控制好终止,会出现递归死循环,导致栈内存溢出错误
递归经典案例:猴子吃桃问题
- 猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个,第二天又吃了昨天剩余桃子数量的一半,决定好不过瘾,于是又多吃了一个,以后每天都是吃昨天剩余桃子数量的一半,觉得好不过瘾,又多吃一个,等到第10天的时候,发现桃子只有一个了,求猴子第一天摘了多少桃子?
public class RecursionTest1 {
public static void main(String[] args) {
// 目标:猴子吃桃问题
// 终结点 : f(10) = 1
// 公式:f(x) - f(x) /2 -1 = f(x+1)
// 变形: 2 * f(x) -f(x)-2 = 2 * f(x+1)
// 变形2: f(x) = 2 * f(x+1) +2
System.out.println(f(1));
}
public static int f(int x) {
if(x == 10){
return 1;
} else {
return 2 * f(x+1) + 2;
}
}
}
案例1:文件搜索
需求:从D:盘中,搜索“QQ.exe”这个文件,找到后输出其位置
分析:
- 先找出D:盘下的所有一级文件对象
- 遍历全部一级文件对象,判断是否是文件
- 如果是文件,判断是否是自己想要找的文件
- 如果是文件夹,需要继续进入到该文件夹,重复上述过程
/**
* 目标:掌握文件搜索的实现
*/
public class RecursionTest1 {
public static void main(String[] args) throws Exception {
searchFile(new File("D:\\Program Files\\Tencent\\QQNT"),"QQ.exe");
}
/**
* 去目录(文件夹)下搜索某个文件
* @param dir 目录(文件夹)
* @param fileName 要搜索的文件名称
*/
public static void searchFile(File dir,String fileName) throws IOException {
// 1、把非法的情况都拦截住
if(dir == null || !dir.exists() || dir.isFile()) {
return; // 代表无法搜索
}
// 2、dir不是null,存在,一定是目录对象(文件夹对象)
// 获取当前目录(文件夹)下的全部一级文件对象
File[] files = dir.listFiles();
// 3、判断当前目录下是否存在一级文件对象(文件夹里是否为空),以及是否可以拿到一级文件对象(是否有权限)
if(files != null && files.length > 0) { // 存在一级文件对象,并且有权限
// 4、遍历全部一级文件对象
for (File f : files) {
// 5、判断文件对象 -> 是否是文件,还是文件夹
if(f.isFile()) {
// 一定是文件,判断这个文件的文件名是否是要找的
if(f.getName().equals(fileName)) {
System.out.println("该文件的绝对路径是:" + f.getAbsoluteFile());
Runtime r = Runtime.getRuntime();
r.exec(f.getAbsolutePath()); // 启动QQ程序
} else {
// 一定是文件夹,继续重复这个过程(递归)
searchFile(f,fileName);
}
}
}
}
}
}
案例2:删除非空文件夹
/**
* 目标:掌握删除非空文件夹
*/
public class RecursionTest2 {
public static void main(String[] args) {
// 目标:删除非空文件夹,独立功能独立成方法
File dir = new File("D:\\reSource\\照片");
if(deletedir(dir) == -1) {
System.out.println("异常终止");
} else {
System.out.println("成功删除");
}
}
public static int deletedir(File dir) {
if(dir == null || !dir.exists()) {
return -1;
}
if(dir.isFile()) {
dir.delete();
return 0;
}
// 1、dir存在且是文件夹,拿里面的一级文件对象
File[] files = dir.listFiles();
if(files == null) { // 没有访问权限
return -1;
}
if(files.length == 0) {
dir.delete(); // 代表空文件夹直接删除
return 0;
}
// 这是一个有内容的文件夹,删掉里面的内容,再删掉自己
for(File file : files) {
if(file.isFile()) { // 是文件直接删除
file.delete();
}else { // 是文件夹,重复上述过程
deletedir(file);
}
}
// 把自己的内容删完了,最后要把自己删除
dir.delete();
return 0;
}
}
案例3:
需求:改变某个文件夹下文件的序号,要求从19开始
public class RecursionTest3 {
public static void main(String[] args) {
// 目标:改变某个文件夹下视频的序号,要求从19开始
File dir = new File("D://reSource//视频");
// 1、拿到视频目录下面全部的视频对象 ——>一级文件对象
File[] videos = dir.listFiles();
// 2、遍历一个一个的文件对象
for (File video : videos) {
// 3、拿到它的名字,改成新名字
String name = video.getName(); // "10、封装、继承...."
String index = name.substring(0, name.indexOf("、")); // 包前不包后
String lastName = name.substring(name.indexOf("、"));
String newName = (Integer.valueOf(index) + 18 ) + lastName;
// 4、正式改名
// video.getParent() : 拿到video的父路径 ——> dir
// video.renameTo(new File(video.getParent(),newName));
video.renameTo(new File(dir,newName));
}
}
}
案例4:
啤酒问题:啤酒2元一瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元可以喝多少瓶?
public class RecursionTest4 {
public static int totalNumber; // 总酒数
public static int lastCoverNumber; // 剩余的瓶盖数
public static int lastBottleNumber; // 剩余的瓶子数
public static void main(String[] args) {
// 啤酒问题:啤酒2元一瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元可以喝多少瓶?
buy(10);
System.out.println("总数:" + totalNumber);
System.out.println("剩余盖子数:" + lastCoverNumber);
System.out.println("剩余瓶子数:" + lastBottleNumber);
}
public static void buy(int money) {
// 1、先买
int buyNumber = money / 2;
totalNumber = totalNumber + buyNumber;
// 2、把瓶子和盖子换算成钱继续买酒
int allBottleNumber = buyNumber + lastBottleNumber;
int allCoverNumber = buyNumber + lastCoverNumber;
int allMoney = 0;
if (allBottleNumber >= 2) {
allMoney = (allBottleNumber / 2) * 2;
}
lastBottleNumber = (allBottleNumber % 2);
if (allCoverNumber >= 4) {
allMoney += (allCoverNumber / 4) * 2;
}
lastCoverNumber = (allCoverNumber % 4);
if (allMoney >= 2) {
buy(allMoney);
}
}
}
前置知识:字符集
- 常见字符集介绍
- 字符集的编码、解码操作
标准ASCII字符集
- ASCII:美国标准信息交换代码,包含了英文、符号等
- 标准ASCII使用1个字节存储一个字符,首位是0,总共可表示128个字符
GBK(汉字内码扩展规范,国标码)
- 汉字编码字符集,包含了2万多个汉字字符,GBK中一个中文字符编码成两个字节的形式存储
- GBK兼容了ASCII字符集
Unicode字符集(UTF-8字符集,统一码,也叫万国码)
- Unicode是国际组织制定的,可以容纳世界上所有的文字,符号的字符集
- 英文字符、数字等只占一个字节(兼容ASCII编码),汉字字符占用3个字节
小结
- ASCII字符集:只有英文、数字、符号等,占1个字节
- GBK字符集:汉字占2个字节,英文、数字占1个字节
- UTF-8字符集:汉字占3个字节,英文、数字占1个字节
注意1:字符编码时使用的字符集,和解码时使用的字符集一致,否则会出现乱码
注意2:英文,数字一半不会乱码,因为很多字符集都兼容了ASCII编码
字符集的编码,解码操作
- 编码:把字符按照指定字符集编码成字节
- 解码:把字节按照指定字符集解码成字符
java代码完成对字符的编码
String类提供了如下方法
方法名 | 说明 |
---|---|
byte[] getBytes() | 使用平台的默认字符将该String编码为一系列字节,将结果存储到新的字节数组中 |
byte[] getBytes(String charSetName) | 使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中 |
java代码完成对字符的解码
String类提供了如下方法
方法名 | 说明 |
---|---|
String(byte[] bytes) | 通过使用平台默认字符集解码指定的字节数组来构造新的String |
String(byte[] bytes,String charSetName) | 通过指定的字符集解码指定的字节数组来构造新的String |
IO流概述
- I 值 Input,称为输入流:负责把数据读到内存中去
- O 指 Output,称为输出流,负责写数据出去(磁盘 / 网络…)
IO流的分类
IO流总体来说就有四大流
- 字节输入流
- 字节输出流
- 字符输入流
- 字符输出流
总结流的四大类
- 字节输入流:以内存为基准,来自磁盘文件 / 网络中的数据,以字节的形式读入到内存中去的流
- 字节输出流:以内存为基准,把内存中的数据以字节的形式写出到磁盘或者网络中去的流
- 字符输入流:以内存为基准,来自磁盘文件 / 网络中的数据,以字符的形式读入到内存中去的流
- 字符输出流:以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流
IO流的体系
- Java.io包下
IO流 —— 字节流
- 文件字节输入流:每次读取一个字节
- 文件字节输入流:每次读取多个字节
- 文件字节输入流:一次读取完全部字节
- 文件字节输出流:写字节出去
- 案例:文件复制
FileInputStream(文件字节输入流)
- 作用:以内存为基准,可以把磁盘文件中的数据以字节的形式读入到内存中去
构造器 | 说明 |
---|---|
Public FileInputStream(File file) | 创建字节输入流管道与源文件接通 |
Public FileInputStream(String Pathname) | 创建字节输入流管道与源文件接通 |
方法名称 | 说明 |
---|---|
Public int read() | 每次读取一个字节返回,如果发现没有数据可读会返回-1 |
Public int read(byte[] buffer) | 每次用一个字节数组去读取数据,返回字节数组读取了多少个字节,如果发现没有数据可读,会返回-1 |
1. 文件字节输入流:每次读取一个字节
public class FileInputStreamTest1 {
public static void main(String[] args) throws IOException {
// 1、创建文件字节输入流管道,与源文件接通
// InputStream is = new FileInputStream(new File("src\\ab.txt"));
// 简化写法,推荐使用
InputStream is = new FileInputStream("src\\ab.txt");
// 2、开始读取文件的字节数据
// public int read() : 每次读取一个字节返回,如果没有数据了, 返回-1
// int b1 = is.read();
// System.out.println(b1); // 97
// System.out.println((char)b1); // a
//
// int b2 = is.read();
// System.out.println(b2); // 98
// System.out.println((char)b2); // b
//
// int b3 = is.read();
// System.out.println(b3); // -1
// 3、使用循环改造上述代码
int b; // 用于记住读取的字节
while((b = is.read()) != -1 ) {
System.out.print((char) b);
}
// 读取数据的性能很差!
// 读取汉字输出会乱码,无法避免
// 流使用完毕之后,必须关闭!释放系统资源
is.close();
}
}
注意事项:
- 使用FileInputStream每次读取一个字节,读取性能较差,并且读取汉字输出会乱码
- 流使用完毕之后,必须关闭,释放系统资源
2. 文件字节输入流:每次读取多个字节
/**
* 目标:掌握使用FileInputStream 每次读取多个字节
*/
public class FileInputStreamTest2 {
public static void main(String[] args) throws Exception {
// 1、创建一个字节输入流对象代表字节输入流管道与源文件接通
InputStream is = new FileInputStream("src/abc66.txt");
// 2、开始读取文件中的字节数据,每次读取多个字节
// public int read(byte b[]) throws IOException
// 每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1
// byte[] buffer = new byte[3];
// int len = is.read(buffer);
// String rs = new String(buffer);
// System.out.println(rs); // abc
// System.out.println("当次读取的字节数量:" + len ); // 3
//
// // buffer = [abc];
// // buffer = [66c]
// int len2 = is.read(buffer);
//
String rs2 = new String(buffer);
System.out.println(rs2); // 66c
//
// // 注意:读取多少,倒出多少
// String rs2 = new String(buffer,0,len2);
// System.out.println(rs2); // 66
// System.out.println("当次读取的字节数量:" + len ); // 2
//
// int len3 = is.read(buffer);
// System.out.println(len3); // -1
// 使用循环改造
byte[] buffer = new byte[3];
int len; // 记住每次读取了多少字节
while( (len = is.read(buffer)) != -1 ) {
// 注意:读取多少,倒出多少
String rs = new String(buffer,0,len);
System.out.println(rs);
}
// 性能得到了明显的提升!
// 这种方案也不能避免读取汉字输出乱码的问题
is.close(); // 关闭流,释放系统资源
}
}
注意事项:
- 使用FileInputStream每次读取多个字节,读取性能得到了提升,但读取汉字输出还是会乱码
- 使用字节流读取中文,如何保证输出不会乱码,怎么解决?
- 定义一个与文件一样大的字节数组,一次性读取完文件的全部字节
3. 文件字节输入流:一次读取完全部字节
- 方式一:自己定义一个字节数组与被读取的文件大小一样大,然后使用该字节数组,一次性读完文件的全部字节
方法名称 | 说明 |
---|---|
Public int read(byte[] buffer) | 每次用一个字节数组去读取,返回字节数组读取了多少个字节,如果发现没有数据可读会返回-1 |
public class FileInputStreamTest3 {
public static void main(String[] args) throws Exception {
// 1、一次性读取完文件的全部字节到一个字节数组中去
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("src/File1");
// 2、方式一:准备一个字节数组,大小与文件的大小正好一样大
File f = new File("src/File1");
long size = f.length();
byte[] buffer = new byte[(int)size];
int len = is.read(buffer);
String rs = new String(buffer);
System.out.println(rs);
System.out.println(size); // 14
System.out.println(len); // 14
}
}
- 方式二:java官方为InputStream提供了如下方法,可以直接把文件的全部字节,读取到一个字节数组中返回
方法名称 | 说明 |
---|---|
Public byte[] readAllBytes() throw IoException | 直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回 |
public class FileInputStreamTest3 {
public static void main(String[] args) throws Exception {
// 1、一次性读取完文件的全部字节到一个字节数组中去
// 创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("src/File1");
// 方式二:
byte[] buffer = is.readAllBytes();
System.out.println(new String(buffer));
}
}
小结:
直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
- 如果文件过大,创建的字节数组也会过大,可能引起内存溢出
- 读写文本内容更适合用字符流
- 字节流适合做数据的转移,如:文件复制等
4. 文件字节输出流:写字节出去
FileOutoutStream(文件字节输出流)
- 作用:以内存为基准,把内存中的数据以字节的形式写出到文件中去
构造器 | 说明 |
---|---|
Public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
Public FileOutputStream(String filepath) | 创建字节输出流管道与源文件路径接通 |
Public FileOutputStream(File file,boolean append) | 创建字节输出流管道与源文件对象接通,可追加数据 |
Public FileOutputStream(String filepath,boolean append) | 创建字节输出流管道与源文件路径接通,可追加数据 |
方法名称 | 说明 |
---|---|
Public void Write(int a) | 写一个字节出去 |
Public void Write(byte[] buffer) | 写一个字节数组出去 |
Public void Write(byte[] buffer,int pos,int len) | 写一个字节数组的一部分出去 |
Public void close() throws IoException | 关闭流 |
/**
* 目标:掌握文件字节输出流FileOutputStream的使用
*/
public class FileInputStreamTest4 {
public static void main(String[] args) throws Exception {
// 1、创建一个字节输出流管道与目标文件接通
// 覆盖管道:覆盖之前的数据
// OutputStream os = new FileOutputStream("src/out1.txt");
// 追加数据的管道
OutputStream os = new FileOutputStream("src/out1.txt",true);
// 2、开始写字节数据出去
os.write(97); // 97就是一个字节,代表a
os.write('b'); // 'b'也是一个字节
// os.write('张'); // UTF-8里一个中文占3个字节,这里默认只能写出去一个字节,所以会乱码
byte[] bytes = "我爱你中国abc".getBytes();
os.write(bytes);
os.write(bytes, 0, 15); // "我爱你中国"
// 换行符
os.write("\r\n".getBytes());
os.close(); // 关闭流
}
}
案例:文件复制
/**
* 目标:使用字节流完成对文件的复制操作
*/
public class CopyTest5 {
public static void main(String[] args) throws Exception {
// 需求:复制照片
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("D:/reSource/logo.png");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("C:/data/logo.png");
// 3、创建一个字节数组,负责转移字节数据
byte[] buffer = new byte[1024]; // 1KB
// 4、从字节输入流中读取字节数据,写出去到字节输出流中,读多少写出去多少
int len; // 记住每次读取了多少个字节
while((len = is.read(buffer)) != -1) {
os.write(buffer,0,len);
}
os.close();
is.close();
System.out.println("复制完成");
}
}
IO流 —— 资源释放的方法
- Try —— catch —— finally
- Try —— with —— resource
try {
...
} catch(IoException e) {
e.printStackTrace();
} finally {
...
}
/**
* 目标:认识try-catch-finally
*/
public class Test1 {
public static void main(String[] args) {
try {
System.out.println(10/0);
return; // 跳出方法的执行
} catch(Exception e) {
e.printStackTrace();
}finally {
System.out.println("=====finally执行了一次====");
}
}
}
// 输出 =====finally执行了一次====
- finally代码区的特点无论try中的程序是正常执行,还是出现了异常,最后都一定会执行finally区,除非JVM虚拟机终止
注意:
finally代码块内一定不能返回数据
public class Test1 {
public static void main(String[] args) {
try {
System.out.println(10/0);
return; // 跳出方法的执行
} catch(Exception e) {
e.printStackTrace();
}finally {
System.out.println("=====finally执行了一次====");
}
System.out.println(chu(10 , 2)); // 输出了11
}
public static int chu(int a,int b) {
try {
return a / b;
} catch(Exception e){
e.printStackTrace();
return -1; // 代表的是出现异常
} finally {
// 千万不要在finally中返回数据!
return 11;
}
}
}
// 最后会输出11(X)
- finally一般用于在程序执行完成后进行资源释放操作(专业级做法)
/**
* 目标:掌握finally的常用方法
*/
public class Test2 {
public static void main(String[] args){
InputStream is = null;
OutputStream os = null;
try {
System.out.println(10 / 0);
// 1、创建一个字节输入流管道与源文件接通
is = new FileInputStream("D:/reSource/logo.png");
// 2、创建一个字节输出流管道与目标文件接通
os = new FileOutputStream("C:/data/logo.png");
System.out.println(10 / 0);
// 3、创建一个字节数组,负责转移字节数据
byte[] buffer = new byte[1024]; // 1KB
// 4、从字节输入流中读取字节数据,写出去到字节输出流中,读多少写出去多少
int len; // 记住每次读取了多少个字节
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("复制完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源的操作
try {
if (os != null)
os.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
if (is != null)
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
JDK7开始提供了更简单的资源释放方案:try —— with —— resource
try( 定义资源1; 定义资源2;...) {
可能出现异常的代码;
} catch(异常类名 变量名) {
异常的处理代码;
}
该资源使用完毕后,会自动调用close()方法,完成对资源的释放
/**
* 目标:掌握更简单的资源释放方案:try - with - resource
*/
public class Test3 {
public static void main(String[] args){
try(
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("D:/reSource/logo.png");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("C:/data/logo.png");
// 注意:这里只能放置资源对象
// 什么是资源对象呢? 资源都是会实现AutoCloseable接口
// 资源都会有一个close方法,并且资源放到()里,用完之后
// 会被自动调用其close方法完成资源的释放操作
){
// 3、创建一个字节数组,负责转移字节数据
byte[] buffer = new byte[1024]; // 1KB
// 4、从字节输入流中读取字节数据,写出去到字节输出流中,读多少写出去多少
int len; // 记住每次读取了多少个字节
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println("复制完成");
} catch (Exception e) {
e.printStackTrace();
}
}
}
验证资源用完之后,是否会自动调用其close方法完成资源的释放操作
public class Test4 {
public static void main(String[] args){
try(
// 1、创建一个字节输入流管道与源文件接通
InputStream is = new FileInputStream("src/out1.txt");
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("src/out2.txt");
// 自己创建一个资源对象
MyConnection conn = new MyConnection();
){
// 3、创建一个字节数组,负责转移字节数据
byte[] buffer = new byte[1024]; // 1KB
// 4、从字节输入流中读取字节数据,写出去到字节输出流中,读多少写出去多少
int len; // 记住每次读取了多少个字节
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
System.out.println(conn); // 用一下这个资源对象,用完后好自动释放资源
System.out.println("复制完成");
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 输出以下语句:
// 复制完成
// 释放了与某个硬件的连接释放~~~
---------------------------------------------------------------------------------------------
public class MyConnection implements AutoCloseable{ // 实现AutoCloseable接口
@Override // 要重写AutoCloseable接口里的close方法
public void close() throws Exception {
System.out.println("释放了与某个硬件的连接释放~~~");
}
}
注意:
- ( ) 中只能放置资源,否则报错
- 什么是资源呢? 资源一般指的是最终实现了AutoCloseable接口
Public abstract class InputStream implements Closeable{ };
Public abstract class OutputStream implements Closeable,Flushable{ };
Public interface Closeable extends AutoCloseable{ };