File,IO流
File,IO流
变量,数组,对象,集合都是内容中的数据容器,他们在内存中,会因为断电,或者程序终止时会丢失
文件是非常重要的存储方式,他存储在计算机硬盘中,即便断电了,或者程序终止了,存储在硬盘文件中的数据也不会丢失
File类
而 File类,它就用来表示当前系统下的文件,或者文件夹,通过File类提供的方法可以获取文件大小,判断文件是否存在,创建文件,创建文件夹等
判断功能
//1.创建File对象 File f1 = new File("E/resource/meinv.jpg"); System.out.println(f1.length());//返回的是字节数 //2.file对象可以代表文件,也可以代表文件夹 File f2 = new File("E:/resource"); System.out.println(f2.length());//那文件夹的大小确实是不准确的 //3.File对象代表的文件路径可以是不存在的 File f3 = new File("E:/resource/aaabccc"); System.out.println(f3.exists()); //4.File对象的路径可以支持绝对路径,相对路径(相对) //什么是绝对路径?从磁盘开始一路寻找的路径 File f4 = new File("E:/resource/menice.jpg");
获取功能
File f1 = new File("D:/resource/ab.txt"); // 5.public String getName():获取文件的名称(包含后缀) System.out.println(f1.getName()); // 6.public long length():获取文件的大小,返回字节个数 System.out.println(f1.length()); // 7.public long lastModified():获取文件的最后修改时间。 long time = f1.lastModified(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); System.out.println(sdf.format(time)); // 8.public String getPath():获取创建文件对象时,使用的路径 File f2 = new File("D:\\resource\\ab.txt"); File f3 = new File("file-io-app\\src\\itheima.txt"); System.out.println(f2.getPath()); System.out.println(f3.getPath()); // 9.public String getAbsolutePath():获取绝对路径 System.out.println(f2.getAbsolutePath()); System.out.println(f3.getAbsolutePath());
创建和删除文件的方法
// 1、public boolean createNewFile():创建一个新文件(文件内容为空),创建成功返回true,反之。 File f1 = new File("D:/resource/itheima2.txt"); System.out.println(f1.createNewFile()); // 2、public boolean mkdir():用于创建文件夹,注意:只能创建一级文件夹 File f2 = new File("D:/resource/aaa"); System.out.println(f2.mkdir()); // 3、public boolean mkdirs():用于创建文件夹,注意:可以创建多级文件夹 File f3 = new File("D:/resource/bbb/ccc/ddd/eee/fff/ggg"); System.out.println(f3.mkdirs()); // 3、public boolean delete():删除文件,或者空文件,注意:不能删除非空文件夹。 System.out.println(f1.delete()); System.out.println(f2.delete()); File f4 = new File("D:/resource"); System.out.println(f4.delete()); }
值得注意的是
1.mkdir():只能创建单级文件夹 2.mkdirs():才能创建多级文件夹 3.delete():文件可以直接删除,但是文件夹只能删除空的文件夹
遍历文件夹
用来获取文件夹中的内容
public String[] list() 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回
public File[] listFiles() 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点)
// 1、public String[] list():获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。(了解) File f1 = new File("D:\\course\\待研发内容"); String[] names = f1.list(); for (String name : names) { System.out.println(name); } // 2、public File[] listFiles():(重点)获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回(重点) File[] files = f1.listFiles(); //用listFiles()将文件的所有一级对象放入File[]数组中,可以用遍历方式进行对象操作(******) for (File file : files) { System.out.println(file.getAbsolutePath());//地址 } File f = new File("D:/resource/aaa"); File[] files1 = f.listFiles(); System.out.println(Arrays.toString(files1)); }
注意
1.当主调是文件时,或者路径不存在时,返回null 2.当主调是空文件时,返回一个长度为0的数组 3.当主调是一个有内容的文件夹时,将里面所有的一级文件和文件夹路径放在File数组中,并把数组返回 4.当主调是一个文件夹,且里面有隐藏文件时,将里面所有文件和文件夹路径放在File数组中,包含隐藏文件 5.当主调是一个文件夹,但是没有权限访问时,返回null
递归
通过以上学习,只能获取到一级文件夹,那么如何获取文件夹中子文件夹中的内容呢,那么我们就需要使用递归这个知识点
什么是递归?
递归是一种算法,从形式上来说,方法调用自己的形式被称为递归
递归的形式:有直接递归,间接递归
/** * 目标:认识一下递归的形式。 */ public class RecursionTest1 { public static void main(String[] args) { test1(); } // 直接方法递归 public static void test1(){ System.out.println("----test1---"); test1(); // 直接方法递归 } // 间接方法递归 public static void test2(){ System.out.println("---test2---"); test3(); } public static void test3(){ test2(); // 间接递归 } }
如果执行上面代码,会进入死循环,最终导致栈内存溢出
递归算法执行过程
递归的执行
-
列如:5的阶乘 =5*4*3*2*1 假设f(n)表示n的阶乘,那么我们可以推导出下面的式子 f(5)=5*f(5-1) f(4)=4*f(4-1) f(3)=3*f(3-1) ... /** * 目标:掌握递归的应用,执行流程和算法思想。 */ public class RecursionTest2 { public static void main(String[] args) { System.out.println("5的阶乘是:" + f(5)); } //求n个数的阶乘 public static int f(int n){ // 终结点 if(n == 1){ return 1; }else { return f(n - 1) * n; } } }
该递归调用的特点:一层一层调用,再一层一层回返 ### 递归文件搜索(**) 接下来我们用递归算法来遍历文件夹 案例需求:在`D:\\`判断下搜索QQ.exe这个文件,然后直接输出。 ```java 1.先调用文件夹的listFiles方法,获取文件夹的一级内容,得到一个数组 2.然后再遍历数组,获取数组中的File对象 3.因为File对象可能是文件也可能是文件夹,所以接下来就需要判断 判断File对象如果是文件,就获取文件名,如果文件名是`QQ.exe`则打印,否则不打印 判断File对象如果是文件夹,就递归执行1,2,3步骤 所以:把1,2,3步骤写成方法,递归调用即可。
/** * @param dir 目录 * @param fileName 要搜索的文件名称 * @throws Exception */ public static void searchFile(File dir, String fileName) throws Exception { //1.把非法的情况都拦截住 // exists()表示要检查的目录 // isFile()表示是否是普通文件 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().contains(fileName)) { System.out.println("找到了" + f.getAbsoluteFile()); Runtime runtime = Runtime.getRuntime(); runtime.exec(f.getAbsolutePath()); } } else { //是文件夹,继续重复这个过程 searchFile(f, fileName); } } } } }
字符集
字符集的来历
由于计算机能够处理的数据只能是0和1组成的二进制数据,为了让计算机能够处理字符,于是美国人就把他们会用到的每一个字符进行编码(所谓编码,就是为了一个字符编一个二进制数据)
美国人常用的字符有英文字母、标点符号、数字以及一些特殊字符,这些字符一共也不到128个,所以他们用1个字节来存储1字符就够了。 美国人把他们用到的字符和字符对应的编码总结成了一张码表,这张码表叫做ASCII码表(也叫ASCII字符集)。
其实计算机只在美国用是没有问题的,但是计算机慢慢的普及到全世界,当普及到中国的时候,在计算机中想要存储中文,那ASCII字符集就不够用了,因为中文太多了,随便数一数也有几万个字符。
于是中国人为了在计算机中存储中文,也编了一个中国人用的字符集叫做GBK字符集,这里面包含2万多个汉字字符,GBK中一个汉字采用两个字节来存储,为了能够显示英文字母,GBK字符集也兼容了ASCII字符集,在GBK字符集中一个字母还是采用一个字节来存储。
需要我们注意汉字和字母的编码特点:
-
- 如果是存储字母,采用1个字节来存储,一共8位,其中第1位是0
- 如果是存储汉字,采用2个字节来存储,一共16位,其中第1位是1
当读取文件中的字符时,通过识别读取到的第1位是0还是1来判断是字母还是汉字
- 如果读取到第1位是0,就认为是一个字母,此时往后读1个字节。
- 如果读取到第1位是1,就认为是一个汉字,此时往后读2个字节。
Unicode字符集
们国家可以用GBK字符集来表示中国人使用的文字,那世界上还有很多其他的国家,他们也有自己的文字,他们也想要自己国家的文字在计算机中处理,于是其他国家也在搞自己的字符集,就这样全世界搞了上百个字符集,而且各个国家的字符集互不兼容。 这样其实很不利于国际化的交流,可能一个文件在我们国家的电脑上打开好好的,但是在其他国家打开就是乱码了。
为了解决各个国家字符集互不兼容的问题,由国际化标准组织牵头,设计了一套全世界通用的字符集,叫做Unicode字符集。在Unicode字符集中包含了世界上所有国家的文字,一个字符采用4个自己才存储。
在Unicode字符集中,采用一个字符4个字节的编码方案,又造成另一个问题:如果是说英语的国家,他们只需要用到26大小写字母,加上一些标点符号就够了,本身一个字节就可以表示完,用4个字节就有点浪费。
于是又对Unicode字符集中的字符进行了重新编码,一共设计了三种编码方案。分别是UTF-32、UTF-16、UTF-8; 其中比较常用的编码方案是UTF-8
1.UTF-8是一种可变长的编码方案,工分为4个长度区 2.英文字母、数字占1个字节兼容(ASCII编码) 3.汉字字符占3个字节 4.极少数字符占4个字节
字符集小结
ASCII字符集:《美国信息交换标准代码》,包含英文字母、数字、标点符号、控制字符 特点:1个字符占1个字节 GBK字符集:中国人自己的字符集,兼容ASCII字符集,还包含2万多个汉字 特点:1个字母占用1个字节;1个汉字占用2个字节 Unicode字符集:包含世界上所有国家的文字,有三种编码方案,最常用的是UTF-8 UTF-8编码方案:英文字母、数字占1个字节兼容(ASCII编码)、汉字字符占3个字节
编码与解码
在String类类中就提供了相应的方法,可以完成编码和解码的操作
- 编码:把字符串按照指定的字符集转换为字节数组
- 解码:把字节数组按照指定的字符集转换为字符串
/** * 目标:掌握如何使用Java代码完成对字符的编码和解码。 */ public class Test { public static void main(String[] args) throws Exception { // 1、编码 String data = "a我b"; byte[] bytes = data.getBytes(); // 默认是按照平台字符集(UTF-8)进行编码的。 System.out.println(Arrays.toString(bytes)); // 按照指定字符集进行编码。 byte[] bytes1 = data.getBytes("GBK"); System.out.println(Arrays.toString(bytes1)); // 2、解码 String s1 = new String(bytes); // 按照平台默认编码(UTF-8)解码 System.out.println(s1); String s2 = new String(bytes1, "GBK"); System.out.println(s2); } }
IO流(字节流)
由于File只能操作文件,但是不能操作文件中的内容。所以我们要使用IO流的知识
IO流的作用:就是可以对文件或者网络中的数据进行读写的操作。
- 把数据从磁盘,网络中读取到程序中来,用的是输入流
- 把程序中的数据写入磁盘,网络中,用到的是输出流
- 口令:输入流(读数据),输出流(写数据)
IO流分为两大派系
- 字节流:字节流又分为字节输入流,字节输出流
- 字符类:字符流由分为字符输入流,字符输出流
FilelnputStream读取一个字节
字节流中的字节输入流,用InputStream来表示。但是InputStream是抽象类,我们用的是它的子类,叫FilelnputStream。
使用FilelnputStream读取文件中的字节数据
1.创建FilelnputStream文件字节输入流管道,与源文件接通 2.调用read()方法开始读取文件的字节数据 3.调用close()方法释放资源
代码如下
/** * 目标:掌握文件字节输入流,每次读取一个字节。 */ public class FileInputStreamTest1 { public static void main(String[] args) throws Exception { // 1、创建文件字节输入流管道,与源文件接通。 InputStream is = new FileInputStream(("file-io-app\\src\\itheima01.txt")); // 2、开始读取文件的字节数据。 // public int read():每次读取一个字节返回,如果没有数据了,返回-1. int b; // 用于记住读取的字节。 while ((b = is.read()) != -1){ System.out.print((char) b); } //3、流使用完毕之后,必须关闭!释放系统资源! is.close(); } }
问题:
由于一个中文在UTF-8编码方案中是占3个字节,采用一次读取一个字节的方式,读一个字节就相当于读了1/3个汉字,此时将这个字节转换为字符,是会有乱码的。
FilelnputStream读取多个字节
单个字节读取单个太慢,我们可以使用read(byte[] bytes)的重载方法,可以一次性读取多个字节
使用FilelnputStream一次读取多个字节的步骤如下
1.创建FilelnputStream文件字节输入流管道,与源文件接通 2.调用read(byte[] bytes)方法开始读取文件的字节数据 3.调用close()方法释放资源
代码如下:
/** * 目标:掌握使用FileInputStream每次读取多个字节。 */ public class FileInputStreamTest2 { public static void main(String[] args) throws Exception { // 1、创建一个字节输入流对象代表字节输入流管道与源文件接通。 InputStream is = new FileInputStream("file-io-app\\src\\itheima02.txt"); // 2、开始读取文件中的字节数据:每次读取多个字节。 // public int read(byte b[]) throws IOException // 每次读取多个字节到字节数组中去,返回读取的字节数量,读取完毕会返回-1. // 3、使用循环改造。 byte[] buffer = new byte[3]; int len; // 记住每次读取了多少个字节。 abc 66 while ((len = is.read(buffer)) != -1){ // 注意:读取多少,倒出多少。 String rs = new String(buffer, 0 , len); System.out.print(rs); } // 性能得到了明显的提升!! // 这种方案也不能避免读取汉字输出乱码的问题!! is.close(); // 关闭流 } }
- 需要我们注意的是:read(byte[] bytes)它的返回值,表示当前这一次读取的字节个数
假设有个文件
abcde
每次读取过程如下
也就是说,并不是每次读取的时候都把数组装满,比如数组是byte[] bytes = new byte[3]; 第一次调用read(bytes)读取了3个字节(分别是97,98,99),并且往数组中存,此时返回值就是3 第二次调用read(bytes)读取了2个字节(分别是99,100),并且往数组中存,此时返回值是2 第三次调用read(bytes)文件中后面已经没有数据了,此时返回值为-1
注意:采用读取多个字节的方式,也有可能有乱码的,因为也有可能读取到半个汉字
FileInputStream读取全部字节
读取全部字节只需要一次性读取,然后把全部字节转换为一个字符串,就不会有乱码
代码如下
// 1、一次性读取完文件的全部字节到一个字节数组中去。 // 创建一个字节输入流管道与源文件接通 InputStream is = new FileInputStream("file-io-app\\src\\itheima03.txt"); // 2、准备一个字节数组,大小与文件的大小正好一样大。 File f = new File("file-io-app\\src\\itheima03.txt"); long size = f.length(); byte[] buffer = new byte[(int) size]; int len = is.read(buffer); System.out.println(new String(buffer)); //3、关闭流 is.close();
最后,还是要注意一个问题:一次读取所有字节虽然可以解决乱码问题,但是文件不能过大,如果文件过大,可能导致内存溢出。
FileOutputStream写字节
我们学习了使用FileInputStream读取文件中的字节数据,然后就该写入数据
往文件中写数据需要用到OutputStream下面的一个子类FileOutputStream。
步骤如下
- 创建FileOutputStream文件字节输出流管道,与目标文件接通
- 调用Wirte()方法往文件中写数据
- 调用close()方法释放资源
/** * 目标:掌握文件字节输出流FileOutputStream的使用。 */ public class FileOutputStreamTest4 { public static void main(String[] args) throws Exception { // 1、创建一个字节输出流管道与目标文件接通。 // 覆盖管道:覆盖之前的数据 // OutputStream os = // new FileOutputStream("file-io-app/src/itheima04out.txt"); // 追加数据的管道 OutputStream os = new FileOutputStream("file-io-app/src/itheima04out.txt", true); // 2、开始写字节数据出去了 os.write(97); // 97就是一个字节,代表a os.write('b'); // 'b'也是一个字节 // os.write('磊'); // [ooo] 默认只能写出去一个字节 byte[] bytes = "我爱你中国abc".getBytes(); os.write(bytes); os.write(bytes, 0, 15); // 换行符 os.write("\r\n".getBytes()); os.close(); // 关闭流 } }
字节流复制文件
以上是字节输入流和字节输出流,现在我们可以将这两种流配合起来使用,将一个文件复制
比如:我们要复制一张图片,从磁盘D:/resource/meinv.png
的一个位置,复制到C:/data/meinv.png
位置。
思路如下
- 需要创建一个FileInputStream流与源文件接通,创建FileInputStream与目标文件接通
- 然后创建一个数组,使用FileInputStream每次读取一个字节数组的数据,存入数组中
- 然后使用再使用FileOutputStream把字节数组中的有效元素,写入目标数组中
//1.创建一个文件字节输出流与源文件接通 try { InputStream is = new FileInputStream("...."); //2.创建一个文件字节输出流与目标文件接通 OutputStream os = new FileOutputStream("....."); //3.开始定义字节数组转移数据 byte[] buffer = new byte[1024]; //1kb //1024+1024+3 //4.开始转移字节到目标文件 int len;//记录每次读取的字节数 while ((len = is.read(buffer)) != -1) { os.write(buffer, 0, len); } os.close(); is.close(); } catch (Exception e) { e.printStackTrace(); }
我们使用了os.close()可以释放资源,使用try{}catch(Exception e ){}
可以防止由于异常导致释放资源语句无法执行,导致会占用资源
字节流的释放资源方式
- try-catch-finally
- try-with-resource
Finally
最终一定会执行一把除非JVM挂了
try{...}catch{...}finally{ 当代码被干掉的时候,finally也一定会跑 }
IO流(字符流)
字节流可以读取文件中的字节数据,但是如果文件中有中文,中文是由三个字节组成的,使用字节流来读取可能读到半个汉字的情况,这样会导致乱码。
字符流,可以说是专门为读取文本数据而生的
FileReader类
FileReader类,这是字符输入流,用来将文件的字符数据读取到程序中来
FileReader读取文件的步骤如下
第一步:创建FileReader对象与要读取的源文件接通
第二步:调用read()方法读取文件中的字符
第三步:调用close()文件关闭流
先通过构造器创建对象,再通过read方法读取数据
/** * 目标:掌握文件字符输入流。 */ public class FileReaderTest1 { public static void main(String[] args) { try ( // 1、创建一个文件字符输入流管道与源文件接通 Reader fr = new FileReader("io-app2\\src\\itheima01.txt"); ){ // 2、一个字符一个字符的读(性能较差) // int c; // 记住每次读取的字符编号。 // while ((c = fr.read()) != -1){ // System.out.print((char) c); // } // 每次读取一个字符的形式,性能肯定是比较差的。 // 3、每次读取多个字符。(性能是比较不错的!) char[] buffer = new char[3]; int len; // 记住每次读取了多少个字符。 while ((len = fr.read(buffer)) != -1){ // 读取多少倒出多少 System.out.print(new String(buffer, 0, len)); } } catch (Exception e) { e.printStackTrace(); } } }
FileWriter类
FileWriter可以将程序中的字符数据写入文件
第一步:创建FileWiret对象与要读取的目标文件接通
第二步:调用write(字符数据/数据数组/字符串)方法读取文件中的字符
第三步:调用close()方法关闭流
构造器是用来创建FileWriter对象的,有了对象才能调用write方法写数据到文件。
/** * 目标:掌握文件字符输出流:写字符数据出去 */ public class FileWriterTest2 { public static void main(String[] args) { try ( // 0、创建一个文件字符输出流管道与目标文件接通。 // 覆盖管道 // Writer fw = new FileWriter("io-app2/src/itheima02out.txt"); // 追加数据的管道 Writer fw = new FileWriter("io-app2/src/itheima02out.txt", true); ){ // 1、public void write(int c):写一个字符出去 fw.write('a'); fw.write(97); //fw.write('磊'); // 写一个字符出去 fw.write("\r\n"); // 换行 // 2、public void write(String c)写一个字符串出去 fw.write("我爱你中国abc"); fw.write("\r\n"); // 3、public void write(String c ,int pos ,int len):写字符串的一部分出去 fw.write("我爱你中国abc", 0, 5); fw.write("\r\n"); // 4、public void write(char[] buffer):写一个字符数组出去 char[] buffer = {'黑', '马', 'a', 'b', 'c'}; fw.write(buffer); fw.write("\r\n"); // 5、public void write(char[] buffer ,int pos ,int len):写字符数组的一部分出去 fw.write(buffer, 0, 2); fw.write("\r\n"); } catch (Exception e) { e.printStackTrace(); } } }
FileWriter的注意事项
FileWriter写完数据之后,必须刷新或者关闭,写出去的数据才能生效
列如
//1.创建FileWriter对象 Writer fw = new FileWriter("io-app2/src/itheima03out.txt"); //2.写字符数据出去 fw.write('a'); fw.write('b'); fw.write('c');
而下面的代码,加上了flush()方法之后,数据就会立即到目标文件中去。
//1.创建FileWriter对象 Writer fw = new FileWriter("io-app2/src/itheima03out.txt"); //2.写字符数据出去 fw.write('a'); fw.write('b'); fw.write('c'); //3.刷新 fw.flush();
下面的代码,调用了close()方法,数据也会立即到文件中去。因为close()方法在关闭流之前,会将内存中缓存的数据先刷新到文件,再关流。
//1.创建FileWriter对象 Writer fw = new FileWriter("io-app2/src/itheima03out.txt"); //2.写字符数据出去 fw.write('a'); fw.write('b'); fw.write('c'); //3.关闭流 fw.close(); //会先刷新,再关流
IO缓冲流
缓冲流的作用:可以对原始流进行包装,提高原始流读写数据的性能
缓冲字节流
缓冲字节流是如何提高读写数据的性能的呢?
原理是缓冲流底层为自己封装了一个长度为8KB的字节数组,但是缓冲流不能单独使用,必须依赖于原始流。
-
读数据时:它先用原始字节输入流一次性读取8kb的数据存入缓冲流内部的数组,再从8kb的字节数组中读取一个字节或者多个字节
-
写数据时:它是先把数据写到缓冲流内部的8kb的数组中,等数组存满了,再通过原始的字节输出流,一次性写道目标文件中去
在创建缓冲字节流对象时,需要封装一个原始流对象进来。
如果我们用缓冲流复制文件,代码如下
public class BufferedInputStreamTest1 { public static void main(String[] args) { try ( InputStream is = new FileInputStream("io-app2/src/itheima01.txt"); // 1、定义一个字节缓冲输入流包装原始的字节输入流 InputStream bis = new BufferedInputStream(is); OutputStream os = new FileOutputStream("io-app2/src/itheima01_bak.txt"); // 2、定义一个字节缓冲输出流包装原始的字节输出流 OutputStream bos = new BufferedOutputStream(os); ){ 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(); } } }
字符缓冲流
字符缓冲流和字节缓冲流的原理是类似的,它的底层也会有一个8kb的数组,但是这里是字符数组,字符缓冲流也不能单独使用,它需要依赖于原始字符流一起使用。
-
BufferedReader读取数据时:
它先原始字符输入流 一次性读取8KB的数据存入缓冲流内部的数组中,再从8KB的字符数组中读取一个字符或者多个字符
创建BufferedReader对象,需要用到BufferedReader的构造方法,内部需要封装一个原始的字符输入流,我们也可以传入FileReader。
BufferedReader还有特有的方法,一次可以读取文本文件的一行
代码如下
public class BufferedReaderTest2 { public static void main(String[] args) { try ( Reader fr = new FileReader("io-app2\\src\\itheima04.txt"); // 创建一个字符缓冲输入流包装原始的字符输入流 BufferedReader br = new BufferedReader(fr); ){ // char[] buffer = new char[3]; // int len; // while ((len = br.read(buffer)) != -1){ // System.out.print(new String(buffer, 0, len)); // } // System.out.println(br.readLine()); // System.out.println(br.readLine()); // System.out.println(br.readLine()); // System.out.println(br.readLine()); String line; // 记住每次读取的一行数据 while ((line = br.readLine()) != null){ System.out.println(line); } } catch (Exception e) { e.printStackTrace(); } } }
-
BufferedWriter写数据时
它是先把数据写到字符缓冲流内部的8kb的数组中,等数组存满了,再通过原始的字符输出流,一次性写到目标文件中去
创建BufferedWriter对象时需要用到BufferedWriter的构造方法,而且内部需要封装一个原始的字符输出流,我们这里可以传递FileWriter
而且BufferedWriter新增了一个功能,可以用来写一个换行符号
使用BufferedWriter往文件中写入字符数据,代码如下
public class BufferedWriterTest3 { public static void main(String[] args) { try ( Writer fw = new FileWriter("io-app2/src/itheima05out.txt", true); // 创建一个字符缓冲输出流管道包装原始的字符输出流 BufferedWriter bw = new BufferedWriter(fw); ){ bw.write('a'); bw.write(97); bw.write('磊'); bw.newLine(); bw.write("我爱你中国abc"); bw.newLine(); } catch (Exception e) { e.printStackTrace(); } } }
缓冲流性能分析
虽然缓冲流内部多了一个数组,可以提高原始流的读写性能。但是我们使用原始流,自己加一个8kb的数组不是一样吗,缓冲流就一定能提高性能吗?
缓冲流不一定能提高性能
接下来我们使用四种方式进行文件复制,并记录文件复制时间
- 使用低级流一个字节一个字节的复制
- 使用低级流按照字节数组的形式复制
- 使用缓冲流一个字节一个字节的复制
- 使用缓冲流按照字节数组的形式复制
- 低级流一个字节一个字节复制:慢到无法忍受
- 低级流按照字节数组复制:12.117s
- 缓冲流一个字节复制:11.058s
- 缓冲流按照字节数组复制:2.163s
得出结论:默认情况下,采用一次复制1024字节,缓冲流完胜
但是缓冲流就一定性能高吗,我们采用一次复制8192个字节
低级流按照字节数组复制:2.535s
缓冲流按照字节数组复制:2.088
经过测试,我们可以得知:一次读取8192个字节时,低级流和缓冲流性能相当
当你继续加大的时候,缓冲流的性能不一定比低级流高,其实低级流自己加一个数组,性能不是很差,只不过缓冲流帮你加了一个大小比较合理的数组
转换流
InputStreamReader类
由于FileReader默认只能读取UTF-8编码格式的文件,如果使用FileReader读取BGK格式的文件,可能存在乱码,因为FileReader读取GBK格式的文件,可能存在乱码,因为FileReader它遇到汉字默认是按照3个字节来读取的,而GBK格式的文件一个汉字是占了2个字节,这样就会导致乱码
Java为我们提供了另外两种流,InputStreamReader,OutputStreamWriter,这两个流我们把它叫做转换流,他们可以将字节流转换为字符流,并且可以指定编码方案
同样,InputStreamReader也不是能单独使用的,它内部需要封装InputStream的子类的对象,再指定一个编码表,如果不指定编码表,默认会按照UTF-8形式进行转换
public class InputStreamReaderTest2 { public static void main(String[] args) { try ( // 1、得到文件的原始字节流(GBK的字节流形式) InputStream is = new FileInputStream("io-app2/src/itheima06.txt"); // 2、把原始的字节输入流按照指定的字符集编码转换成字符输入流 Reader isr = new InputStreamReader(is, "GBK"); // 3、把字符输入流包装成缓冲字符输入流 BufferedReader br = new BufferedReader(isr); ){ String line; while ((line = br.readLine()) != null){ System.out.println(line); } } catch (Exception e) { e.printStackTrace(); } } }
OutputStreamWriter类
OutputStream表示字节输入流,后面是Writer表示字符输入流,合在一起意思就是表示可以把OutputStream转换为Writer,最终OutputStreamWriter其实也是Writer的子类,所有也算是字符输出流。
OutputStreamReader也是不能单独使用的,它内部需要封装一个OutputStream的子类对象,再指定一个编码表,如果不不指定编码表,默认会按照UTF-8形式进行转换
public class OutputStreamWriterTest3 { public static void main(String[] args) { // 指定写出去的字符编码。 try ( // 1、创建一个文件字节输出流 OutputStream os = new FileOutputStream("io-app2/src/itheima07out.txt"); // 2、把原始的字节输出流,按照指定的字符集编码转换成字符输出转换流。 Writer osw = new OutputStreamWriter(os, "GBK"); // 3、把字符输出流包装成缓冲字符输出流 BufferedWriter bw = new BufferedWriter(osw); ){ bw.write("我是中国人abc"); bw.write("我爱你中国123"); } catch (Exception e) { e.printStackTrace(); } } }
打印流
PrintStream/PrintWriter
打印流,实际上也就是写数据的意思,它和普通的write方法写数据还不太一样,一般会使用打印流特有的方法叫做print(数据)或者printlen(数据)
打印流有两个,一个是字节打印流PrintStream,一个是字符打印流PrintWriter
PrintStream和PrintWriter的用法是一样的
public class PrintTest1 { public static void main(String[] args) { try ( // 1、创建一个打印流管道 // PrintStream ps = // new PrintStream("io-app2/src/itheima08.txt", Charset.forName("GBK")); // PrintStream ps = // new PrintStream("io-app2/src/itheima08.txt"); PrintWriter ps = new PrintWriter(new FileOutputStream("io-app2/src/itheima08.txt", true)); ){ ps.print(97); //文件中显示的就是:97 ps.print('a'); //文件中显示的就是:a ps.println("我爱你中国abc"); //文件中显示的就是:我爱你中国abc ps.println(true);//文件中显示的就是:true ps.println(99.5);//文件中显示的就是99.5 ps.write(97); //文件中显示a,发现和前面println方法的区别了吗? } catch (Exception e) { e.printStackTrace(); } } }
重定向输出语句
System.out.println();//打印输出
这句话表示打印输出,但是为什么能够输出呢。
因为System里面有一个静态变量叫做out,out的数据类型就是PrintStream,它是一个打印流,而且这个打印流的默认输出目的地是控制台
并且System还提供了一个方法,可以修改底层的打印流,这样我们就可以重定向打印语句的输出目的地。
public class PrintTest2 { public static void main(String[] args) { System.out.println("老骥伏枥"); System.out.println("志在千里"); try ( PrintStream ps = new PrintStream("io-app2/src/itheima09.txt"); ){ // 把系统默认的打印流对象改成自己设置的打印流 System.setOut(ps); System.out.println("烈士暮年"); System.out.println("壮心不已"); } catch (Exception e) { e.printStackTrace(); } } }
以上代码中,使用println打印语句,不会在控制台打印,而是会打印在文件中。
数据流
当我们想把数据和数据的类型一并写到文件中去,读取的时候也将数据和数据类型一并读出来,这就可以用到数据流,有两个DataInputStream和DataOutputStream.
DataOutputStream类
它是一种包装流,创建DataOutputStream对象时,底层需要依赖一个原始的OutputStream流对象,然后调用它的wirteXxx方法,写的是特定类型的数据
列如:往文件中写整数,小数,布尔类型数据,字符串数据
public class DataOutputStreamTest1 { public static void main(String[] args) { try ( // 1、创建一个数据输出流包装低级的字节输出流 DataOutputStream dos = new DataOutputStream(new FileOutputStream("io-app2/src/itheima10out.txt")); ){ dos.writeInt(97); dos.writeDouble(99.5); dos.writeBoolean(true); dos.writeUTF("程序员666!"); } catch (Exception e) { e.printStackTrace(); } } }
DataInputStream类
它也是一种包装类,创建DataInputStream对象时。底层需要依赖于一个原始的InputStream流对象,然后调用它的readXxx()方法就可以读取特定类型的数据
列如:读取文件中的特定类型的数据(整数,小数,字符串等)
代码如下
public class DataInputStreamTest2 { public static void main(String[] args) { try ( DataInputStream dis = new DataInputStream(new FileInputStream("io-app2/src/itheima10out.txt")); ){ int i = dis.readInt(); System.out.println(i); double d = dis.readDouble(); System.out.println(d); boolean b = dis.readBoolean(); System.out.println(b); String rs = dis.readUTF(); System.out.println(rs); } catch (Exception e) { e.printStackTrace(); } } }
序列化流
众所周知,字节流是以字节为单位来读写数据,字符流是按照字符为单位来读写数据。对象流是按照对象为单位来读写数据,也就是把对象当作一个整体,可以写一个对象到文件,也可以从文件中把对象读取出来
所以所谓序列化也就是说,把对象写到文件或者网络中去
而反序列化,把对象从文件或者网路中读取出来
ObjectOutputStream类
这是一个包装流,不能单独使用,需要结合原始的字节输出流来使用
代码如下:将一个User对象写到文件中去
- 第一步:先准备一个User类,必须让其实现Serializable接口。
// 注意:对象如果需要序列化,必须实现序列化接口。 public class User implements Serializable { private String loginName; private String userName; private int age; // transient 这个成员变量将不参与序列化。 private transient String passWord; public User() { } public User(String loginName, String userName, int age, String passWord) { this.loginName = loginName; this.userName = userName; this.age = age; this.passWord = passWord; } @Override public String toString() { return "User{" + "loginName='" + loginName + '\'' + ", userName='" + userName + '\'' + ", age=" + age + ", passWord='" + passWord + '\'' + '}'; } }
- 第二步:再创建ObjectOutputStream流对象,调用writeObject方法对象到文件
public class Test1ObjectOutputStream { public static void main(String[] args) { try ( // 2、创建一个对象字节输出流包装原始的字节 输出流。 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("io-app2/src/itheima11out.txt")); ){ // 1、创建一个Java对象。 User u = new User("admin", "张三", 32, "666888xyz"); // 3、序列化对象到文件中去 oos.writeObject(u); System.out.println("序列化对象成功!!"); } catch (Exception e) { e.printStackTrace(); } } }
如果你此时使用记事本打开,会发现乱码,因为对象本身不是文本数据,打开就是乱码
ObjectInputStream类
这也是一个包装类,不能单独使用,需要结合原始的字节输入流进行使用
public class Test2ObjectInputStream { public static void main(String[] args) { try ( // 1、创建一个对象字节输入流管道,包装 低级的字节输入流与源文件接通 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("io-app2/src/itheima11out.txt")); ){ User u = (User) ois.readObject(); System.out.println(u); } catch (Exception e) { e.printStackTrace(); } } }
IO框架
IO框架可以简化对IO操作
由apache开源基金会提供了一组有关IO流小框架,可以提高IO流的开发效率
- commons-io
框架本质是别人写好的一些字节码文件(class文件),打包成了一个jar包,我们只需要把jar包引入我们的项目中,就可以直接使用了
注意的是在使用框架时,需要先引入jar包
- 在模块的目录下,新建一个lib文件夹
- 把jar包复制粘贴到lib文件夹下
- 选择lib下的jar包,右键点击Add As Library,然后就可以用了
代码如下
public class CommonsIOTest1 { public static void main(String[] args) throws Exception { //1.复制文件 FileUtils.copyFile(new File("io-app2\\src\\itheima01.txt"), new File("io-app2/src/a.txt")); //2.复制文件夹 FileUtils.copyDirectory(new File("D:\\resource\\私人珍藏"), new File("D:\\resource\\私人珍藏3")); //3.删除文件夹 FileUtils.deleteDirectory(new File("D:\\resource\\私人珍藏3")); // Java提供的原生的一行代码搞定很多事情 Files.copy(Path.of("io-app2\\src\\itheima01.txt"), Path.of("io-app2\\src\\b.txt")); System.out.println(Files.readString(Path.of("io-app2\\src\\itheima01.txt"))); } }
本文作者:奕帆卷卷
本文链接:https://www.cnblogs.com/yifan0820/p/17838732.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步