Kbaor_2023_9_21_Java_I/O流
Kbaor_2023_9_21_Java_I/O流
1. File类
1.1 File和IO的概述
问题:
1.如果程序只用变量、数组、集合这样的容器存储数据,有什么问题?
2.对文件进行读写的前提条件是什么?
3.IO和File分别是用来干什么的?
- IO流是什么?
- 可以将数据从本地文件中读取出来
- 可以将数据从内存保存到本地文件
- File类是什么?
- 在读写数据的时候告诉虚拟机要操作的(文件/文件夹)在哪
- 对(文件/文件夹)本身进行操作。包括创建、删除等
1.2 File类概述和构造方法
问题:
1.File封装的一个文件对象,必须是磁盘中真实存在的吗?
2.为什么要把字符串表示形式的路径变成File对象?
3.File的构造方法中的参数,是为了提供什么信息?
-
File类介绍
- 它是文件和目录路径名的抽象表示
- 文件和目录是可以通过File封装成对象的
- 对于File而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已.它可以是存在的,也可以是不存在的.将来是要通过具体的操作把这个路径的内容转换为具体存在的
-
File类的构造方法
方法名 说明 File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例 File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例 File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例 -
示例代码
public class FileDemo01 { public static void main(String[] args) { File f1 = new File("E:\\itcast\\java.txt"); System.out.println(f1); File f2 = new File("E:\\itcast","java.txt"); System.out.println(f2); File f3 = new File("E:\\itcast"); File f4 = new File(f3,"java.txt"); System.out.println(f4); } }
1.3 绝对路径和相对路径
问题:
1.绝对路径是从什么地方开始的,为什么叫绝对?
是一个完整的路径,从盘符开始
2.相对路径是相对于什么位置而言的?
是一个简化的路径,相对当前项目下的路径
3.new File("myFile\\file.txt")表示的文件是在项目下还是模块下?
-
绝对路径
- 是一个完整的路径,从盘符开始
-
相对路径
- 是一个简化的路径,相对当前项目下的路径
-
示例代码
public class FileDemo02 { public static void main(String[] args) { // 是一个完整的路径,从盘符开始 File file1 = new File("D:\\itheima\\a.txt"); // 是一个简化的路径,从当前项目根目录开始 File file2 = new File("a.txt"); File file3 = new File("模块名\\a.txt"); } }
1.4 File类创建功能
问题:
1.用createNewFile方法创建文件时,若文件已存在会返回什么结果?
2.使用mkdir创建文件夹时,需要注意哪些问题?
3.mkdir不能创建多级文件夹,那么mkdirs能否创建单级文件夹?
-
方法分类
方法名 说明 public boolean createNewFile() 当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件 public boolean mkdir() 创建由此抽象路径名命名的目录 public boolean mkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录 -
示例代码
public class FileDemo02 { public static void main(String[] args) throws IOException { //需求1:我要在E:\\itcast目录下创建一个文件java.txt File f1 = new File("E:\\itcast\\java.txt"); System.out.println(f1.createNewFile()); System.out.println("--------"); //需求2:我要在E:\\itcast目录下创建一个目录JavaSE File f2 = new File("E:\\itcast\\JavaSE"); System.out.println(f2.mkdir()); System.out.println("--------"); //需求3:我要在E:\\itcast目录下创建一个多级目录JavaWEB\\HTML File f3 = new File("E:\\itcast\\JavaWEB\\HTML"); //System.out.println(f3.mkdir()); System.out.println(f3.mkdirs()); System.out.println("--------"); //需求4:我要在E:\\itcast目录下创建一个文件javase.txt File f4 = new File("E:\\itcast\\javase.txt"); //System.out.println(f4.mkdir()); System.out.println(f4.createNewFile()); } }
1.5 File类删除功能
问题:
1.使用File的delete方法删除文件,是否还能通过回收站找回?
2.使用File的delete方法删除文件夹时,需要注意什么?
-
方法分类
方法名 说明 public boolean delete() 删除由此抽象路径名表示的文件或目录 -
示例代码
public class FileDemo03 { public static void main(String[] args) throws IOException { //File f1 = new File("E:\\itcast\\java.txt"); //需求1:在当前模块目录下创建java.txt文件 File f1 = new File("myFile\\java.txt"); //System.out.println(f1.createNewFile()); //需求2:删除当前模块目录下的java.txt文件 System.out.println(f1.delete()); System.out.println("--------"); //需求3:在当前模块目录下创建itcast目录 File f2 = new File("myFile\\itcast"); //System.out.println(f2.mkdir()); //需求4:删除当前模块目录下的itcast目录 System.out.println(f2.delete()); System.out.println("--------"); //需求5:在当前模块下创建一个目录itcast,然后在该目录下创建一个文件java.txt File f3 = new File("myFile\\itcast"); //System.out.println(f3.mkdir()); File f4 = new File("myFile\\itcast\\java.txt"); //System.out.println(f4.createNewFile()); //需求6:删除当前模块下的目录itcast System.out.println(f4.delete()); System.out.println(f3.delete()); } }
1.6 File类判断和获取功能
问题:
1.判断文件类型的方法名都是以什么开头?
2.a.txt文件对象调用getName()获取文件名的结果是什么?
-
判断功能
方法名 说明 public boolean isDirectory() 测试此抽象路径名表示的File是否为目录 public boolean isFile() 测试此抽象路径名表示的File是否为文件 public boolean exists() 测试此抽象路径名表示的File是否存在 -
获取功能
方法名 说明 public String getName() 返回由此抽象路径名表示的文件或目录的名称 -
示例代码
public class FileDemo04 { public static void main(String[] args) { //创建一个File对象 File f = new File("myFile\\java.txt"); //public boolean isDirectory():测试此抽象路径名表示的File是否为目录 //public boolean isFile():测试此抽象路径名表示的File是否为文件 //public boolean exists():测试此抽象路径名表示的File是否存在 System.out.println(f.isDirectory()); System.out.println(f.isFile()); System.out.println(f.exists()); //public String getAbsolutePath():返回此抽象路径名的绝对路径名字符串 //public String getPath():将此抽象路径名转换为路径名字符串 //public String getName():返回由此抽象路径名表示的文件或目录的名称 System.out.println(f.getAbsolutePath()); System.out.println(f.getPath()); System.out.println(f.getName()); System.out.println("--------"); //public File[] listFiles():返回此抽象路径名表示的目录中的文件和目录的File对象数组 File f2 = new File("E:\\itcast"); File[] fileArray = f2.listFiles(); for(File file : fileArray) { //System.out.println(file); //System.out.println(file.getName()); if(file.isFile()) { System.out.println(file.getName()); } } } }
1.7 File的listFile方法
问题:
1.调用listFiles方法返回的文件数组中,包含哪些类型的文件?
2.当调用listFiles方法的文件对象是一个文件时,需要注意什么?
3.如果文件夹对象需要有权限才能访问,调用listFile方法会返回什么?
| 方法名 | 说明 |
|---|---|
| public File[] listFiles() | 返回此抽象路径名表示的目录中的文件和目录的File对象数组 |
| public String getPath() | 将此抽象路径名转换为路径名字符串 |
| public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
- 注意事项:
- 如果调用listFiles方法的是一个不存在的文件夹对象,返回为null
- 如果调用listFiles方法的是一个文件对象,返回为null
- 如果调用listFiles犯法的是一个空文件夹,返回的是一个数组,只不过数组长度为0
1.8 File练习一
-
案例需求
- 在当前模块下的aaa文件夹中创建一个a.txt文件
- 注意:
- 模块路径不要搞错了
- 创建文件之前,文件夹必须存在
-
实现步骤
- 创建File对象,指向aaa文件夹
- 判断aaa文件夹是否存在,如果不存在则创建
- 创建File对象,指向aaa文件夹下的a.txt文件
- 创建这个文件
-
代码实现
//1.创建对应的文件夹aaa File file1 = new File("test-file\\aaa"); //2.判断该文件夹是否存在 if (!file1.exists()) { //3.如果不存在,就创建这个文件夹,保证文件夹必须存在 file1.mkdirs(); } //4.执行到这一步,文件夹肯定存在,所以开始创建文件 //可以使用第三个构造,使用父文件夹和文件夹拼接得到新的文件对象 File file2 = new File(file1, "a.txt"); //5.创建文件 file2.createNewFile();
1.9 File练习二
-
案例需求
- 删除一个多级文件夹
-
实现步骤
- 定义一个方法,接收一个File对象
- 遍历这个File对象,获取它下边的每个文件和文件夹对象
- 判断当前遍历到的File对象是文件还是文件夹
- 如果是文件,直接删除
- 如果是文件夹,递归调用自己,将当前遍历到的File对象当做参数传递
- 参数传递过来的文件夹File对象已经处理完成,最后直接删除这个空文件夹
-
代码实现
public class Test {
public static void main(String[] args) {
//8.创建一个对应的文件夹对象,验证方法
File file = new File("D:\\itcast");
delDir(file);
}
//1.定义一个用于删除文件夹的方法,形式参数就是File对象
public static void delDir(File file) {
//2.根据传入的File对象,获取该文件夹里面所有的文件和文件夹
File[] files = file.listFiles();
//3.遍历文件数组
for (File f : files) {
//4.遍历出来的内容,有可能是文件夹,也有可能是文件
//判断是否是文件
if (f.isFile()) {
//5.如果是文件,就直接删除
f.delete();
} else {
//6.不是文件,就是文件夹,所以递归调用当前方法
//把遍历出来的文件夹对象传入
delDir(f);
}
}
//7.删除完传入的文件夹里面的所有内容后,就把当前这个文件夹干掉
file.delete();
}
}
1.10 File练习三
-
案例需求
统计一个文件夹中每种文件的个数并打印
打印格式如下:
txt:3个 doc:4个 jpg:6个 … -
实现步骤
- 定义一个方法,参数是HashMap集合用来统计次数和File对象要统计的文件夹
- 遍历File对象,获取它下边的每一个文件和文件夹对象
- 判断当前File对象是文件还是文件夹
- 如果是文件,判断这种类型文件后缀名在HashMap集合中是否出现过
- 没出现过,将这种类型文件的后缀名存入集合中,次数存1
- 出现过,获取这种类型文件的后缀名出现的次数,对其+1,在存回集合中
- 如果是文件夹,递归调用自己,HashMap集合就是参数集合,File对象是当前文件夹对象
-
代码实现
public class Test {
public static void main(String[] args) {
//1.一个文件中,有文件也有文件夹,这里面肯定涉及到递归,所以需要定义一个方法解决
//预期这个方法可以统计里面的每种文件以及对应的次数,这是一个对应关系,如果要存储,要用map集合
//这个map集合应该是应该在方法外创建还是在方法内?
//因为我们最终需要的只有1个集合存储所有的结果,如果在方法里创建,递归调用了几次就有几个集合
//处理很麻烦,所以放到方法外定义,保证所有的递归操作的集合都是同一个
//2.创建File对象,以及Map集合
HashMap<String, Integer> hm = new HashMap<>();
File file = new File("test-file");
getCount(hm, file);
System.out.println(hm);
}
public static void getCount(HashMap<String, Integer> hm, File file) {
//3.遍历file对象,获取这里面所有内容
File[] files = file.listFiles();
//4.遍历上一步遍历出来的File数组
for (File f : files) {
//5.遍历出来的File对象有可能是文件,也有可能是文件夹
//6.判断是否是文件,如果是文件,开始记录
if (f.isFile()) {
//6.1 获取文件名字
String name = f.getName();
//6.2 文件名是带后缀的名字,需要把.后面的内容给拿出来,使用字符串的split方法
//根据.切割,但是.这个符号在正则表达是中具有特殊作用,代表任意字符
//如果想要表达这个.符号本身,就要加转义字符,所以提供的字符串是\\.
String[] strs = name.split("\\.");
//6.3 根据索引获取后缀名
String suffix = strs[strs.length - 1];
//6.4 判断这个集合中是否包含这个后缀名
if (hm.containsKey(suffix)) {
//6.5 如果包含,代表已经有了,获取存储的次数,重新设置,次数加1
Integer count = hm.get(suffix);
//注意事项:此处只能是使用++count或者count+1
//hm.put(suffix, count++);
hm.put(suffix, count+1);
} else {
//6.6 如果不包含,存储的时候,次数是1
hm.put(suffix, 1);
}
} else {
//7.如果是文件夹,当前方法就是统计文件夹中的结果,所以递归调用
//注意,传入的File对象是遍历出来的,不要传错了
getCount(hm, f);
}
}
}
}
2. 字节流
2.1 IO流概述
问题:
1.学习IO流的目的有哪几个,分别是什么?
2.在数据传输的过程中,是谁在读,是谁在写?
-
不适用IO流存储数据的弊端:
- 不能永久化存储,只要代码运行结束,所有数据都会丢失
-
学习IO流的目的:
- 将数据写到文件中,实现数据的永久话存储
- 读取文件中已经存在的数据
-
IO流介绍:
- IO:输入/输出(Input/Output)
- 流:是一种抽象概念,是对数据传输的总称,也就是说数据在设备间的传输称为流,流的本质是数据传输
- IO流就是用来处理设备间数据传输问题的。常见的应用:文件复制;文件上传;文件下载
-
IO数据传输的参照物:内存。内存在读,内存在写
2.2 IO的分类
问题:
1.IO流可以按照哪些方式划分,分别有哪些?
2.字符流一般用来操作什么类型的文件?
3.如何判断一个文件是否是纯文本文件?
- IO流的分类
- 按照数据的流向
- 输入流:读数据
- 输出流:写数据
- 按照数据类型来分
- 字节流
- 字节输入流
- 字节输出流
- 字符流
- 字符输入流
- 字符输出流
- 字节流
- 按照数据的流向
- 判断是否是纯文本文件
- 使用记事本打开,能够看到读的懂内容,就是纯文本文件
- 纯文本文件可以使用字符流
- IO流的使用场景
- 如果操作的是纯文本文件,优先使用字符流
- 如果操作的是图片、视频、音频等二进制文件,优先使用字节流
- 如果不确定文件类型,优先使用字节流.字节流是万能的流
2.3 字节流输出流快速入门
问题:
1.字节流写数据的步骤分为哪几步?
2.创建文件输出流对象时一般,使用哪种构造器?
-
使用字节输出流写数据的步骤
- 创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
- 调用字节输出流对象的写数据方法
- 释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
-
字节流抽象基类
- InputStream:这个抽象类是表示字节输入流的所有类的超类
- OutputStream:这个抽象类是表示字节输出流的所有类的超类
- 子类名特点:子类名称都是以其父类名作为子类名的后缀
-
字节输出流
- FileOutputStream(String name):创建文件输出流以指定的名称写入文件
-
示例代码
public class FileOutputStreamDemo01 { public static void main(String[] args) throws IOException { //创建字节输出流对象 /* 注意点: 1.如果文件不存在,会帮我们创建 2.如果文件存在,会把文件清空 */ //FileOutputStream(String name):创建文件输出流以指定的名称写入文件 FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt"); //void write(int b):将指定的字节写入此文件输出流 fos.write(97); //fos.write(57); //fos.write(55); //最后都要释放资源 //void close():关闭此文件输出流并释放与此流相关联的任何系统资源。 fos.close(); } }
2.4 注意事项
问题:
1.如果文件输出流对应写入的文件不存在,会出现什么情况?
2.如果文件输出流对应写入的文件中有内容,往里面写内容会怎样?
3.使用文件输出流的写入方法,传入一个数字,会把数字写到文件中吗?
4.最后一步释放资源有什么作用?
- 注意事项:
- 如果文件不存在,会帮我们自动创建出来
- 如果文件存在,会把文件清空
- 传递一个整数时,那么实际写到文件中的,是这个整数在码表中对应的那个字符
- 释放资源后,会告诉操作系统,现在已经不需要再用这个文件了
2.5 一次写多个数据
-
字节流写数据的三种方式
方法名 说明 void write(int b) 将指定的字节写入此文件输出流 一次写一个字节数据 void write(byte[] b) 将 b.length字节从指定的字节数组写入此文件输出流 一次写一个字节数组数据 void write(byte[] b, int off, int len) 将 len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流 一次写一个字节数组的部分数据 -
示例代码
public class FileOutputStreamDemo02 { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt"); //FileOutputStream fos = new FileOutputStream(new File("myByteStream\\fos.txt")); //fos.write(97); //fos.write(98); //fos.write(99); //fos.write(100); //fos.write(101); //byte[] bys = {97, 98, 99, 100, 101}; //字符串方法:byte[] getBytes():返回字符串对应的字节数组 byte[] bys = "abcde".getBytes(); //fos.write(bys); //fos.write(bys,0,bys.length); fos.write(bys,1,3); //释放资源 fos.close(); } }
2.6 两个问题
问题:
1.字节流写数据如何实现换行?
2.字节流写数据如何实现追加写入呢?
-
字节流写数据如何实现换行
- windows:\r\n
- linux:\n
- mac:\r
-
字节流写数据如何实现追加写入
- public FileOutputStream(String name,boolean append)
- 创建文件输出流以指定的名称写入文件。如果第二个参数为true ,则字节将写入文件的末尾而不是开头
-
示例代码
public class FileOutputStreamDemo03 { public static void main(String[] args) throws IOException { //创建字节输出流对象 //FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt"); FileOutputStream fos = new FileOutputStream("myByteStream\\fos.txt",true); //写数据 for (int i = 0; i < 10; i++) { fos.write("hello".getBytes()); fos.write("\r\n".getBytes()); } //释放资源 fos.close(); } }
2.7 try...catch捕获异常
问题:
1.关闭流的操作为什么不推荐写在try的代码块中?
2.异常处理的标准格式由哪几个部分组成?
3.在finally代码块中直接就关闭资源会不会存在问题?
-
异常处理格式
-
try-catch-finally
try{ 可能出现异常的代码; }catch(异常类名 变量名){ 异常的处理代码; }finally{ 执行所有清除操作; } -
finally特点
- 被finally控制的语句一定会执行,除非JVM退出
-
-
示例代码
FileOutputStream fos=null; try { //1.创建输出流对象,此处用的是FileOutputStream fos = new FileOutputStream("test-file\\aaa\\a.txt", true); //2.使用输出流写数据 //定义一个字符串 String s = "你好,李四"; //字符串是可以转换成字节数组 byte[] bys = s.getBytes(); //把字符串转成的字节数组交给这个写入方法,试试效果 fos.write(bys); } catch (IOException e) { e.printStackTrace(); } finally { //3.释放资源 //在创建流对象之前有可能也有异常,导致对象没有创建,所以要做非空判断 if (fos != null) { fos.close(); } }
//在高版本的jdk中,已经可以自动释放资源,但是需要把流的创建放到try语句中
try
(//1.创建输出流对象,此处用的是FileOutputStream
FileOutputStream fos = new FileOutputStream("test-file\\aaa\\a.txt", true);
)
{
//2.使用输出流写数据
//定义一个字符串
String s = "你好,李四";
//字符串是可以转换成字节数组
byte[] bys = s.getBytes();
//把字符串转成的字节数组交给这个写入方法,试试效果
fos.write(bys);
} catch (IOException e) {
e.printStackTrace();
}
2.8 小结
- 创建字节输出流对象
- 文件不存在,就创建
- 文件存在就清空,如果不想被清空则加true
- 写数据
- 可以写一个字节,一个字节数组,一个字节数组的一部分
- 写回车换行:\r\n
- 释放资源
2.9 字节输入流基本学习
问题:
1.字节输入流读取数据分为哪几步?
2.如果字节输入流对应的文件不存在会怎样?
3.使用字节输入流操作的时候有哪些注意事项?
-
字节输入流
- FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名
-
字节输入流读取数据的步骤
- 创建字节输入流对象
- 调用字节输入流对象的读数据方法
- 释放资源
-
示例代码
//1.创建输入流对象 FileInputStream fis = new FileInputStream("test-file\\aaa\\a.txt"); //2.读取内容,使用read int b = fis.read(); System.out.println(b); System.out.println((char)b); //3.释放资源 fis.close();
2.10 读多个字节
- 创建字节输入流对象
- 定义一个int类型的整数变量接收输入流读取的字节数据
- 使用while循环读取数据,每次读取将数据存入变量
- 当接收的数据为-1时就结束循环
- 释放资源
//1.创建输入流对象
FileInputStream fis = new FileInputStream("test-file\\aaa\\a.txt");
//3.想要读取多个字节,这是重复操作,而且不知道读取的次数,使用while循环
//什么时候读取结束?读到的字节数是-1的时候就结束
int b;
//2.读取内容,使用read
while ((b = fis.read()) != -1) {
System.out.print((char)b);
}
//3.释放资源
fis.close();
2.11 案例:文件复制
-
案例需求
- 把“C:\\itheima\\a.avi” 复制到当前模块下
-
实现步骤
- 复制文本文件,其实就把文本文件的内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)
- 数据源:
- C:\\itheima\\a.avi --- 读数据 --- InputStream --- FileInputStream
- 目的地:
- 当前模块\目标文件 --- 写数据 --- OutputStream --- FileOutputStream
-
代码实现
//1.创建输入、输出流
FileInputStream fis = new FileInputStream("D:\\itcast\\泷泽萝拉.avi");
FileOutputStream fos = new FileOutputStream("test-file\\aaa\\泷泽老师.avi");
//2.使用输入流开始读取数据
int b;
while ((b = fis.read()) != -1) {
//3.使用输出流将读取的数据写出到文件中
fos.write(b);
//4.释放资源
fis.close();
fos.close();
2.12 定义小数组拷贝
问题:
1.使用读取字节数组的方式读取有什么好处?
2.读取字节数组的方法返回值代表什么?
-
一次读一个字节数组的方法
- public int read(byte[] b):从输入流读取最多b.length个字节的数据
- 返回的是读入缓冲区的总字节数,也就是实际的读取字节个数
-
示例代码
//11.创建字节输入、输出流对象 FileInputStream fis = new FileInputStream("D:\\itcast\\泷泽萝拉.avi"); FileOutputStream fos = new FileOutputStream("test-file\\aaa\\泷泽老师.avi"); //2.定义字节数组,接收每次读取的字节数据,放入数组中 byte[] bys = new byte[1024]; //1024及其整数倍 //3.文件的字节总数不一定是1024的整数倍,所以最后一次很有可能装不满 //所以定义一个变量,接收每次读取的字节个数 int len; //4.循环读取 while ((len=fis.read(bys))!=-1) { //5.每次read方法执行结束,字节数组中就是新读取的字节数据 //写入的时候,写入的字节数量就是得到的字节个数 //防止最后一次的时候,如果没有读取1024个,会把默认的、不属于文件的0这个数据写进去 fos.write(bys,0,len); } //6.释放资源 fis.close(); fos.close();
2.13 小数组拷贝原理

2.14 课后练习:文件复制
-
案例需求
把“E:\itcast\mn.jpg”复制到模块目录下的“mn.jpg” (文件可以是任意文件去)
-
实现步骤
- 根据数据源创建字节输入流对象
- 根据目的地创建字节输出流对象
- 读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)
- 释放资源
-
代码实现
3. 字符流
3.1 字节流操作文本文件出现乱码问题
问题:
1.既然字节流可以操作所有的文件,为什么还要学习字符流?
- 学习字符流的原因
- 如果利用字节流,把文件中的中文读取到内存中,有可能出现乱码
- 如果利用字节流,把文件中的中文写到内存中,也有可能出现乱码
3.2 编码表
问题:
1.编码和解码分别是什么意思?
2.出现乱码的原因是什么?
3.ASCII码表中是否包含中文?
4.在GBK编码中,一个中文以几个字节的形式存储?
5.在UTF-8编码中,一个中文以几个字节的形式存储?
-
基础知识:
- 计算机中存储的信息都是用二进制数表示的
- 按照某种规则,将字符变成二进制,再存储到计算机中,称为编码
- 按照同样的规则,将存储在计算机中的二进制数解析出来,称为解码
- 编码和解码的方式必须一致,否则会导致乱码
- 简单理解:
- 存储一个字符a,首先需要在码表中查到对应的数字是97,然后转换成二进制进行存储
- 读取的时候,首先把二进制解析出来,再转成97,通过97查找对应的字符是a
-
什么是字符集
- 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等
- l计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBXXX字符集、Unicode字符集等
-
常见的字符集
- ASCII字符集:
- ASCII:是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
- GBK字符集:
- Windows系统默认的码表,也是最常用的中文码表。共收录了21003个汉字,同时支持繁体汉字以及日韩汉字等
- 是中国的码表,一个中文以两个字节的形式存储。不包含世界所有国家的文字
- Unicode字符集:
- 是统一的万国码,容纳了世界上大多数国家的所有常见文字和符号
- 但是Unicode表示字符太多,所以Unicode码表中的数字不是直接以二进制形式存储到计算机中。会先通过UTF-7,UTF-7.5,UTF-8,UTF-16,以及UTF-32进行编码,再存储到计算机,其中最为常见的是UTF-8
- UTF-8中,一个中文以三个字节的形式存储
- ASCII字符集:
-
编码表小结

- 汉字存储和展示过程解析
3.3 编码和解码的方法
问题:
1.getBytes()方法使用的平台默认编码是什么?
2.调用String构造方法传入指定编码时,大小写有没有关系?
-
相关方法
方法名 说明 byte[] getBytes() 使用平台的默认字符集将该 String编码为一系列字节 byte[] getBytes(String charsetName) 使用指定的字符集将该 String编码为一系列字节 String(byte[] bytes) 使用平台的默认字符集解码指定的字节数组来创建字符串 String(byte[] bytes, String charsetName) 通过指定的字符集解码指定的字节数组来创建字符串 -
代码演示
//1.编码:把看得懂的内容,变成看不懂的 //定义看得懂的中文字符串 String s1 = "黑马程序员"; //2.编码,调用getBytes byte[] bys1 = s1.getBytes(); System.out.println(Arrays.toString(bys1)); //3.结果:-23, -69, -111, -23, -87, -84, -25, -88, -117, -27, -70, -113, -27, -111, -104 //都是负数,15个数字,说明这个平台默认编码是UTF-8 //4.指定编码 byte[] bys2 = s1.getBytes("UTF-8"); System.out.println(Arrays.toString(bys2)); //结论:和前面的结果对应,一模一样,说明确实是UTF-8 //5.把看不懂的变成看得懂,就是解码,使用String的构造方法 System.out.println(new String(bys1, "UTF-8")); System.out.println(new String(bys1, "GBK")); //6.结论:如果编码和解码的规则不一致,就会出现乱码 -
字节流读取中文出现乱码的原因
- 因为字节流一次读一个字节,而不管是gbk还是utf-8,一个中文都是多个字节,用字节流每次只能读其中的一部分,所以就会出现乱码问题
3.4 读取中文出现乱码的原因
- 字节流读取出现乱码的原因
- 字节流一次读一个字节,不管GBK还是UTF-8一个中文都是多个字节,用字节流每次只能读取其中一部分,所以就会出现乱码
3.5 读取中文的过程
问题:
1.在读取的时候,底层如何判断读取的是中文?
2.读取中文总共有几个步骤,分别是什么?
3.如果是拷贝文本文件,使用什么流进行操作?
- 字符流 = 字节流 + 编码表
- 基础知识:
- 不管在哪张码表中,中文的第一个字节一定是负数
- 读取中文的步骤:
- 发现读取的是中文
- 看是哪种码表,确定对应字节数
- 一次性读取对应的字节数
- 把整体读取的字节转成中文
- 小结:
- 想要进行拷贝,一律使用字节流或者字节缓冲流
- 想要把文本文件中的数据读到内存中,使用字符输入流
- 想要把内存中的数据写到文本文件中,使用字符输出流
- GBK码表一个中文2个字节,UTF-8编码格式一个中文3个字节
3.6 写出数据
问题:
1.使用字符流写数据,使用的字符输出流是哪一个?
2.使用字符流写数据的方法时,可以传入几种类型的数据?
3.调用字符输出流构造方法,底层会调用什么流的构造?
-
步骤:
- 创建字符输出流对象
- 写数据
- 释放资源
-
介绍:
-
Writer:用于写入字符流的抽象父类
- FileWriter:用于写入字符流的常用子类
-
FileWriter构造方法
方法名 说明 FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象 FileWriter(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象 FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象 FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象 -
成员方法
方法名 说明 void write(int c) 写一个字符 void write(char[] cbuf) 写入一个字符数组 void write(char[] cbuf, int off, int len) 写入字符数组的一部分 void write(String str) 写一个字符串 void write(String str, int off, int len) 写一个字符串的一部分 -
代码演示
//目标:掌握字符输出流的创建和基本使用
public class Test {
public static void main(String[] args) throws IOException {
//1.创建字符输出流对象,这里使用FileWriter。允许追加写出
FileWriter fw = new FileWriter("test-io\\a.txt",true);
//2.分别使用字符输出流的各个不同写出方法,写出数据
//2.1 写出一个字节数据,会转换成对应的字符写到文件中
fw.write(97);
fw.write("\r\n");
//2.2 可以写出一个字符数组
char[] chs1 = {'a', 'b', 'c'};
//2.3 字符数组中可以是数字,因为有ASCII码表的关系,每一个字符都有一个对应的数字
char[] chs2 = {97, 98, 99};
fw.write(chs1);
fw.write("\r\n");
fw.write(chs2);
fw.write("\r\n");
fw.write(chs2,0,1);
fw.write("\r\n");
//2.4 写出一个字符串
fw.write("键盘敲烂,月薪过万");
fw.write("\r\n");
fw.write("键盘敲烂月薪过万",0,4);
fw.write("\r\n");
//3.释放资源
fw.close();
}
}
3.7 写出数据的注意事项
问题:
1.创建字符输出流对象时,需要注意哪些?
2.能否使用字符输出流去写出int类型的数据?
- 注意事项
- 创建字符输出流对象时
- 如果文件不存在,就会创建文件,但是要保证父级路径存在
- 如果文件存在,就会清空文件内容
- 写数据时
- 写出int类型的整数,实际写出的是整数在码表上对应的字母
- 写出字符串数据,是把字符串本身原样写出
- 释放资源时
- 每次使用完必须要释放资源
- 创建字符输出流对象时
3.8 flush和close方法
问题:
1.使用FileWriter写数据后,flush()和close()都没调用,会有什么问题?
2.flush()方法有哪些特点?
3.调用close()方法后,需要注意什么?
| 方法名 | 说明 |
|---|---|
| flush() | 刷新流,还可以继续写数据 |
| close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
- 注意:如果flush()和close()方法都没有调用,数据就没有刷新到本地文件
3.9 读取数据
-
介绍:
- Reader:用于读取字符流的抽象父类
- FileReader:用于读取字符流的常用子类
-
构造方法
方法名 说明 FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader
-
成员方法
方法名 说明 int read() 一次读一个字符数据 int read(char[] cbuf) 一次读一个字符数组数据 -
代码演示
//1.创建流对象 FileReader fr = new FileReader("test-io\\a.txt"); //2.一次读取一个字符数组,可以提高效率,定义字符数组 char[] chs = new char[1024]; //3.定义一个长度,代表每次读取的字符数组中数据的个数 int len; //4.开始读取,循环度。把字符数组丢到read方法中,就是把这个数据给数组 while ((len = fr.read(chs)) != -1) { //5.把读取到的数据变成字符串 System.out.println(new String(chs, 0, len)); } //5.释放资源 fr.close();
3.10 练习
- 案例需求:
- 将键盘录入的用户名和密码保存到本地实现永久化存储
- 要求用户名独占一行,密码独占一行
- 实现步骤
- 获取用户输入的用户名和密码
- 将用户输入的用户名和密码写入到本地文件中
- 关流,释放资源
- 代码实现
//1.键盘录入需要录入的字符串
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
//2.创建字符输出流对象
FileWriter fw = new FileWriter("test-io\\account.txt");
//3.写出数据
fw.write(username);
//换行
fw.write("\r\n");
fw.write(password);
fw.write("\r\n");
//4.刷新数据,释放资源
fw.flush();
fw.close();

浙公网安备 33010602011771号