目录标题

  • 覆盖与追加
  • 在日常开发中 难免会对文件 进行写入操作 有些时候需要清空原有的内容 写入新内容 或者有些时候需要在原有内容的基础上 接着写
  • 这时就需要针对不同情况 使用不同的构造方法 在下面的五个构造方法中 只有带append参数的构造方法 才可以指定写入方式
  • 写入的方式有两种 一种是 false 代表覆盖 还有 一种是true 代表追加 除了最后一个构造方法以外 另外两个不带append参数的构造方法 内部都是调用带append参数的构造方法(重点!!!) 其append 默认为 false 即写入方式默认为覆盖
  • 在覆盖方式下 只要是创建了输出流 即便你没有写入任何内容 原有内容都将被清空 这一点是非常危险的
  • 练习
  • 下面我们来验证 覆盖 是不是 如上面所说的 下面这是一段有内容的文件 然后我们仅仅只是创建了输出流 没有写入任何内容 执行程序后
  • 打开刚刚的文件 可以看到 文件的内容 已经被清空 由此可见 在日常开发中 覆盖这种方式 要谨慎使用 (重点!!!) 因为这样 很容易误删文件里面的内容 造成不必要的损失
  • 反观 追加方式 要安全的多
  • 下面演示一下 追加的例子 还是创建一个有内容的文件 这次 我们使用追加方式来创建输出流
  • 将 append参数值 设置为 true 然后顺便 追加一些新内容 执行程序后 打开刚刚的文件 可以看到
  • 最后总结
  • 本节 介绍了 append参数的两种含义 覆盖与 追加
  • Java复制文件
  • 复制的本质其实是 把数据从一个文件中读出来 然后再写到另一个文件中去 这个过程是边读边写的 读到 -1 时结束
  • 复制的前后内容一致
  • 上面 提到的 ”读“ 和 ”写“ 分别需要用到对应的流
  • 读取数据 需要用到输入流 InputStream 这里我们选择它其中的一个子类 FileInputStream
  • 而写入数据 需要用到 输出流OutputStream 这里我们选择它其中的一个子类 FileOutputStream
  • 选好输入输出流以后 接下来 编写示例代码 下面是我们要复制的文件 内容是 ”abc“
  • 首先 声明 输入输出流 初始值 为null 然后 写上try 代码块 在 try 代码块中 初始化 输入输出流 输入流 传入的是 目标文件路径 (也就是你要复制哪个文件) 输出流传入的是目的地文件路径(也就是你要把复制的文件放在哪) 创建输入输出流有异常抛出 使用catch将其捕获
  • 接下来 创建一个字节数组 长度为 1024 这是最常用的长度 接着 声明一个用于记录读取字节数的变量 length 使用 while语句 循环读取字节 读到 -1 时结束 每读取一次 都调用输出流的write方法 写入字节数组 偏移量 为 0(也就是从字节数组下标0的位置开始写) 每次写入的长度为 当前读取的字节数length 至此 try代码块里面的内容编写完成
  • 接下来 写上 finally代码块 在finally代码块中 关闭输入输出流 在关闭流之前 先判断一下 流是否为空 至此 整个示例编写完成
  • 执行程序 观察执行结果 从执行结果来看 程序没有报错 文件复制成功 内容一模一样
  • 总结
  • 本节介绍了 使用输入输出流 完成复制功能 一共有四步 第一步:声明输入输出流 第二步:初始化输入输出流 第三步:边读边写 第四步:关闭输入输出流
  • 处理未知文件(未知目录不存在)
  • 我们可能会遇到这种问题 向文件中写入数据时 明明已经判断了 当文件不存在时 创建新文件 可还是会出现错误 会显示 “No such file or directory”(无此文件或目录)
  • “无此文件”可以理解 所以我们才要创建 问题就出现在“无此目录”上 文件名之前的都是目录
  • 经过验证: Users(用户)目录存在 admin目录存在 Downloads(下载)目录存在 发现问题 Java目录不存在 所以只要其中有一个目录不存在 文件就会创建失败
  • 在实际开发中 究竟是哪一个目录不存在 我们不得而知 但无论是哪个目录不存在 我们都可以使用 File对象的mkdirs方法轻松解决 它可将路径中所有不存在的目录都创建出来
  • 接下来 我们来重写 这段处理未知文件的逻辑 依然是使用File对象 当文件不存在时 调用file 对象的mkdirs方法创建目录
  • 注意点! 这里的 “二哥一直坚持.txt” 是我们要创建的文件 它不是一个目录 它前面的才是目录 所以需要先调用getParentFile()方法 获得父路径 返回的是 一个File对象 紧接着再调用mkdirs方法 创建目录
  • 当目录创建失败时 输出“目录创建失败” 并 return 当目录创建成功时 继续创建文件 当文件创建失败时 输出“文件创建失败” 并 return 至此 整个逻辑编写成功(重点 重点 重点!!!)
  • 但是这部分代码还可以优化 创建文件的代码可以省略不用写 因为使用输出流时 若文件不存在 系统会自动帮我们创建 else 这部分代码 可以移动到 if 语句中 不要忘记取反 至此 优化后的代码编写完成
  • 在日常开发中 我们可以用下面这段代码 来创建未知文件 接下来 我们用新逻辑替换掉上面的逻辑代码 再试试看 重点: 对比两者的不同
  • 从执行结果来看: 程序没有报错 不存在的目录 和 文件都已经创建好 内容也写了进去
  • 对比两者 总结为什么代码会出错
  • 总结:
  • 本节介绍了 如何处理未知文件 一共有四步 第一步 创建 File对象 第二步 判断文件是否存在 第三步 创建目录 第四步 处理创建失败的情况
  • 字符编码
  • 学习字符编码相关知识 有助于我们了解 计算机是如何储存字符的
  • 一:ASCII 计算机里面只能存 0 和 1 存不了 A,B,C, 更存不了中文
  • 那该怎么办呢? 只能搞一张表格 让字符与 “O” 和 “1" 对应起来 例如 01000001对应A 01000010对应B
  • 以此类推 最早搞这张表的是美国 他们取名叫 “ASCII编码表” 也叫 “ASCII字符集” 共128个字符 其中包括 32个不可打印的控制符 10个数字 26个大写字母 26个小写字母 和 34个标点符号
  • 随着计算机的普及 发展壮大 各国开始制作自家的编码表 我国也不例外
  • 1981年 5月1日 发布 “GB(国标)2312字符集” 共 7445 个字符 其中包括 6763个汉字 编码表中的第一个汉字 是 “啊”
  • 其码值如下图所示 一个字节最多可以表示 256 个字符 两个字节最多可以表示 65535 个字符
  • 而 GB2312字符集里面 有七千多个字符 一个字节不够存 所以 它里面的字符 都是两个字节
  • 每一个国家都搞一张表 意味着在互联网上 你想看懂其他国家在说什么 还要去下载他们的编码表解析一下 否则看到的就是乱码 这样不利于各国之间的合作与交流
  • 二 : GB2312
  • 于是 国际组织提议 全世界共用一张表 把各国的子符都容纳进来 这张表就叫做 :“Unicode编码表” 也叫 “Unicode字符集”
  • 其中汉字在 4E00-9FFF 区间 字符集中的第一个汉字是 “一” 其Unicode编码是U+4E00 Unicode编码都以 “U+” 开头
  • 大家可以看到: 下图中 并没有列出 “ 一 ” 的十六进制 十进制 和 二进制 存储形式 原因是 Unicode只负责给字符编码 不负责具体怎么储存
  • 它为什么不负责存储呢 举例 : 拿 大写字母 “A” 来说 它的Unicode编码是 “U+0041” 转成 二进制 有 16 位 占两个字节 实际上 :存储“A”用不了两个字节 一个字节就够了 类似于这样的字符 还有很多 为了 节省存储空间 Unicode在存储这方面不做具体规定
  • 具体的由 UTF-8 UTF-16 UTF-32 等 去负责怎么存储 其中日常开发中用得较多的是UTF-8 使用可变长度字节来存储存 Unicode字符
  • 有关 UTF-8 相关知识 在下一节知识讲解会
  • 三:Unicode
  • 总结:
  • 本节介绍了 ASCII字符集 GB2312字符集 和 Unicode字符集 了解字符编码 对学习字符流 和 处理乱码 有帮助 要理解掌握
  • 中文是如何存储在电脑上的(UTF-8)
  • 下面我们要学习 UTF-8 相关知识 在上面知识中 介绍了 计算机是如何给 字符编码的 了解到 Unicode 只负责 编码工作 也就是只负责给每个字符编一个号 它不负责存储工作 也就是不规定 每个字符 占 几个字节
  • 原因是每个字符 所占 字节数 不同 例如:英文 占一个 字节 拉丁文 占 两个 字节 中文 占三个字节 图形文字 占 四个字节
  • 如果进行统一规定 : 所有字符都 占 四个字节 那么 : 占字节数较少的字符就很浪费存储空间
  • 为了 节省存储空间 Unicode 在存储这方面 不做 具体规定 具体的由 UTF-8 UTF-16 和 UTF-32等 去负责
  • 其中 日常开发中 用的比较多的是 UTF-8 使用可变长度字节来储存Unicode字符 字节数 能省则 省
  • 从这些字符的二进制里 我们可以找到一些存储的规律 例如: 占四个字节的字符 它的第一个字节 开头有 4 个连续的 “ 1 ” 占三个字节的字符 它的第一个字节 开头有 3 个连续的“ 1 ” 占二个字节的字符 它的第一个字节 开头有 2 个连续的“ 1 ” 占一个字节的字符 它的第一个字节 开头有 0 个连续的“ 1 ”
  • 由此我们可以总结出 UTF-8 的第一个特点: 除字节数为1的字符以外 其余字符的第一个字节里 开头有多少个连续的 “1” 就代表着该字符使用多少个字节来储存
  • 另外 我们还可以发现 字节数大于 1 的字符 除第一个字节以外 其余字节 都以 “10” 开头 由此 我们可以总结出 :UTF-8的第二个特点: 字节数大于 1的字符 除第一个字节以外 其余字节都以“ 10 ” 开头
  • 现在每个字节开头要么代表 字符所占字节数 要么就是固定写法 那么剩余的字符又有什么含义呢
  • 那么剩余的字符又有什么含义呢 我们可以将 字符的Unicode 编码转为 二进制 查看 我们发现 它们是一一对应的关系 从后往前 依次填充 直到填满为止
  • 由此我们可以总结出 UTF-8编码规则: 如果 Unicode编码值 在 0-7F 区间 则使用 1 个字节存储 字节的第一位 填充 “ 0 ”
  • 如果Unicode编码值 在 80-7FF 区间 则使用两个字节 存储 第一个字节的前三位 填充 “ 110 ” 第二个字节的前两位 填充 “ 10 ”
  • 如果Unicode编码值 在 800-FFFF区间 则使用三个字节存储 第一个字节的 前四位 填充 “ 1110 ” 第 二 三 个字节的前两位 填充 “ 10 ”
  • 如果 Unicode编码值 在 10000-10FFFF区间 则使用 四个 字节存储 第一个字节 的前五位 填充 “ 11110 ” 第 二 三 四 个字节的前两位 填充 “ 10 “
  • 练习
  • 下面 我们来举一个中文例子 讲解: 以汉字 ” 一 “ 为例 它的 Unicode编码为 U+4E00 属于 800-FFFF区间 二进制格式 如图所示 将Unicode编码转为 二进制 然后从后往前 依次填充 填满为止 这样就得到了 汉字 ” 一 “ 的UTF-8编码 它的 二进制 十进制 十六进制 如图所示
  • 总结:
  • 本节介绍了 Unicode字符 如何存储在电脑上 其中 UTF-8编码方式 最为常用 编码规则 如下图所示
  • 以字符为单位读取数据
  • 什么是 以字符为单位读取数据 通过 下图两节的学习
  • 已经学习了 字符编码 Unicode的基础知识 以及 如何存储 Unicode字符的 UTF-8 编码
  • 在这节学习中 你要学习到 什么是 ” 以字符为单位读取数据 “ 我们学过 字符是以 字节的形式存储在文件中的 既然 要以字符为单位 进行读取 那么就必须知道 这个字符 占几个字节
  • 若读到的字节 以 ” 0 “ 开头 说明该字符 占一个字节 读完一个字节后 再将读到的字节值转为 Unicode编码 然后再根据编码 在Unicode字符集中 找到对应的字符 得到 U+004D 也就是 字符 ”M“
  • 若读到的字节 以” 110 “ 开头 有两个 连续的 ” 1 “ 说明该字符 占 两个 字节 读完两个字节以后 以同样的方法 找到相应的字符 这是一个拉丁文字符
  • 若读到的字节 以” 1110 “ 开头 有三个连续的 “ 1 ” 说明该字符 占 三个 字节 读完三个字节 以后 以同样的方法 找到相应的字符 这个一个中文字符
  • 若读到的字节 以“ 11110 ” 开头 有 四个连续的 “ 1 ” 说明该字符 占 四个字节 读完四个 字节以后 以同样的方法 找到相应的字符 这是一个图形字符
  • 所有的字节已读完 均已找到对应的字符 以上就是“ 以字符为单位读取数据 ” 的意思
  • 总结 :
  • 以字符为单位 读取数据 就是 字符占几个字节 就连续读几个字节 下一讲要学会 如何使用 字符输入流:Reader 进行以字符为单位读取数据
  • 字符输入流 Reader
  • 如果我们想以 字符为单位读取数据的话 就需要借助 字符输入流 Reader
  • 和字节输入流 一样 字符输入流 也有一套体系 共有十个类 名称均以:“ Reader ” 结尾
  • 针对不同的需求 选择合适的类 比较常用的有: 带缓冲区的字符输入流 :BufferedReader 带行号的字符输入流:LineNumberReader 和用于读取文本文件的: 文件字符输入流FileReader
  • 以及 将字节输入流转换为 字符输入流: InputStreamReader
  • 练习
  • 接下来 : 使用FileReader 编写一个 读取文本文件内容的示例
  • 它一共有五个构造方法 第一个参数 我们在 ” 字节输入流InputStream “ 中介绍过 第二个参数: 指定文件的字符集 现在一般都是 ” UTF-8 “
  • 还不会字符集的 要学习上面的内容 :字符编码学习和UTF-8编码
  • 示例: 下面是我们读取的文件: 内容为:” abcdefg 二哥一直坚持! “
  • 首先 先声明一个字符输入流: 初始值为null 然后初始化它 传入 你要读取的文本文件路径 创建FileReader 有异常抛出 使用 try-catch 将其捕获 写上 finally 代码块
  • 在finally代码块中 调用 输入流 的close方法 关闭流 在关闭流 之前 先判断一下 流是否为空 当流不为空时 再关闭流 close方法 有异常抛出 使用 try-catch 将其捕获
  • 接下来 我们来编写读取数据的部分 声明一个用于记录每次读到的字符变量 为什么是int类型 在上面学习中我们已经学到了 “ read方法读的是字节 为什么返回 int “ 已经解释过了
  • 然后使用while循环 每次读取单个字符 读到 -1 时结束 最后 输出读到的字符
  • 若直接输出 ” c “ 得到的只是字节值 想要得到字符 需要将其转为char类型
  • 另外这里不要用 println进行输出 它会一行输出 一个字符 建议使用print 进行输出 这样输出的内容格式 和 原来的文件 是一样的
  • 到此 示例代码编写完成 执行程序 观察执行结果 从执行结果来看 程序输出的内容 和原来的文件一模一样
  • 每次只读一个字节的效率太低 可以使用字符数组 进行批量读取 依次来提高效率
  • 总结:
  • 本节介绍了 字符输入流体系 以及如何使用字符输入流 读取文本文件
  • 以字符为单位写入数据
  • 之前我们写数据都是通过字节写入的 即使你写入的数据是字符 那也要将它们转为 字节 然后再通过字节写入 若是要省去手动 ” 字符转字节 “ 这个环节 直接以字符为单位 写入的话 需要借助 字符输出流 Writer
  • 在了解 字符输出流之前 先来 了解 ” 以字符为单位写入数据 “ 的过程是怎么样的
  • 以 Unicode字符为例 假如我们要写入汉字 ” 一 “ 首先找到它的Unicode编码 然后根据Unicode编码所属范围 确定 UTF-8 编码方式
  • 最后将 Unicode编码 根据 UTF-8编码方式转为 二进制 写入文件中
  • 这些就是 ” 以字符为单位写入数据 “ 的过程 虽然它的本质还是通过字节来写入的 但是它省去了 手动字符 转字节这个过程 写程序就更方便了
  • 字符输出流Writer
  • 我们要学习 什么是字符输出流
  • 字符输出流体系 它和字符输入流 Reader一样 也有一套体系 常用的类有四个 第一个是:带缓冲区的 BufferedWriter 第二个是:将字节输出流转换为 字符输出流的OutputStreamWriter 第三个是:以字符为单位将数据输出到文件的 FileWriter 第四个是:打印各种类型数据的打印流 PrintWriter
  • 接下来 使用 FileWriter 完成一个复制文件的示例 它一共有九个构造方法 虽然看起来比较多 但实际上 我们只用 带 append参数的方法 一共有四个
  • 第一个参数: 我们在 ” 字节输入流 InputStream “ 中学到过 charset参数 指定文件的字符集 一般为 ” UTF-8 “ 我们在 ” 字符编码 Unicode “ 和 ” 中文是如何存储到电脑上的(UTF-8编码) “ 中 学习过
  • append参数 我们在 ” 追加和覆盖 “ 中学习过 它可以指定写入模式 true为追加 false 为覆盖 建议使用 追加方式 覆盖这种方式要慎用 因为这样很容易误删 文件里的内容 造成不必要的损失
  • 练习
  • 介绍完上面的参数 接下来我们要编写 复制文件 示例 下图为我们要复制的文件 内容为: ” abcdefg 二哥一直坚持! “
  • 首先声明 字符输入输出流 初始值都为 null 然后初始化它们 读取的是源文件 输出的是副本 创建输入输出流时 有异常抛出 使用try-catch 将其捕获 写上finally 代码块
  • 在finally代码块中 调用close方法关闭流 在关闭流之前 先判断流 是否为空 当流不为空时 再关闭流 close方法 有异常抛出 使用try-catch 将其捕获
  • 接下来 我们来编写复制数据的部分 定义一个长度为 100 的字符数组 作为缓冲区 读取数据的时候 直接读到缓冲区里面
  • 输出的时候 直接从缓冲区里面输出 接着 定义一个变量length 用于记录每次已读字符数 当length 等于 -1 时 说明数据已读完
  • 接下来 使用while循环 批量读取字符 读到 -1 时结束 最后使用 writer输出 读到的字符
  • 第一个参数为字符缓冲区 第二个参数 为偏移量 是 0 表示从 缓冲区的第一位 开始输出 第三个参数 为 输出的长度 就是已读到的字符数 读多少 输出多少
  • 到此 示例代码编写完成 执行程序 观察执行结果 从执行结果来看 程序没有报错
  • 文件已经复制完成 副本与源文件内容一摸一样
  • 总结:
  • 本节介绍了 字符输出流体系 以及如何使用字符输出流 这些方法的功能 如下图所示
  • 复制文本文件一行代码搞定
  • 在上个知识点中 我们使用 FileReader 和FileWriter 完成了 复制文本文件的示例 这个示例 还可以更简单一下
  • 读取数据和写入数据部分 可以使用Reader 的transferTo方法 一行代码搞定 方法需传入一个字符输出流 writer 这样就写好了
  • 对比两边的代码 代码量直接减少 90% 提高了开发效率 我们把右边的代码 带入到完整的代码中 执行程序观看
  • 从执行结果来看 程序没有报错 文件也复制成功 副本与源文件内容一模一样
  • 总结
  • 这一节介绍了 transferTo方法 一行代码搞定复制文本文件
  • 注意 该方法只有字符输入流拥有 字节输入流没有 所以它只能复制文本文件 不能复制其他文件


覆盖与追加

在日常开发中
难免会对文件 进行写入操作
有些时候需要清空原有的内容
写入新内容
或者有些时候需要在原有内容的基础上
接着写



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java

这时就需要针对不同情况
使用不同的构造方法
在下面的五个构造方法中
只有带append参数的构造方法
才可以指定写入方式


java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_02

写入的方式有两种
一种是 false 代表覆盖
还有 一种是true 代表追加
除了最后一个构造方法以外
另外两个不带append参数的构造方法
内部都是调用带append参数的构造方法(重点!!!)
其append 默认为 false
即写入方式默认为覆盖



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_03

在覆盖方式下
只要是创建了输出流
即便你没有写入任何内容
原有内容都将被清空
这一点是非常危险的



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_04

练习

下面我们来验证
覆盖 是不是 如上面所说的
下面这是一段有内容的文件
然后我们仅仅只是创建了输出流
没有写入任何内容
执行程序后



打开刚刚的文件
可以看到
文件的内容 已经被清空
由此可见 在日常开发中
覆盖这种方式 要谨慎使用 (重点!!!)
因为这样 很容易误删文件里面的内容
造成不必要的损失



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_05

反观 追加方式
要安全的多



下面演示一下 追加的例子
还是创建一个有内容的文件
这次 我们使用追加方式来创建输出流



将 append参数值 设置为 true
然后顺便 追加一些新内容
执行程序后
打开刚刚的文件
可以看到




java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_06

最后总结

本节 介绍了 append参数的两种含义
覆盖与 追加



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_07

Java复制文件

复制的本质其实是
把数据从一个文件中读出来
然后再写到另一个文件中去
这个过程是边读边写的
读到 -1 时结束



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_08

复制的前后内容一致

java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_09

上面 提到的 ”读“ 和 ”写“
分别需要用到对应的流



读取数据 需要用到输入流 InputStream
这里我们选择它其中的一个子类
FileInputStream



而写入数据 需要用到 输出流OutputStream
这里我们选择它其中的一个子类
FileOutputStream



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_10

选好输入输出流以后
接下来 编写示例代码
下面是我们要复制的文件
内容是 ”abc“



首先 声明 输入输出流
初始值 为null
然后 写上try 代码块
在 try 代码块中 初始化 输入输出流
输入流 传入的是 目标文件路径 (也就是你要复制哪个文件)
输出流传入的是目的地文件路径(也就是你要把复制的文件放在哪)
创建输入输出流有异常抛出
使用catch将其捕获



接下来 创建一个字节数组
长度为 1024
这是最常用的长度
接着 声明一个用于记录读取字节数的变量 length
使用 while语句
循环读取字节 读到 -1 时结束
每读取一次 都调用输出流的write方法
写入字节数组 偏移量 为 0(也就是从字节数组下标0的位置开始写)
每次写入的长度为 当前读取的字节数length
至此 try代码块里面的内容编写完成




接下来 写上 finally代码块
在finally代码块中 关闭输入输出流
在关闭流之前
先判断一下 流是否为空
至此
整个示例编写完成



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_11

执行程序 观察执行结果
从执行结果来看
程序没有报错
文件复制成功
内容一模一样



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_12

总结

本节介绍了 使用输入输出流 完成复制功能
一共有四步
第一步:声明输入输出流
第二步:初始化输入输出流
第三步:边读边写
第四步:关闭输入输出流



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_13

处理未知文件(未知目录不存在)

我们可能会遇到这种问题
向文件中写入数据时
明明已经判断了 当文件不存在时 创建新文件 可还是会出现错误
会显示 “No such file or directory”(无此文件或目录)



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_14

“无此文件”可以理解 所以我们才要创建
问题就出现在“无此目录”上
文件名之前的都是目录



经过验证: Users(用户)目录存在
admin目录存在 Downloads(下载)目录存在
发现问题 Java目录不存在
所以只要其中有一个目录不存在 文件就会创建失败



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_15

在实际开发中 究竟是哪一个目录不存在
我们不得而知
但无论是哪个目录不存在
我们都可以使用
File对象的mkdirs方法轻松解决
它可将路径中所有不存在的目录都创建出来



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_16

接下来 我们来重写
这段处理未知文件的逻辑
依然是使用File对象
当文件不存在时
调用file 对象的mkdirs方法创建目录



注意点! 这里的 “二哥一直坚持.txt” 是我们要创建的文件
它不是一个目录
它前面的才是目录
所以需要先调用getParentFile()方法
获得父路径 返回的是 一个File对象
紧接着再调用mkdirs方法 创建目录



当目录创建失败时 输出“目录创建失败”
并 return
当目录创建成功时 继续创建文件
当文件创建失败时 输出“文件创建失败”
并 return
至此 整个逻辑编写成功(重点 重点 重点!!!)



但是这部分代码还可以优化
创建文件的代码可以省略不用写
因为使用输出流时
若文件不存在 系统会自动帮我们创建
else 这部分代码
可以移动到 if 语句中
不要忘记取反
至此 优化后的代码编写完成



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_17

在日常开发中 我们可以用下面这段代码
来创建未知文件
接下来 我们用新逻辑替换掉上面的逻辑代码
再试试看
重点: 对比两者的不同



从执行结果来看:
程序没有报错
不存在的目录 和 文件都已经创建好
内容也写了进去



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_18

对比两者 总结为什么代码会出错



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_19

总结:

本节介绍了
如何处理未知文件
一共有四步
第一步 创建 File对象
第二步 判断文件是否存在
第三步 创建目录
第四步 处理创建失败的情况



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_20

字符编码

学习字符编码相关知识
有助于我们了解
计算机是如何储存字符的


java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_21

一:ASCII
计算机里面只能存 0 和 1
存不了 A,B,C,
更存不了中文



那该怎么办呢?
只能搞一张表格
让字符与 “O” 和 “1" 对应起来
例如 01000001对应A
01000010对应B



以此类推
最早搞这张表的是美国 他们取名叫 “ASCII编码表”
也叫 “ASCII字符集”
共128个字符
其中包括 32个不可打印的控制符
10个数字 26个大写字母 26个小写字母
和 34个标点符号



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_22

java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_23

随着计算机的普及
发展壮大
各国开始制作自家的编码表
我国也不例外



1981年 5月1日
发布 “GB(国标)2312字符集”
共 7445 个字符
其中包括 6763个汉字
编码表中的第一个汉字 是 “啊”



其码值如下图所示
一个字节最多可以表示 256 个字符
两个字节最多可以表示 65535 个字符



而 GB2312字符集里面 有七千多个字符
一个字节不够存
所以
它里面的字符 都是两个字节



每一个国家都搞一张表
意味着在互联网上
你想看懂其他国家在说什么
还要去下载他们的编码表解析一下
否则看到的就是乱码
这样不利于各国之间的合作与交流



二 : GB2312



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_24

java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_25

于是
国际组织提议
全世界共用一张表
把各国的子符都容纳进来
这张表就叫做 :“Unicode编码表”
也叫 “Unicode字符集”



其中汉字在 4E00-9FFF 区间
字符集中的第一个汉字是 “一”
其Unicode编码是U+4E00
Unicode编码都以 “U+” 开头



大家可以看到:
下图中 并没有列出
“ 一 ” 的十六进制 十进制 和 二进制 存储形式
原因是 Unicode只负责给字符编码
不负责具体怎么储存



它为什么不负责存储呢
举例 : 拿 大写字母 “A” 来说
它的Unicode编码是 “U+0041”
转成 二进制 有 16 位 占两个字节
实际上 :存储“A”用不了两个字节
一个字节就够了
类似于这样的字符 还有很多
为了 节省存储空间
Unicode在存储这方面不做具体规定



具体的由 UTF-8 UTF-16 UTF-32
等 去负责怎么存储
其中日常开发中用得较多的是UTF-8
使用可变长度字节来存储存 Unicode字符



有关 UTF-8 相关知识
在下一节知识讲解会



三:Unicode



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_26

java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_27

总结:

本节介绍了
ASCII字符集
GB2312字符集
和 Unicode字符集
了解字符编码
对学习字符流 和 处理乱码 有帮助
要理解掌握



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_28

中文是如何存储在电脑上的(UTF-8)

下面我们要学习 UTF-8 相关知识
在上面知识中 介绍了 计算机是如何给 字符编码的
了解到 Unicode 只负责 编码工作
也就是只负责给每个字符编一个号
它不负责存储工作
也就是不规定 每个字符 占 几个字节



原因是每个字符 所占 字节数 不同
例如:英文 占一个 字节
拉丁文 占 两个 字节
中文 占三个字节
图形文字 占 四个字节



如果进行统一规定 :
所有字符都 占 四个字节
那么 : 占字节数较少的字符就很浪费存储空间




为了 节省存储空间
Unicode 在存储这方面 不做 具体规定
具体的由 UTF-8 UTF-16 和 UTF-32等 去负责



其中 日常开发中 用的比较多的是 UTF-8
使用可变长度字节来储存Unicode字符
字节数 能省则 省



从这些字符的二进制里
我们可以找到一些存储的规律
例如:
占四个字节的字符 它的第一个字节 开头有 4 个连续的 “ 1 ”
占三个字节的字符 它的第一个字节 开头有 3 个连续的“ 1 ”
占二个字节的字符 它的第一个字节 开头有 2 个连续的“ 1 ”
占一个字节的字符 它的第一个字节 开头有 0 个连续的“ 1 ”



由此我们可以总结出
UTF-8 的第一个特点:
除字节数为1的字符以外 其余字符的第一个字节里
开头有多少个连续的 “1” 就代表着该字符使用多少个字节来储存




另外 我们还可以发现 字节数大于 1 的字符
除第一个字节以外 其余字节 都以 “10” 开头
由此 我们可以总结出 :UTF-8的第二个特点:
字节数大于 1的字符 除第一个字节以外 其余字节都以“ 10 ” 开头



现在每个字节开头要么代表 字符所占字节数
要么就是固定写法
那么剩余的字符又有什么含义呢



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_29

java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_30

那么剩余的字符又有什么含义呢
我们可以将 字符的Unicode 编码转为 二进制 查看
我们发现
它们是一一对应的关系
从后往前 依次填充 直到填满为止



由此我们可以总结出 UTF-8编码规则:
如果 Unicode编码值 在 0-7F 区间
则使用 1 个字节存储
字节的第一位 填充 “ 0 ”



如果Unicode编码值 在 80-7FF 区间
则使用两个字节 存储
第一个字节的前三位 填充 “ 110 ”
第二个字节的前两位 填充 “ 10 ”



如果Unicode编码值 在 800-FFFF区间
则使用三个字节存储
第一个字节的 前四位 填充 “ 1110 ”
第 二 三 个字节的前两位 填充 “ 10 ”



如果 Unicode编码值 在 10000-10FFFF区间
则使用 四个 字节存储
第一个字节 的前五位 填充 “ 11110 ”
第 二 三 四 个字节的前两位 填充 “ 10 “



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_31

练习

下面 我们来举一个中文例子 讲解:
以汉字 ” 一 “ 为例
它的 Unicode编码为 U+4E00
属于 800-FFFF区间
二进制格式 如图所示
将Unicode编码转为 二进制
然后从后往前
依次填充
填满为止
这样就得到了 汉字 ” 一 “ 的UTF-8编码
它的 二进制 十进制 十六进制 如图所示



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_32

总结:

本节介绍了
Unicode字符 如何存储在电脑上
其中 UTF-8编码方式 最为常用
编码规则 如下图所示



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_33

以字符为单位读取数据

什么是 以字符为单位读取数据
通过 下图两节的学习



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_34

已经学习了 字符编码 Unicode的基础知识
以及 如何存储 Unicode字符的 UTF-8 编码


java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_35

在这节学习中
你要学习到 什么是 ” 以字符为单位读取数据 “
我们学过
字符是以 字节的形式存储在文件中的
既然 要以字符为单位 进行读取
那么就必须知道 这个字符 占几个字节



若读到的字节 以 ” 0 “ 开头 说明该字符 占一个字节
读完一个字节后 再将读到的字节值转为 Unicode编码
然后再根据编码 在Unicode字符集中 找到对应的字符
得到 U+004D 也就是 字符 ”M“



若读到的字节 以” 110 “ 开头
有两个 连续的 ” 1 “
说明该字符 占 两个 字节
读完两个字节以后 以同样的方法 找到相应的字符
这是一个拉丁文字符



若读到的字节 以” 1110 “ 开头
有三个连续的 “ 1 ”
说明该字符 占 三个 字节
读完三个字节 以后 以同样的方法 找到相应的字符
这个一个中文字符



若读到的字节 以“ 11110 ” 开头
有 四个连续的 “ 1 ”
说明该字符 占 四个字节
读完四个 字节以后
以同样的方法 找到相应的字符
这是一个图形字符



所有的字节已读完
均已找到对应的字符
以上就是“ 以字符为单位读取数据 ” 的意思



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_36

java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_37

总结 :

以字符为单位 读取数据
就是 字符占几个字节
就连续读几个字节
下一讲要学会
如何使用 字符输入流:Reader
进行以字符为单位读取数据



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_38

字符输入流 Reader

如果我们想以 字符为单位读取数据的话
就需要借助 字符输入流 Reader



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_39

和字节输入流 一样
字符输入流 也有一套体系
共有十个类
名称均以:“ Reader ” 结尾



针对不同的需求
选择合适的类
比较常用的有:
带缓冲区的字符输入流 :BufferedReader
带行号的字符输入流:LineNumberReader
和用于读取文本文件的:
文件字符输入流FileReader



以及 将字节输入流转换为 字符输入流: InputStreamReader



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_40

练习

接下来 :
使用FileReader 编写一个
读取文本文件内容的示例



它一共有五个构造方法
第一个参数
我们在 ” 字节输入流InputStream “ 中介绍过
第二个参数: 指定文件的字符集
现在一般都是 ” UTF-8 “



还不会字符集的
要学习上面的内容 :字符编码学习和UTF-8编码



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_41

示例:
下面是我们读取的文件:
内容为:” abcdefg 二哥一直坚持! “



首先 先声明一个字符输入流:
初始值为null
然后初始化它
传入 你要读取的文本文件路径
创建FileReader 有异常抛出
使用 try-catch 将其捕获
写上 finally 代码块



在finally代码块中
调用 输入流 的close方法 关闭流
在关闭流 之前
先判断一下 流是否为空
当流不为空时
再关闭流
close方法 有异常抛出
使用 try-catch 将其捕获



接下来
我们来编写读取数据的部分
声明一个用于记录每次读到的字符变量
为什么是int类型
在上面学习中我们已经学到了
“ read方法读的是字节 为什么返回 int “
已经解释过了



然后使用while循环
每次读取单个字符 读到 -1 时结束
最后 输出读到的字符


若直接输出 ” c “
得到的只是字节值
想要得到字符
需要将其转为char类型



另外这里不要用 println进行输出
它会一行输出 一个字符
建议使用print 进行输出
这样输出的内容格式 和 原来的文件 是一样的



到此 示例代码编写完成
执行程序
观察执行结果
从执行结果来看
程序输出的内容 和原来的文件一模一样



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_42

每次只读一个字节的效率太低
可以使用字符数组
进行批量读取
依次来提高效率



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_43

总结:

本节介绍了
字符输入流体系
以及如何使用字符输入流
读取文本文件



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_44

以字符为单位写入数据

之前我们写数据都是通过字节写入的
即使你写入的数据是字符
那也要将它们转为 字节
然后再通过字节写入
若是要省去手动 ” 字符转字节 “ 这个环节
直接以字符为单位 写入的话
需要借助 字符输出流 Writer



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_45

在了解 字符输出流之前
先来 了解
” 以字符为单位写入数据 “ 的过程是怎么样的



以 Unicode字符为例
假如我们要写入汉字 ” 一 “
首先找到它的Unicode编码
然后根据Unicode编码所属范围
确定 UTF-8 编码方式



最后将 Unicode编码
根据 UTF-8编码方式转为 二进制
写入文件中



这些就是 ” 以字符为单位写入数据 “ 的过程
虽然它的本质还是通过字节来写入的
但是它省去了 手动字符 转字节这个过程
写程序就更方便了



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_46

字符输出流Writer

我们要学习 什么是字符输出流


java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_47

字符输出流体系
它和字符输入流 Reader一样
也有一套体系
常用的类有四个
第一个是:带缓冲区的 BufferedWriter
第二个是:将字节输出流转换为 字符输出流的OutputStreamWriter
第三个是:以字符为单位将数据输出到文件的 FileWriter
第四个是:打印各种类型数据的打印流 PrintWriter



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输出流_48

接下来
使用 FileWriter 完成一个复制文件的示例
它一共有九个构造方法
虽然看起来比较多
但实际上
我们只用 带 append参数的方法
一共有四个



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_49

第一个参数:
我们在 ” 字节输入流 InputStream “ 中学到过
charset参数 指定文件的字符集
一般为 ” UTF-8 “
我们在 ” 字符编码 Unicode “ 和 ” 中文是如何存储到电脑上的(UTF-8编码) “ 中 学习过



append参数
我们在 ” 追加和覆盖 “ 中学习过
它可以指定写入模式 true为追加 false 为覆盖
建议使用 追加方式
覆盖这种方式要慎用
因为这样很容易误删 文件里的内容
造成不必要的损失



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_java_50

练习

介绍完上面的参数
接下来我们要编写 复制文件 示例
下图为我们要复制的文件
内容为:
” abcdefg 二哥一直坚持! “



首先声明 字符输入输出流
初始值都为 null
然后初始化它们
读取的是源文件
输出的是副本
创建输入输出流时
有异常抛出
使用try-catch 将其捕获
写上finally 代码块



在finally代码块中
调用close方法关闭流
在关闭流之前 先判断流 是否为空
当流不为空时 再关闭流
close方法 有异常抛出
使用try-catch 将其捕获



接下来
我们来编写复制数据的部分
定义一个长度为 100 的字符数组 作为缓冲区
读取数据的时候
直接读到缓冲区里面



输出的时候
直接从缓冲区里面输出
接着
定义一个变量length
用于记录每次已读字符数
当length 等于 -1 时
说明数据已读完



接下来 使用while循环
批量读取字符
读到 -1 时结束
最后使用 writer输出 读到的字符



第一个参数为字符缓冲区
第二个参数 为偏移量 是 0
表示从 缓冲区的第一位 开始输出
第三个参数 为 输出的长度 就是已读到的字符数
读多少 输出多少



到此 示例代码编写完成
执行程序
观察执行结果
从执行结果来看
程序没有报错



文件已经复制完成
副本与源文件内容一摸一样



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_51

总结:

本节介绍了 字符输出流体系
以及如何使用字符输出流
这些方法的功能 如下图所示


java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_52

复制文本文件一行代码搞定

在上个知识点中
我们使用 FileReader 和FileWriter
完成了 复制文本文件的示例
这个示例 还可以更简单一下



读取数据和写入数据部分
可以使用Reader 的transferTo方法
一行代码搞定
方法需传入一个字符输出流 writer
这样就写好了



对比两边的代码
代码量直接减少 90%
提高了开发效率
我们把右边的代码 带入到完整的代码中
执行程序观看



从执行结果来看 程序没有报错
文件也复制成功
副本与源文件内容一模一样



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_输入输出流_53

总结

这一节介绍了
transferTo方法
一行代码搞定复制文本文件



注意 该方法只有字符输入流拥有
字节输入流没有
所以它只能复制文本文件
不能复制其他文件



java 覆盖写入文件 FileOutputStream java文件覆盖与追加_ico_54