目录标题
- 覆盖与追加
-
- 在日常开发中 难免会对文件 进行写入操作 有些时候需要清空原有的内容 写入新内容 或者有些时候需要在原有内容的基础上 接着写
- 这时就需要针对不同情况 使用不同的构造方法 在下面的五个构造方法中 只有带append参数的构造方法 才可以指定写入方式
- 写入的方式有两种 一种是 false 代表覆盖 还有 一种是true 代表追加 除了最后一个构造方法以外 另外两个不带append参数的构造方法 内部都是调用带append参数的构造方法(重点!!!) 其append 默认为 false 即写入方式默认为覆盖
- 在覆盖方式下 只要是创建了输出流 即便你没有写入任何内容 原有内容都将被清空 这一点是非常危险的
- 练习
- 下面我们来验证 覆盖 是不是 如上面所说的 下面这是一段有内容的文件 然后我们仅仅只是创建了输出流 没有写入任何内容 执行程序后
- 打开刚刚的文件 可以看到 文件的内容 已经被清空 由此可见 在日常开发中 覆盖这种方式 要谨慎使用 (重点!!!) 因为这样 很容易误删文件里面的内容 造成不必要的损失
- 反观 追加方式 要安全的多
- 下面演示一下 追加的例子 还是创建一个有内容的文件 这次 我们使用追加方式来创建输出流
- 将 append参数值 设置为 true 然后顺便 追加一些新内容 执行程序后 打开刚刚的文件 可以看到
- 最后总结
- 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 语句中 不要忘记取反 至此 优化后的代码编写完成
- 在日常开发中 我们可以用下面这段代码 来创建未知文件 接下来 我们用新逻辑替换掉上面的逻辑代码 再试试看 重点: 对比两者的不同
- 从执行结果来看: 程序没有报错 不存在的目录 和 文件都已经创建好 内容也写了进去
- 对比两者 总结为什么代码会出错
- 总结:
- 字符编码
-
- 学习字符编码相关知识 有助于我们了解 计算机是如何储存字符的
- 一: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
- 总结:
- 中文是如何存储在电脑上的(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的基础知识 以及 如何存储 Unicode字符的 UTF-8 编码
- 在这节学习中 你要学习到 什么是 ” 以字符为单位读取数据 “ 我们学过 字符是以 字节的形式存储在文件中的 既然 要以字符为单位 进行读取 那么就必须知道 这个字符 占几个字节
- 若读到的字节 以 ” 0 “ 开头 说明该字符 占一个字节 读完一个字节后 再将读到的字节值转为 Unicode编码 然后再根据编码 在Unicode字符集中 找到对应的字符 得到 U+004D 也就是 字符 ”M“
- 若读到的字节 以” 110 “ 开头 有两个 连续的 ” 1 “ 说明该字符 占 两个 字节 读完两个字节以后 以同样的方法 找到相应的字符 这是一个拉丁文字符
- 若读到的字节 以” 1110 “ 开头 有三个连续的 “ 1 ” 说明该字符 占 三个 字节 读完三个字节 以后 以同样的方法 找到相应的字符 这个一个中文字符
- 若读到的字节 以“ 11110 ” 开头 有 四个连续的 “ 1 ” 说明该字符 占 四个字节 读完四个 字节以后 以同样的方法 找到相应的字符 这是一个图形字符
- 所有的字节已读完 均已找到对应的字符 以上就是“ 以字符为单位读取数据 ” 的意思
- 总结 :
- 字符输入流 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 表示从 缓冲区的第一位 开始输出 第三个参数 为 输出的长度 就是已读到的字符数 读多少 输出多少
- 到此 示例代码编写完成 执行程序 观察执行结果 从执行结果来看 程序没有报错
- 文件已经复制完成 副本与源文件内容一摸一样
- 总结:
- 复制文本文件一行代码搞定
- 总结