File、方法递归、IO流
1.File类概述
File类在包java.io.File下、代表操作系统的文件对象(文件、文件夹)。
File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能。
File类作用:创建对象定位文件,可以删除、获取文件信息等。但是不能读写文件内容。
File类构造器
方法名 |
说明 |
public File (String pathname) |
根据文件路径创建文件对象 |
public File (String parent, String child) |
从父路径名字符串和子路径名字符串创建文件对象 |
public File (File parent, String child) |
根据父路径对应文件对象和子路径名字符串 创建文件对象 |
File对象可以定位文件和文件夹。
File封装的对象仅仅是一个路径名,这个路径可以是存在的,也可以是不存在的。
绝对路径和相对路径
如下图所示,绝对路径:从盘符开始,依赖于当前系统。
如下图所示,相对路径:不带盘符,默认直接到当前工程下的目录寻找文件。
示例代码如下:
publicstaticvoidmain(String[] args) { // 1.创建File对象(指定文件路径)// 路径写法// 反斜杠\ D:\360Downloads\modiloginbg.jpgFilef=newFile("D:\\360Downloads\\modiloginbg.jpg"); // 正斜杠/ D:/360Downloads/modiloginbg.jpg// File f = new File("D:/360Downloads/modiloginbg.jpg");// API形式生成拼接符替代正(反)斜杠 File.separator 好处:跨平台,可以自动生成Windows、Linux等不同平台的拼接符// File f = new File("D:" + File.separator + "360Downloads" + File.separator + "modiloginbg.jpg");longsize=f.length(); // 返回文件字节大小System.out.println(size); // 612265// 2.※File创建对象,支持绝对路径也支持相对路径// 绝对路径:从盘符开始Filef2=newFile("D:\\360Downloads\\214510.jpg"); System.out.println(f2.length()); // 363322// 相对路径:不带盘符,通常用来定义模块中的某个文件,相对于工程下的路径Filef3=newFile("day10_file_io_app\\src\\2055376.jpg"); System.out.println(f3.length()); // 777576// 3.File创建对象,可以是文件,也可以是文件夹Filef4=newFile("D:\\360Downloads"); // System.out.println(f4.length()); // 一般不通过这种方式获取文件夹的大小System.out.println(f4.exists()); // true 判断该文件对象是否存在 }
注:路径写法
①反斜杠\D:\360Downloads\modiloginbg.jpg
File f = new File("D:\\360Downloads\\modiloginbg.jpg");
②正斜杠/D:/360Downloads/modiloginbg.jpg
File f = new File("D:/360Downloads/modiloginbg.jpg");
③API形式生成拼接符替代正(反)斜杠File.separator
File f = new File("D:" + File.separator + "360Downloads" + File.separator + "modiloginbg.jpg");
好处:跨平台,可以自动生成Windows、Linux等不同平台的拼接符。
2.File类的常用API
File类的判断文件类型、获取文件信息功能常用API
方法名 |
说明 |
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 long length() |
返回文件字节大小 |
示例代码如下:
publicstaticvoidmain(String[] args) { // 绝对路径Filef1=newFile("D:\\360Downloads\\214510.jpg"); // 1.public boolean isDirectory() 测试此抽象路径名表示的File是否为文件夹System.out.println(f1.isDirectory()); // false// 2.public boolean isFile() 测试此抽象路径名表示的File是否为文件System.out.println(f1.isFile()); // true// 3.public boolean exists() 测试此抽象路径名表示的File是否存在System.out.println(f1.exists()); // true// 4.public String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串System.out.println(f1.getAbsolutePath()); // D:\360Downloads\214510.jpg// 5.public String getPath() 将此抽象路径名转换为路径名字符串(怎么定义的就怎么拿)System.out.println(f1.getPath()); // D:\360Downloads\214510.jpg// 6.public String getName() 返回由此抽象路径名表示的文件名+后缀或文件夹名System.out.println(f1.getName()); // 214510.jpg// 7.public long lastModified() 返回文件最后修改的时间毫秒值longtime=f1.lastModified(); System.out.println("最后修改时间:"+newSimpleDateFormat("yyyy-MM-ss HH:mm:ss").format(time)); // 最后修改时间:2021-04-50 13:02:50 }
File类创建文件的功能常用API
方法名 |
说明 |
public boolean createNewFile() |
创建一个新的空的文件(几乎不用) |
public boolean mkdir() |
只能创建一级文件夹 |
public boolean mkdirs() |
可以创建多级文件夹 |
File类删除文件的功能常用API
方法名 |
说明 |
public boolean delete () |
删除由此抽象路径名表示的文件或空文件夹(文件在被占用状态时也可以被删除) |
示例代码如下:
publicstaticvoidmain(String[] args) throwsIOException { Filef=newFile("day10_file_io_app\\src\\2055376.jpg"); // 1.public boolean createNewFile() 创建一个新的空的文件(几乎不用)System.out.println(f.createNewFile()); // falseFilef2=newFile("day10_file_io_app\\src\\2055376aaa.jpg"); System.out.println(f2.createNewFile()); // true// 2.public boolean mkdir() 只能创建一级文件夹Filef3=newFile("D:\\360Downloads\\abc"); System.out.println(f3.mkdir()); // trueFilef4=newFile("D:\\360Downloads\\abc\\def\\ghi"); System.out.println(f4.mkdir()); // false// 3.public boolean mkdirs() 可以创建多级文件夹Filef5=newFile("D:\\360Downloads\\abc\\def\\ghi"); System.out.println(f5.mkdirs()); // true// 4.public boolean delete () 删除由此抽象路径名表示的文件或空文件夹(文件在被占用状态时也可以被删除)System.out.println(f2.delete()); // trueSystem.out.println(f3.delete()); // false 非空文件夹不可删除System.out.println(f5.delete()); // true 只能删除空文件夹 }
注:delete方法默认只能删除文件和空文件夹。
delete方法直接删除不走回收站。
File类的遍历功能
方法名 |
说明 |
public String[] list() |
获取当前目录下所有的"一级文件名称" 到一个字符串数组中去返回 |
public File[] listFiles()(常用) |
获取当前目录下所有的"一级文件对象" 到一个文件对象数组中去返回(重点) |
示例代码如下:
publicstaticvoidmain(String[] args) { Filef1=newFile("D:\\360Downloads"); // 1.public String[] list()// 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回String[] nameArray=f1.list(); System.out.println(Arrays.toString(nameArray)); // [2055376.jpg, 214510.jpg, abc, addlogin.jpg, bgpic5.jpg, loginbg.jpg, loginbg1.jpg, loginbg2.jpg, modiloginbg.jpg, Software, wpcache]// 2.public File[] listFiles()(常用)// 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)File[] fileArray=f1.listFiles(); for (Filefile : fileArray) { System.out.println(file.getAbsolutePath()); } /*D:\360Downloads\2055376.jpgD:\360Downloads\214510.jpgD:\360Downloads\abcD:\360Downloads\addlogin.jpgD:\360Downloads\bgpic5.jpgD:\360Downloads\loginbg.jpgD:\360Downloads\loginbg1.jpgD:\360Downloads\loginbg2.jpgD:\360Downloads\modiloginbg.jpgD:\360Downloads\SoftwareD:\360Downloads\wpcache*/ }
listFiles方法注意事项:
当调用者不存在时,返回null。
当调用者是一个文件时,返回null。
当调用者是一个空文件夹时,返回一个长度为0的数组。
当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回。
当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容。
当调用者是一个需要权限才能进入的文件夹时,返回null。
3.方法递归
3.1递归的形式和特点
什么是方法递归?
方法直接调用自己或者间接调用自己的形式称为方法递归(recursion)。
递归做为一种算法在程序设计语言中广泛应用。
递归的形式
直接递归:方法自己调用自己。
间接递归:方法调用其他方法,其他方法又回调方法自己。
方法递归存在的问题?
递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象。
示例代码如下:
publicclassRecursionDemo1 { publicstaticvoidmain(String[] args) { // test(); // 进入死循环,栈内存溢出错误// test2(); // 进入死循环,栈内存溢出错误 } // 直接递归publicstaticvoidtest() { System.out.println("--------test执行--------"); test(); // 方法递归,直接递归 } // 间接递归publicstaticvoidtest2() { System.out.println("-------test2执行--------"); test3(); // 方法递归,间接递归 } privatestaticvoidtest3() { System.out.println("-------test3执行--------"); test2(); } }
3.2递归的算法流程、核心要素
案例:递归案例导学-计算n的阶乘
需求:计算n的阶乘的结果,使用递归思想解决,我们先从数学思维上理解递归的流程和核心点。
分析:
① 假如我们认为存在一个公式是 f(n) = 1*2*3*4*5*6*7*…(n-1)*n;
② 那么公式等价形式就是: f(n) = f(n-1) *n
③ 如果求的是 1-5的阶乘 的结果,手工应该应该如何应用上述公式计算。
④ f(5) = f(4) * 5f(4) = f(3) * 4f(3) = f(2) * 3f(2) = f(1) * 2f(1) = 1
递归解决问题的思路
把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归算法三要素大体可以总结为:
递归的公式: f(n) = f(n-1) * n
递归的终结点:f(1)
递归的方向必须走向终结点
示例代码如下:
publicclassRecursionDemo2 { publicstaticvoidmain(String[] args) { /*案例:递归案例导学-计算n的阶乘需求:计算n的阶乘的结果,使用递归思想解决,我们先从数学思维上理解递归的流程和核心点。分析:① 假如我们认为存在一个公式是 f(n) = 1*2*3*4*5*6*7*…(n-1)*n;② 那么公式等价形式就是: f(n) = f(n-1) *n③ 如果求的是 1-5的阶乘 的结果,手工应该应该如何应用上述公式计算。④ f(5) = f(4) * 5 f(4) = f(3) * 4 f(3) = f(2) * 3 f(2) = f(1) * 2 f(1) = 1*/System.out.println("5的阶乘是:"+f(5)); // 5的阶乘是:120 } publicstaticintf(intn) { if (n==1) { return1; } else { returnf(n-1) *n; } } }
3.3递归经典问题
案例-猴子吃桃问题
猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个。第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个。以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个。等到第10天的时候发现桃子只有1个了。
需求:请问猴子第一天摘了多少个桃子?
分析:
① 整体来看,每一天都是做同一个事件,典型的规律化问题,考虑递归三要素:
② 递归公式:f(n) = 2 * (f(n + 1) + 1)
③ 递归终结点:f(10) = 1
④ 递归方向:n不断增大,向10靠拢,合理。
示例代码如下:
publicclassRecursionDemo4 { publicstaticvoidmain(String[] args) { /*案例-猴子吃桃问题猴子第一天摘下若干桃子,当即吃了一半,觉得好不过瘾,于是又多吃了一个。第二天又吃了前天剩余桃子数量的一半,觉得好不过瘾,于是又多吃了一个。以后每天都是吃前天剩余桃子数量的一半,觉得好不过瘾,又多吃了一个。等到第10天的时候发现桃子只有1个了。 需求:请问猴子第一天摘了多少个桃子? 分析:① 整体来看,每一天都是做同一个事件,典型的规律化问题,考虑递归三要素:② 递归公式:f(n) = 2 * (f(n + 1) + 1)③ 递归终结点:f(10) = 1④ 递归方向:n不断增大,向10靠拢,合理。*/System.out.println("第一天摘下"+eat(1) +"个桃子"); // 第一天摘下1534个桃子 } publicstaticinteat(intn) { if (n==10) { return1; } else { return (eat(n+1) +1 ) *2; } } }
3.4非规律化递归案例-文件搜索
案例-文件搜索
需求:文件搜索、从C:盘中,搜索出某个文件名称并输出绝对路径。
分析:
① 先定位出的应该是一级文件对象。
② 遍历全部一级文件对象,判断是否是文件。
③ 如果是文件,判断是否是自己想要的。
④ 如果是文件夹,需要继续递归进去重复上述过程。
示例代码如下:
publicclassRecursionDemo5 { publicstaticvoidmain(String[] args) { // 需求:去D盘搜索2055376.jpg文件searchFile(newFile("D:/"), "2055376.jpg"); /*文件存在,路径名称:D:\360Downloads\2055376.jpg文件存在,路径名称:D:\sss2055376.jpg*/ } /*** create by: 全聚德在逃烤鸭、* description: 搜索某个目录下的所需文件* create time: 2022/6/3 0003 15:50** @param dir 查询目录* @param fileName 被搜索的文件名称* @return void*/publicstaticvoidsearchFile(Filedir, StringfileName) { // 判断是否是文件夹if (dir!=nul&&dir.isDirectory()) { File[] fileArray=dir.listFiles(); // 判断文件数组中是否不为null且有内容(数组为null时,增强for循环报错空值异常)if (fileArray!=nul&&fileArray.length>0) { for (Filefile : fileArray) { // 判断是否是文件if (file.isFile()) { // 是文件,判断是否是被搜索的文件if (file.getName().contains(fileName)) { System.out.println("文件存在,路径名称:"+file.getAbsolutePath()); } } else { // 否则为文件夹,调用递归方法searchFile继续向下搜索searchFile(file, fileName); } } } } else { System.out.println("当前搜索的位置不是文件夹!"); } } }
注:切记注意空值异常的问题,尽可能增加判断空值的代码!!!
3.5非规律化递归案例-啤酒问题
需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子。
答案:15瓶 3盖子 1瓶子。
第一种方式:将空瓶、瓶盖折算为金钱,买酒方法参数为money。
示例代码如下:
publicclassRecursionDemo6 { // 定义静态变量,代表买酒的数目,每轮买酒后增加publicstaticinttotalNumber; publicstaticintlastBottleNumber; // 上轮剩余的空瓶数publicstaticintlastCoverNumber; // 上轮剩余的瓶盖数publicstaticvoidmain(String[] args) { // 需求:需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子// 答案:15瓶 3盖子 1瓶子buyBeer(10); // 可以喝15瓶酒,剩余1个空瓶,3个瓶盖 } publicstaticvoidbuyBeer(intmoney) { intbuyNumber=money/2; // 判断剩余的钱是否可以买酒if (buyNumber==0) { // 若不能买酒,则退出并输出相应信息System.out.println("可以喝"+totalNumber+"瓶酒,剩余"+lastBottleNumber+"个空瓶,"+lastCoverNumber+"个瓶盖"); return; } totalNumber+=buyNumber; // 更新买酒的数目// 统计本轮空瓶与瓶盖总数intbottleNumber=lastBottleNumber+buyNumber; intcoverNumber=lastCoverNumber+buyNumber; // 统计可以换算的用于买酒的钱intallMoney=money-buyNumber*2; // 空瓶相当于1元一瓶,满两瓶可买一瓶啤酒,相当于2元allMoney+= (bottleNumber/2) *2; // 若不满2瓶,则无法购买,因此需要保留lastBottleNumber / 2lastBottleNumber=bottleNumber%2; // 更新本轮剩余空瓶数,用于下一轮买酒// 瓶盖相当于0.5元一个,满四个可买一瓶啤酒,相当于2元allMoney+= (coverNumber/4) *2; // 若不满4个,则无法购买,因此需要保留lastCoverNumber / 4lastCoverNumber=coverNumber%4; // 更新本轮剩余瓶盖瓶数,用于下一轮买酒// 调用递归方法,进行下一轮买酒buyBeer(allMoney); } }
第二种方式:买酒方法参数为金钱、当前空瓶数量、当前瓶盖数量。
示例代码如下:
publicclassRecursionDemo7 { // 定义静态变量,代表买酒的数目,每轮买酒后增加publicstaticinttotalNumber; publicstaticvoidmain(String[] args) { // 需求:需求:啤酒2元1瓶,4个盖子可以换一瓶,2个空瓶可以换一瓶,请问10元钱可以喝多少瓶酒,剩余多少空瓶和盖子// 答案:15瓶 3盖子 1瓶子buyBeer(10, 0, 0); // 可以喝15瓶酒,剩余1个空瓶,3个瓶盖 } publicstaticvoidbuyBeer(intmoney, intlastBottleNumber, intlastCoverNumber) { intbottleNumber=lastBottleNumber; // 本轮剩余空瓶数目intcoverNumber=lastCoverNumber; // 本轮剩余瓶盖数目// 金额足够用钱买酒,钱不够了用空瓶、瓶盖换酒// 判断剩余金额是否足够买酒if (money>=2) { intbuyNumber=money/2; totalNumber+=buyNumber; // 更新买酒的数目money-=buyNumber*2; // 更新剩余金额// 更新当前剩余空瓶、瓶盖数目bottleNumber+=buyNumber; coverNumber+=buyNumber; } else { if (bottleNumber>=2) { intbuyNumber=bottleNumber/2; // 空瓶换购啤酒数目totalNumber+=buyNumber; // 更新买酒的数目// 更新当前剩余空瓶、瓶盖数目bottleNumber=bottleNumber%2+buyNumber; coverNumber+=buyNumber; } if (coverNumber>=4) { intbuyNumber=coverNumber/4; // 瓶盖换购啤酒数目totalNumber+=buyNumber; // 更新买酒的数目// 更新当前剩余空瓶、瓶盖数目bottleNumber+=buyNumber; coverNumber=coverNumber%4+buyNumber; } } // 调用递归方法,进行下一轮买酒if (money>=2||bottleNumber>=2||coverNumber>=4) { // 若满足下一轮买酒条件则继续,否则结束买酒buyBeer(money, bottleNumber, coverNumber); } else { System.out.println("可以喝"+totalNumber+"瓶酒,剩余"+bottleNumber+"个空瓶,"+coverNumber+"个瓶盖"); } } }
4.字符集
4.1常见字符集介绍
字符集基础知识
计算机底层不可以直接存储字符的。计算机中底层只能存储二进制(0、1)。
二进制是可以转换成十进制的。
结论:计算机底层可以表示十进制编号,计算机给人类字符进行编号存储的编号规则就是字符集。
ASCII字符集
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码):包括了数字、英文、符号。
ASCII使用1个字节存储一个字符,一个字节是8位,总共可以表示128个字符信息,对于英文,数字来说是够用的。
GBK
Windows系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字。
注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字。
Unicode码表
Unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准,容纳世界上大多数国家的所有常见文字和符号。
由于Unicode会先通过UTF-8,UTF-16,以及UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是UTF-8(UTF-8是Unicode的一种编码方式)。
注:
Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储。
UTF-8也要兼容ASCII编码表。
技术人员都应该使用UTF-8的字符集编码。
编码前和编码后的字符集需要一致,否则会出现中文乱码。
汉字存储和展示过程如下图所示。
知识点汇总:
字符串常见的字符底层组成是什么样的?
英文和数字等在任何国家的字符集中都占1个字节。
GBK字符中一个中文字符占2个字节。
UTF-8编码中一个中文1般占3个字节。
编码前的字符集和编码后的字符集有什么要求?
必须一致,否则会出现中文字符乱码。
英文和数字在任何国家的编码中都不会乱码。
4.2字符集的编码、解码操作
String编码常用API
方法名 |
说明 |
byte[] getBytes() |
使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中 |
byte[] getBytes(String charsetName) |
使用指定的字符集将该 String编码为一系列字节, 将结果存储到新的字节数组中 |
String解码常用构造器
方法名 |
说明 |
String (byte[] bytes) |
通过使用平台的默认字符集解码指定的字节数组来构造新的String |
String (byte[] bytes, String charsetName) |
通过指定的字符集解码指定的字节数组 来构造新的String |
String(byte bytes[], int offset, int length) |
解码指定的字节数组,指定开始位置与解码长度 |
示例代码如下:
publicclassTest { publicstaticvoidmain(String[] args) throwsUnsupportedEncodingException { // 1.编码 把文字转换为字节(可以使用指定编码)Stringname="abc我爱你中国"; byte[] bytes=name.getBytes(); // 以当前代码默认字符集进行编码(当前默认UTF-8)// 英文、数字在UTF-8字符集中为正数,汉字为负数System.out.println(Arrays.toString(bytes)); // [97, 98, 99, -26, -120, -111, -25, -120, -79, -28, -67, -96, -28, -72, -83, -27, -101, -67]System.out.println(bytes.length); // 18// 指定编码(字符集)为GBKbyte[] bytes2=name.getBytes("GBK"); System.out.println(Arrays.toString(bytes2)); // [97, 98, 99, -50, -46, -80, -82, -60, -29, -42, -48, -71, -6]System.out.println(bytes2.length); // 13// 2.解码 把字节转换为文字(编码前和编码后的字符集必须一致,否则出现乱码)Stringrs=newString(bytes); // 以当前代码默认字符集进行解码(当前默认UTF-8)System.out.println(rs); // abc我爱你中国// 指定解码(字符集)为GBKStringrs2=newString(bytes2, "GBK"); System.out.println(rs2); // abc我爱你中国 } }
5.IO流概述
IO流也称为输入、输出流,就是用来读写数据的。
I表示intput,是数据从硬盘文件读入到内存的过程,称之输入,负责读。
O表示output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写。
IO流的分类
字节输入流:以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流。
字节输出流:以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流。
字符输入流:以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流。
字符输出流:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件或者网络中去的流称为字符输出流。
IO流体系如下图所示。
6.字节流的使用
6.1文件字节输入流:每次读取一个字节
文件字节输入流:FileInputStream
作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去。
FileInputStream构造器
方法名 |
说明 |
public FileInputStream(File file) |
创建字节输入流管道与源文件对象接通 |
public FileInputStream(String pathname) |
创建字节输入流管道与源文件路径接通 |
FileInputStream常用API
方法名 |
说明 |
public int read() |
每次读取一个字节,将读取的数据内容的编号返回, 如果字节已经没有可读的返回-1 |
public int read(byte[] buffer) |
每次读取一个字节数组,将读取的数据数目返回,读取的数据内容保存在字节数组buffer中,如果字节已经没有可读的返回-1 |
示例代码如下;
publicstaticvoidmain(String[] args) throwsException { // 1.创建一个文件字节输入流管道与源文件接通InputStreamis=newFileInputStream("day10_file_io_app\\src\\data.txt"); // 读取第一个字节返回intb1=is.read(); System.out.println(b1); // 97 字符编号System.out.println((char) b1); // a 转为字符型intb2=is.read(); System.out.println(b2); // 98System.out.println((char) b2); // bintb3=is.read(); System.out.println(b3); // 99System.out.println((char) b3); // c// 如果字节已经没有可读的返回-1intb4=is.read(); System.out.println(b4); // -1 }
注:每次读取一个字节会不可避免的存在一些问题。
性能较慢。
读取中文字符输出无法避免乱码问题(每次读取1个字节,1个中文字符在UTF-8中占3个字节)。
6.2文件字节输入流:每次读取一个字节数组
示例代码如下:
publicstaticvoidmain(String[] args) throwsException { // 1.创建一个文件字节输入流管道与源文件接通InputStreamis=newFileInputStream("day10_file_io_app\\src\\data2.txt"); // 2.定义一个字节数组,用于读取字节数组中的数据byte[] buffer=newbyte[3]; // 每次最多读取3B的数据intnumber=is.read(buffer); System.out.println("读取了几个字节:"+number); // 读取了几个字节:3// 解码 把字节转换为文字Stringrs=newString(buffer); System.out.println(rs); // abcintnumber2=is.read(buffer); System.out.println("读取了几个字节:"+number2); // 读取了几个字节:3Stringrs2=newString(buffer); System.out.println(rs2); // 我intnumber3=is.read(buffer); System.out.println("读取了几个字节:"+number3); // 读取了几个字节:2// String rs3 = new String(buffer);// System.out.println(rs3); // 12�/*第28行后,此时字节数组buffer没有清空,其中存储的是代表汉字"我"的三个字节,再次进行读取时,仅能读取2个字节,因此将"12"代表的2个字节存入至buffer的前2位中第3位仍为代表汉字"我"的3个字节中的第3位,因此乱码*/// 改进 读取多少数据,转换多少数据Stringrs3=newString(buffer, 0, number3); // 指定开始位置与解码长度System.out.println(rs3); // 12// 如果字节已经没有可读的返回-1intnumber4=is.read(buffer); System.out.println("读取了几个字节:"+number4); // 读取了几个字节:-1 }
注:
第28行后,此时字节数组buffer没有清空,其中存储的是代表汉字"我"的三个字节,再次进行读取时,仅能读取2个字节,因此将"12"代表的2个字节存入至buffer的前2位中,第3位仍为代表汉字"我"的3个字节中的第3位,因此乱码。
每次读取一个字节数组会不可避免的存在一些问题。
读取中文字符输出无法避免乱码问题(读取字节数组容易将代表每个中文字符的3个字节截断,在转换为文本时会出现乱码)。
如何使用字节输入流读取中文内容输出不乱码呢?
定义一个与文件一样大的字节数组,一次性读取完文件的全部字节。
示例代码如下:
publicstaticvoidmain(String[] args) throwsException { Filef=newFile("day10_file_io_app\\src\\data3.txt"); InputStreamis=newFileInputStream(f); // 获取文件大小并强转为int型intlength= (int) f.length(); byte[] buffer=newbyte[length]; intnumber=is.read(buffer); System.out.println("读取了多少字节:"+number); // 读取了多少字节:139System.out.println("文件大小:"+f.length() +"字节"); // 文件大小:139字节Stringrs=newString(buffer); System.out.println(rs); /*abc我12dsadasd撒大声地所多sdggabc我12dsadasd撒大声地所多sdggabc我12dsadasd撒大声地所多s的dggsfSDAS是RQ示范法123*/ }
注:直接把文件数据全部读取到一个字节数组可以避免乱码,是否存在问题?
如果文件过大,定义的字节数组可能引起内存溢出。
6.3文件字节输出流:写字节数据到文件
文件字节输出流:FileOutputStream
作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流。
FileOutputStream构造器
方法名 |
说明 |
public FileOutputStream (File file) |
创建字节输出流管道与源文件对象接通 |
public FileOutputStream (File file,boolean append) |
创建字节输出流管道与源文件对象接通,可追加数据 |
public FileOutputStream (String filepath) |
创建字节输出流管道与源文件路径接通 |
public FileOutputStream (String filepath,boolean append) |
创建字节输出流管道与源文件路径接通,可追加数据 |
FileOutputStream常用API
方法名 |
说明 |
public void write(int a) |
写一个字节出去 |
public void write(byte[] buffer) |
写一个字节数组出去 |
public void write(byte[] buffer , int pos , int len) |
写一个字节数组的一部分出去 |
流的关闭与刷新
方法名 |
说明 |
flush() |
刷新流,还可以继续写数据 |
close() |
关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
示例代码如下:
publicclassFileOutputStreamDemo4 { publicstaticvoidmain(String[] args) throwsException { // 1.创建一个文件字节输出流管道与目标文件接通// OutputStream os = new FileOutputStream("day10_file_io_app\\output04.txt"); // 写路径,自动创建路径中的文件(会先清空之前的数据,再将新数据写入)OutputStreamos=newFileOutputStream("day10_file_io_app\\output04.txt", true); // 追加数据,不会先清空之前的数据// public void write(int a) 写一个字节出去os.write('a'); os.write(98); // public void write(byte[] buffer) 写一个字节数组出去byte[] buffer= {'a', 97, 98}; os.write(buffer); os.write("\r\n".getBytes()); // 换行byte[] buffer2="我爱你中国".getBytes(); os.write(buffer2); // public void write(byte[] buffer , int pos , int len) 写一个字节数组的一部分出去byte[] buffer3= {'d', 58, 28, 39}; os.write(buffer3, 0, 3); // 将buffer数组中第0位开始的前3位写入到文件中// 刷新流,写数据必须刷新数据os.flush(); // 用完后一定要关闭流,释放资源(关闭流中包含刷新流)os.close(); } }
注:os.write("\r\n".getBytes());是换行。
6.4文件拷贝
需求:把某个视频复制到其他文件目录下。
思路:
① 根据数据源创建字节输入流对象。
② 根据目的地创建字节输出流对象。
③ 读写数据,复制视频。
④ 释放资源。
示例代码如下:
publicclassFileCopyDemo05 { publicstaticvoidmain(String[] args) { try { // 1.创建一个字节输入流管道与原文件接通InputStreamis=newFileInputStream("E:\\录屏\\学生作业相关知识学习.mp4"); // 2.创建一个字节输出流管道与目标文件接通OutputStreamos=newFileOutputStream("C:\\Users\\Administrator\\Desktop\\CopyMp4.mp4"); // 一定要加上目标文件名// 3.定义一个字节数组,用于文件复制byte[] buffer=newbyte[1024]; // 每次读取1kb的数据// 不能用此种方式,每次循环is.read(buffer)输入流执行了两次,需要定义变量来记录读取长度/* while (is.read(buffer) != -1) {os.write(buffer, 0, is.read(buffer)); // 读多少倒多少}*/// 定义变量记录读取的字节数intlen; while ((len=is.read(buffer)) !=-1) { os.write(buffer, 0, len); // 读多少倒多少 } System.out.println("复制结束"); // 切记关闭流os.close(); // 一般从里向外关is.close(); } catch (FileNotFoundExceptione) { e.printStackTrace(); } catch (IOExceptione) { e.printStackTrace(); } } }
7.资源释放的方式
try-catch-finally
finally:在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源。
特点:被finally控制的语句最终一定会执行,除非JVM退出。
异常处理标准格式:try….catch…finally
注:遇到return语句时,先执行finally代码块中的语句,再执行return语句。
try-catch-resource
finally虽然可以用于释放资源,但是释放资源的代码过于繁琐,有没有办法简化?
JDK 7中简化资源释放的改进如下图所示。
注:JDK 7的()中只能放置资源对象,,否则报错,try代码块执行结束后自动调用close()方法,释放资源。
什么是资源?
资源都是实现了Closeable/AutoCloseable接口的类对象,如下图示例所示。
8.字符流的使用
8.1文件字符输入流—一次读取一个字符
文件字符输入流:FileReader
作用:以内存为基准,把磁盘文件中的数据以字符的形式读取到内存中去。
Reader构造器
方法名 |
说明 |
public FileReader(File file) |
创建字符输入流管道与源文件对象接通 |
public FileReader(String pathname) |
创建字符输入流管道与源文件路径接通 |
Reader常用API
方法名 |
说明 |
public int read() |
每次读取一个字符,返回字符对应的编码, 如果字符已经没有可读的返回-1 |
public int read(char[] buffer) |
每次读取一个字符数组,返回读取的字符个数, 如果字符已经没有可读的返回-1 |
示例代码如下:
publicstaticvoidmain(String[] args) throwsException { // 1.创建一个字符输入流管道与源文件接通Readerr=newFileReader("day10_file_io_app\\src\\data3.txt"); // 2.每次读取一个字符返回,如果字符已经没有可读的返回-1intcode=r.read(); // 打印第一个字符对应的字符集(UTF-8)编码System.out.println(code); // 25105// 将字符集(UTF-8)编码转换为对应的字符System.out.println((char) code); // 我 }
注:字符流的好处:读取中文字符不会出现乱码(代码和文件编码一致的前提下)。
8.2文件字符输入流—一次读取一个字符数组
示例代码如下:
publicstaticvoidmain(String[] args) throwsException { Readerr=newFileReader("day10_file_io_app\\src\\data3.txt"); char[] buffer=newchar[3]; // 每次读取三个字符intlen; // 定义变量记录读取字符数while ((len=r.read(buffer)) !=-1) { // 将数组内容以字符串形式拼接起来System.out.print(newString(buffer, 0, len)); // 读取多少拼接多少 } }
注:最终打印时使用public String(char value[])而不是public static String toString(char[] a),前者是将数组中的字符/字节拼接为字符串返回,后者是将数组内容转换为字符串返回(带[])。
8.3文件字符输出流
文件字符输出流:FileWriter
作用:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件中去的流。
FileWriter构造器
方法名 |
说明 |
public FileWriter(File file) |
创建字符输出流管道与源文件对象接通 |
public FileWriter (File file,boolean append) |
创建字符输出流管道与源文件对象接通, 可追加数据 |
public FileWriter (String filepath) |
创建字符输出流管道与源文件路径接通 |
public FileWriter (String filepath, boolean append) |
创建字符输出流管道与源文件路径接通, 可追加数据 |
FileWriter常用API
方法名 |
说明 |
void write(int c) |
写一个字符 |
void write(char[] buffer) |
写入一个字符数组 |
void write(char[] buffer, int pos, int len) |
写入字符数组的一部分 |
void write(String str) |
写一个字符串 |
void write(String str, int pos, int len) |
写一个字符串的一部分 |
流的关闭与刷新
方法名 |
说明 |
flush() |
刷新流,还可以继续写数据 |
close() |
关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
示例代码如下:
publicstaticvoidmain(String[] args) { // 创建一个文件字符输出流管道与目标文件接通try ( // Writer w = new FileWriter("day10_file_io_app\\src\\data_out.txt")Writerw=newFileWriter("day10_file_io_app\\src\\data_out.txt", true); // 不清空原文件数据,在其基础上添加数据 ) { w.write("\r\n"); // 换行// 1.void write(int c) 写一个字符w.write(97); w.write('a'); w.write('中'); // 2.void write(char[] buffer) 写入一个字符数组char[] chars="abc我爱你北京".toCharArray(); w.write(chars); // 3.void write(char[] buffer, int pos, int len) 写入字符数组的一部分w.write(chars, 1, 5); // len代表字符数// 4.void write(String str) 写一个字符串w.write("123我爱你中国"); // 5.void write(String str, int pos, int len) 写一个字符串的一部分w.write("123我爱你中国", 3, 5); } catch (IOExceptione) { e.printStackTrace(); } }
注:w.write("\r\n");是换行。
字节流、字符流如何选择使用?
字节流适合做一切文件数据的拷贝(音视频,文本),不适合读取中文内容输出。
字符流适合做文本文件的操作(读,写)。