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. 如果是存储字母,采用1个字节来存储,一共8位,其中第1位是0
    2. 如果是存储汉字,采用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流分为两大派系

  1. 字节流:字节流又分为字节输入流,字节输出流
  2. 字符类:字符流由分为字符输入流,字符输出流

image-20231117103411962

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。

步骤如下

  1. 创建FileOutputStream文件字节输出流管道,与目标文件接通
  2. 调用Wirte()方法往文件中写数据
  3. 调用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位置。

思路如下

  1. 需要创建一个FileInputStream流与源文件接通,创建FileInputStream与目标文件接通
  2. 然后创建一个数组,使用FileInputStream每次读取一个字节数组的数据,存入数组中
  3. 然后使用再使用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的数组不是一样吗,缓冲流就一定能提高性能吗?

缓冲流不一定能提高性能

接下来我们使用四种方式进行文件复制,并记录文件复制时间

  1. 使用低级流一个字节一个字节的复制
  2. 使用低级流按照字节数组的形式复制
  3. 使用缓冲流一个字节一个字节的复制
  4. 使用缓冲流按照字节数组的形式复制
  • 低级流一个字节一个字节复制:慢到无法忍受
  • 低级流按照字节数组复制: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包

  1. 在模块的目录下,新建一个lib文件夹
  2. 把jar包复制粘贴到lib文件夹下
  3. 选择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 中国大陆许可协议进行许可。

posted @   奕帆卷卷  阅读(15)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起