java基础学习_IO流02_递归、IO流字节流、IO流字符流(自学)_day20总结
============================================================================= ============================================================================= 涉及到的知识点有:
1:递归(理解) (1)递归的概述 (2)递归的注意事项 (3)递归的案例 2:IO流字节流(掌握) (1)IO流概述 (2)IO流的分类 (3)FileOutputStream写出数据(字节输出流) A:字节输出流的操作步骤 B:代码体现 C:要注意的问题? D:字节流写数据的方式 E:字节流写数据加入异常处理 (4)FileInputStream读取数据(字节输入流) A:字节输入流的操作步骤 B:代码体现 C:要注意的问题? D:字节流读取数据的方式 (5)案例:2种方式实现 (6)字节缓冲区流 (7)案例:4种方式实现 3:IO流字符流(自学) ============================================================================= ============================================================================= 1:递归(理解) (1)递归的概述 方法定义中调用方法本身的现象 举例: 从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚在给小和尚讲故事 ...... 庙挂了或者山崩了 学编程 -- 高薪就业 -- 挣钱 -- 娶媳妇 -- 生娃娃 -- 放羊 -- 挣学费 ...... 娶不到媳妇或者生不了娃娃 (2)递归的注意事项 A:递归一定要有出口,否则就是死递归。 B:递归的次数不能太多,否则就内存溢出。 C:构造方法不能递归使用。 (3)递归的案例 A:递归求阶乘
1 package cn.itcast_02; 2 3 /* 4 * 需求:请用代码实现求5的阶乘。 5 * 下面的知识要知道: 6 * 5! = 1*2*3*4*5 7 * 5! = 5*4! 8 * 9 * 有几种方案实现呢? 10 * A:循环实现 11 * B:递归实现 12 * a:做递归要写一个方法 13 * b:出口条件(就是已知的条件,比如题目所给的条件或者自然定理、常识等) 14 * c:规律 15 */ 16 public class DiGuiDemo { 17 public static void main(String[] args) { 18 // 循环实现 19 int jc = 1; 20 for (int x = 2; x <= 5; x++) { 21 jc *= x; 22 } 23 System.out.println("5的阶乘是:" + jc); 24 25 // 递归实现 26 System.out.println("5的阶乘是:" + jieCheng(5)); 27 } 28 29 /* 30 * 做递归要写一个方法: 31 * 返回值类型:int 32 * 参数列表:int n 33 * 出口条件:(就是已知的条件,比如题目所给的条件或者自然定理、常识等) 34 * if (n == 1) { 35 * return 1; 36 * } 37 * 规律: 38 * if (n != 1) { 39 * return n*方法名(n-1); 40 * } 41 */ 42 // 递归实现 43 public static int jieCheng(int n) { 44 if (n == 1) { 45 return 1; 46 } else { 47 return n * jieCheng(n - 1); 48 } 49 } 50 }
B:不死神兔问题(裴波那切数列问题)
1 package cn.itcast_02; 2 3 /* 4 * 不死神兔问题(裴波那切数列问题) 5 * 6 * 有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问第二十个月的兔子对数为多少? 7 * 8 * 分析:我们要想办法找规律 9 * 10 * 兔子对数 11 * 第一个月: 1 12 * 第二个月: 1 13 * 第三个月: 2 (老兔子生了一对兔子) 14 * 第四个月: 3 (老兔子又生了一对兔子) 15 * 第五个月: 5 (老兔子又又生了一对兔子,小兔子生了一对兔子) 16 * 第六个月: 8 (老兔子又又又生了一对兔子,小兔子又生了一对兔子) 17 * ...... 18 * 19 * 由此可见兔子对象的数据是: 20 * 1,1,2,3,5,8...... 21 * 规律: 22 * A:从第三项开始,每一项是前两项之和。 23 * B:说明前两项是已知的。 24 * 25 * 如何实现这个程序呢?有三种方式: 26 * A:数组实现 27 * B:变量的变化实现 28 * C:递归实现 29 * 30 * 假如相邻的两个月的兔子对数是a,b 31 * 第一个相邻的数据:a=1,b=1 32 * 第二个相邻的数据:a=1,b=2 33 * 第三个相邻的数据:a=2,b=3 34 * 第四个相邻的数据:a=3,b=5 35 * 第五个相邻的数据:a=5,b=8 36 * ...... 37 * 规律: 38 * 下一次的a是以前的b,下一次的b是以前的a+b 39 */ 40 public class DiGuiDemo2 { 41 public static void main(String[] args) { 42 // 数组实现 43 // 定义一个数组 44 int[] arr = new int[20]; 45 // 前两项是已知的 46 arr[0] = 1; 47 arr[1] = 1; 48 // 从第三项开始,每一项是前两项之和。 49 // arr[2] = arr[0] + arr[1]; 50 // arr[3] = arr[1] + arr[2]; 51 // ...... 52 for (int x = 2; x < arr.length; x++) { 53 arr[x] = arr[x - 2] + arr[x - 1]; 54 } 55 System.out.println(arr[19]); // 6765 56 System.out.println("----------------"); 57 58 // 变量的变化实现 59 int a = 1; 60 int b = 1; 61 for (int x = 0; x < 18; x++) { // 这里的循环只控制次数,变量x不参与运算。循环1次得到的b是2(相当于第三个月),循环2次得到的b是3(相当于第四个月)......循环18次得到的b是6765(相当于第二十个月) 62 // 临时变量存储上一次的a 63 int temp = a; 64 a = b; // 下一次的a是以前的b 65 b = temp + b; // 下一次的b是以前的a+b 66 } 67 System.out.println(b); // 6765 68 System.out.println("----------------"); 69 70 // 递归实现 71 System.out.println(fib(20)); 72 } 73 74 /* 75 * 方法: 返回值类型:int 参数列表:int n 76 * 出口条件: 第一个月是1,第二个月是1 77 * 规律: 从第三个月开始,每一个月是前两个月之和 78 */ 79 public static int fib(int n) { 80 if (n == 1 || n == 2) { 81 return 1; 82 } else { 83 return fib(n - 1) + fib(n - 2); 84 } 85 } 86 }
C:递归遍历输出指定目录下所有指定后缀名的文件的绝对路径
1 package cn.itcast_03; 2 3 import java.io.File; 4 5 /* 6 * 需求:请大家把E:JavaSE目录下所有的以java结尾的文件的绝对路径给输出在控制台。 7 * 8 * 分析: 9 * A:把 E:\JavaSE 封装成一个File对象 10 * B:获取该目录下所有文件或者文件夹的File数组 11 * C:遍历该File数组,得到每一个File对象 12 * D:判断该File对象是否是文件夹 13 * 是:回到B(递归) 14 * 否:继续判断是否以.java结尾 15 * 是:就输出该文件的绝对路径 16 * 否:不搭理它 17 */ 18 public class FilePathDemo { 19 public static void main(String[] args) { 20 // 把 E:\JavaSE 封装成一个File对象 21 File srcFolder = new File("E:\JavaSE"); 22 23 // 递归功能实现 24 getAllJavaFilePaths(srcFolder); 25 } 26 27 private static void getAllJavaFilePaths(File srcFolder) { 28 // 获取该目录下所有文件或者文件夹的File数组 29 File[] fileArray = srcFolder.listFiles(); 30 31 // 遍历该File数组,得到每一个File对象 32 for (File file : fileArray) { 33 // 判断该File对象是否是文件夹 34 if (file.isDirectory()) { 35 // 是:回到B(递归) 36 getAllJavaFilePaths(file); 37 } else { 38 // 继续判断是否以.java结尾 39 if (file.getName().endsWith(".java")) { 40 // 是:就输出该文件的绝对路径 41 System.out.println(file.getAbsolutePath()); 42 } 43 } 44 } 45 } 46 47 }
D:递归删除带内容的目录(小心使用)
1 package cn.itcast_03; 2 3 import java.io.File; 4 5 /* 6 * 需求:递归删除带内容的目录 7 * 8 * 目录我已经给定:demo 9 * 10 * 分析: 11 * A:把 demo 封装成一个File对象 12 * B:获取该目录下的所有文件或者文件夹的File数组 13 * C:遍历该File数组,得到每一个File对象 14 * D:判断该File对象是否是文件夹 15 * 是:回到B 16 * 否:就删除 17 */ 18 public class FileDeleteDemo { 19 public static void main(String[] args) { 20 // 把 demo 封装成一个File对象 21 File srcFolder = new File("demo"); 22 // 递归实现 23 deleteFolder(srcFolder); 24 } 25 26 private static void deleteFolder(File srcFolder) { 27 // 获取该目录下的所有文件或者文件夹的File数组 28 File[] fileArray = srcFolder.listFiles(); 29 30 if (fileArray != null) { // 增强for的目标fileArray不能为null,否则会抛出空指针异常,那么我们加一个判断即可。 31 // 删文件 32 // 遍历该File数组,得到每一个File对象 33 for (File file : fileArray) { 34 // 判断该File对象是否是文件夹 35 if (file.isDirectory()) { 36 // 是:回到B(递归) 37 deleteFolder(file); 38 } else { 39 System.out.println(file.getName() + "---" + file.delete()); 40 } 41 } 42 43 // 删文件夹,srcFolder最开始是aaa,之后是bbb,之后是demo 44 System.out.println(srcFolder.getName() + "---" + srcFolder.delete()); 45 } 46 } 47 }
----------------------------------------------------------------------------- 2:IO流字节流(掌握) (1)IO流概述 IO流用来处理设备之间的数据传输。 上传文件和下载文件 Java对数据的操作是通过流的方式。 Java用于操作流的对象都在java.io包中。 (2)IO流的分类 A:按照数据流向分(以java程序为参考) 输入流 读取数据 输出流 写出数据 B:按照数据类型为 字节流 字节输入流 InputStream(抽象类) 字节输出流 OutputStream(抽象类) 字符流(为了方便操作文本数据) 字符输入流 Reader(抽象类) 字符输出流 Writer(抽象类) 注意: a:如果我们没有明确说明按照什么分,默认按照数据类型分。 b:除非文件用windows自带的记事本打开我们能够读懂,才采用字符流,否则建议使用字节流。 c:如果你什么都不知道,就用字节流。 --------------------------------------- (3)FileOutputStream写出数据(字节输出流) A:字节输出流的操作步骤 a:创建字节输出流对象 b:调用write()方法,写出数据 c:释放资源 B:代码体现: FileOutputStream fos = new FileOutputStream("fos.txt"); // 该构造方法会抛出一个异常 FileNotFoundException,上课为了代码一致性,throws抛给main函数了。 fos.write("hello".getBytes()); // 该write()方法会抛出一个异常 IOException,上课为了代码一致性,throws抛给main函数了。 fos.close(); // 该close()方法会抛出一个异常 IOException,上课为了代码一致性,throws抛给main函数了。 C:要注意的问题? a:创建字节输出流对象做了几件事情? 1:调用系统功能去创建文件。 2:创建字节输出流对象。 3:把字节输出流对象指向这个文件。 b:为什么一定要close()? 1:让流对象变成垃圾,这样就可以被垃圾回收器回收了。 2:通知系统去释放跟该文件相关的资源。(很重要) c:如何实现数据的换行? 不同的系统针的换行符号是不一样的! windows系统: linux系统: Mac系统: 而一些常见的高级记事本,是可以自动识别任意换行符号的。 d:如何实现数据的追加写入? 使FileOutputStream类的构造方法的第二个参数为true即可。 FileOutputStream fos = new FileOutputStream("fos3.txt", true); 注意: // 下面两句代码等价,常用第一种方式。 FileOutputStream fos = new FileOutputStream("fos3.txt"); FileOutputStream fos = new FileOutputStream("fos3.txt", false); D:字节流写数据的方式 public void write(int b) 一次写一个字节 public void write(byte[] b) 一次写一个字节数组 public void write(byte[] b, int off, int len) 一次写一个字节数组的一部分 E:字节流写数据加入异常处理 示例代码如下:
1 package cn.itcast_01; 2 3 import java.io.FileNotFoundException; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 7 /* 8 * 加入异常处理的字节输出流操作 9 */ 10 public class FileOutputStreamDemo4 { 11 public static void main(String[] args) { 12 // 分开做异常处理,代码的可读性差 13 // FileOutputStream fos = null; // 局部变量,引用数据类型,需要初始化为null。为什么呢?按理说 fos = new FileOutputStream("fos4.txt"); 这一句就可以初始化了啊? 14 // try { // 编译期异常,因为编译的时候,我不知道try里面有什么语句(编译只检查语法),只有运行的时候才知道try里面有什么,如果这句前面有一句抛出异常,则就不会执行到这一句了。fos 就不能初始化了。 15 // fos = new FileOutputStream("fos4.txt"); 16 // } catch (FileNotFoundException e) { 17 // e.printStackTrace(); 18 // } 19 // 20 // try { 21 // fos.write("java".getBytes()); 22 // } catch (IOException e) { 23 // e.printStackTrace(); 24 // } 25 // 26 // try { 27 // fos.close(); 28 // } catch (IOException e) { 29 // e.printStackTrace(); 30 // } 31 32 // 一起做异常处理,代码的可读性增强了 33 // try { 34 // FileOutputStream fos = new FileOutputStream("fos4.txt"); 35 // fos.write("java".getBytes()); 36 // fos.close(); 37 // } catch (FileNotFoundException e) { 38 // e.printStackTrace(); 39 // } catch (IOException e) { 40 // e.printStackTrace(); 41 // } 42 43 // 改进版 44 // 上面的close()方法可能会执行不到,所以把close()方法放在finally里面 45 // 为了在finally里面能够看到该对象就必须把该对象定义到外面, 46 // 为了通过对象调用方法不出问题,还必须给初始化值。 47 FileOutputStream fos = null; 48 try { 49 // fos = new FileOutputStream("z:\fos4.txt"); 50 fos = new FileOutputStream("fos4.txt"); 51 fos.write("java".getBytes()); 52 } catch (FileNotFoundException e) { 53 e.printStackTrace(); 54 } catch (IOException e) { 55 e.printStackTrace(); 56 } finally { 57 // 如果fos不是null,才需要close();fos是null,说明该对象没有new成功,你还close()谁啊!!! 58 if (fos != null) { 59 // 为了保证close()一定会执行,就放到这里了 60 try { 61 fos.close(); // 该close()方法也会抛出一个异常 IOException 62 } catch (IOException e) { 63 e.printStackTrace(); 64 } 65 } 66 } 67 68 } 69 }
--------------------------------------- (4)FileInputStream读取数据(字节输入流) A:字节输入流的操作步骤 a:创建字节输入流对象 b:调用read()方法,读取数据 c:释放资源 B:代码体现: FileInputStream fis = new FileInputStream("fos.txt"); // 该构造方法会抛出一个异常 FileNotFoundException,上课为了代码一致性,throws抛给main函数了。 // 方式1 int by = 0; // 这个赋值为0可要可不要 while ((by = fis.read()) != -1) { System.out.print((char) by); // 把int值转为字符(char) } // 方式2 // 数组的长度一般是1024或者1024的整数倍 byte[] bys = new byte[1024]; int len = 0; // 这个赋值为0可要可不要 while ((len = fis.read(bys)) != -1) { System.out.print(new String(bys, 0, len)); // 把一个字节数组的一部分转成字符串 } fis.close(); // 该close()方法会抛出一个异常 IOException,上课为了代码一致性,throws抛给main函数了。 注意:虽然我们有两种方式可以读取数据,但是,请注意,这两种方式在一个代码中针对同一个对象只能使用一种方式。
C:要注意的问题? a:由上面方式一可知:如果使用字节输入流读取的文件中有中文字符,那么读取到文件就会出现乱码,为什么呢? 因为我们把每一个读取到的中文都人为地转成一个char(字符)了。而输出的时候也是一个一个字节的输出(不会默认的做转换),所以就出现了乱码。 b:计算机是如何识别什么时候该把两个字节转换为一个中文呢?(windows默认GBK编码) 在计算机中中文的存储分两个字节: 第一个字节肯定是负数。 第二个字节常见的是负数,可能有正数。但是没影响。 UTF-8编码:最多用三个字节来表示一个字符。 GBK编码:一个中文二个字节表示。 D:字节流读取数据的方式 public int read() 一次读取一个字节 返回值是下一个数据字节,如果已到文件末尾,则返回-1。 public int read(byte[] b) 一次读取一个字节数组 返回值是实际读取的字节个数,如果已到文件末尾,则返回-1。 --------------------------------------- (5)案例:2种方式实现 A:复制文本文件 把 c盘下的a.txt 的内容复制到 d盘下的b.txt 中 B:复制图片 把 e:\林青霞.jpg 复制到 当前项目目录下的mn.jpg 中 C:复制视频 把 e:\哥有老婆.mp4 复制到 当前项目目录下的copy.mp4 中 (6)字节缓冲区流 字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果。 java本身在设计的时候,也考虑到了这样的设计思想(装饰设计模式后面讲解),所以提供了字节缓冲区流。 也即带缓冲区的字节类,这种类被称为:缓冲区类(高效类)。 java.lang.Object |--java.io.OutputStream(抽象类:输出流) |--java.io.FilterOutputStream(具体类:过滤输出流) |--java.io.BufferedOutputStream(具体类:字节缓冲输出流) A:字节缓冲输出流(写出数据) BufferedOutputStream B:字节缓冲输入流(读取数据) BufferedInputStream 注意事项: a:缓冲区类的构造方法可以指定缓冲区的大小,但是我们一般用不上,因为默认的缓冲区大小就足够了。 public BufferedOutputStream(OutputStream out) public BufferedOutputStream(OutputStream out, int size) b:为什么构造方法不传递一个具体的文件或者文件路径,而是传递一个OutputStream对象呢? 原因很简单,字节缓冲区流仅仅提供缓冲区的作用,为高效而设计的。但是呢,到底我的这个缓冲区作用能否发挥出来,真正的读写操作还得靠基本的流对象来实现。 即:到底我的这个缓冲区作用能否发挥出来,还得靠基本的流对象来实现。 也即:BufferedOutputStream是水杯,OutputStream是水(抽象的水),水有很多种:可乐、雪碧、矿泉水等。 c:虽然我们有两种方式可以读取数据,但是,请注意,这两种方式在一个代码中针对同一个对象只能使用一种方式。 --------------------------------------- (7)案例:4种方式实现 A:复制文本文件
把 c盘下的a.txt 的内容复制到 d盘下的b.txt 中 B:复制图片
把 e:\林青霞.jpg 复制到 当前项目目录下的mn.jpg 中 C:复制视频
把 e:\哥有老婆.mp4 复制到 当前项目目录下的copy.mp4 中
1 package cn.itcast_06; 2 3 import java.io.BufferedInputStream; 4 import java.io.BufferedOutputStream; 5 import java.io.FileInputStream; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 9 /* 10 * 需求:把 e:\哥有老婆.mp4 复制到 当前项目目录下的copy.mp4 中 11 * 12 * 字节流四种方式复制文件: 13 * 基本字节流一次读写一个字节 共耗时:117235毫秒 14 * 基本字节流一次读写一个字节数组 共耗时:156毫秒 15 * 高效字节流一次读写一个字节 共耗时:1141毫秒 16 * 高效字节流一次读写一个字节数组 共耗时:47毫秒 17 */ 18 public class CopyMp4Demo { 19 public static void main(String[] args) throws IOException { 20 long start = System.currentTimeMillis(); 21 // method1("e:\哥有老婆.mp4", "copy1.mp4"); 22 // method2("e:\哥有老婆.mp4", "copy2.mp4"); 23 // method3("e:\哥有老婆.mp4", "copy3.mp4"); 24 method4("e:\哥有老婆.mp4", "copy4.mp4"); 25 long end = System.currentTimeMillis(); 26 System.out.println("共耗时:" + (end - start) + "毫秒"); 27 } 28 29 // 高效字节流一次读写一个字节数组 30 public static void method4(String srcString, String destString) throws IOException { 31 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString)); 32 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString)); 33 34 byte[] bys = new byte[1024]; 35 int len = 0; 36 while ((len = bis.read(bys)) != -1) { 37 bos.write(bys, 0, len); 38 } 39 40 bos.close(); 41 bis.close(); 42 } 43 44 // 高效字节流一次读写一个字节 45 public static void method3(String srcString, String destString) throws IOException { 46 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString)); 47 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString)); 48 49 int by = 0; 50 while ((by = bis.read()) != -1) { 51 bos.write(by); 52 53 } 54 55 bos.close(); 56 bis.close(); 57 } 58 59 // 基本字节流一次读写一个字节数组 60 public static void method2(String srcString, String destString) throws IOException { 61 FileInputStream fis = new FileInputStream(srcString); 62 FileOutputStream fos = new FileOutputStream(destString); 63 64 byte[] bys = new byte[1024]; 65 int len = 0; 66 while ((len = fis.read(bys)) != -1) { 67 fos.write(bys, 0, len); 68 } 69 70 fos.close(); 71 fis.close(); 72 } 73 74 // 基本字节流一次读写一个字节 75 public static void method1(String srcString, String destString) throws IOException { 76 FileInputStream fis = new FileInputStream(srcString); 77 FileOutputStream fos = new FileOutputStream(destString); 78 79 int by = 0; 80 while ((by = fis.read()) != -1) { 81 fos.write(by); 82 } 83 84 fos.close(); 85 fis.close(); 86 } 87 }
----------------------------------------------------------------------------- 3:IO流字符流(自学)
IO流分类:(抽象类的有标注)
字节流:
InputStream(抽象类)
|--FilterInputStream(字节过滤输入流)
|--BufferedInputStream(字节缓冲区输入流)
|--FileInputStream(字节输入流)
OutputStream(抽象类)
|--FilterOutputStream(字节过滤输出流)
|--BufferedOutputStream(字节缓冲区输出流)
|--FileOutputStream(字节输出流)
字符流:
Reader(抽象类)
|--FilterReader(抽象类:字符过滤输入流)
|--InputStreamReader(字符转换输入流)
|--FileReader(字符转换输入流的简化写法)
|--BufferedReader(字符缓冲区输入流)
Writer(抽象类)
|--FilterWriter(抽象类:字符过滤输出流)
|--OutputStreamWriter(字符转换输出流)
|--FileWriter(字符转换输出流的简化写法)
|--BufferedWriter(字符缓冲区输出流)
名称的变化:字符转换流(=字符流=转换流) ==> 字符转换简化流 ==> 字符流
=============================================================================