书接上回:
异常完整形态try...catch...
引入finally语句块:finally里面的代码一定被执行,除非虚拟机停止。因此释放资源之类的代码非常适合写在finally语句块内。
但在关闭前也要判断是否已初始化(是否为null)。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class IODemo1 {
public static void main(String[] args) {
//1.创建对象
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D:\\itheima\\b.mp4");
fos = new FileOutputStream("myio\\copy.mp4");
//2.拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
//e.printstackTrace();
} finally {
//3.释放资源
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
Autocloseable接口
实现Autocloseable的IO类在特定的情况下,可以自动释放资源。
JDK7时的写法:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemoJDK7 {
public static void main(String[] args) {
/*
JDK7:IO流中捕获异常的写法
try后面的小括号中写创建对象的代码,
注意:只有实现了AutoCloseable接口的类,才能在小括号中创建对象。try(){
}catch(){
}
*/
// 1.创建对象
try (FileInputStream fis = new FileInputStream("D:\\itheima\\movie.mp4");
FileOutputStream fos = new FileOutputStream("myio\\copy.mp4")) { // 对象之间用分号;隔开
//2.拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
JDK9时的写法:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemoJDK9 {
public static void main(String[] args) throws FileNotFoundException {
/*
JDK9的写法
*/
// 1.创建对象
FileInputStream fis = new FileInputStream("D:\\itheima\\movie.mp4");
FileOutputStream fos = new FileOutputStream("myio\\copy.mp4");
try (fis; fos) { // 对象之间用分号;隔开
//2.拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5];
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符集
计算机的存储规则(英文字符)
常见字符集介绍
a.GB2312字符集:1980年发布,1981年5月1日实施的简体中文汉字编码国家标准。收录7445个图形字符,其中包括6763个简体汉字
b.BIG5字符集:台湾地区繁体中文标准字符集,共收录13053个中文字,1984年实施。
c.GBK字符集:2000年3月17日发布,收录21003个汉字。
包含国家标准GB13000-1中的全部中、日、韩汉字,和BIG5编码中的所有汉字。windows系统默认使用的就是GBK,系统显示:ANSI。
d.Unicode字符集:国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。
计算机的存储规则(GBK中文字符)
规则1:汉字两个字节存储
规则2:高位字节二进制一定以1开头,转成十进制之后是一个负数。
字符集小结
1.在计算机中,任意数据都是以二进制的形式来存储的
2.计算机中最小的存储单元是一个字节
3.ASCI字符集中,一个英文占一个字节
4.简体中文版Windows,默认使用GBK字符集
5.GBK字符集完全兼容ASCI字符集
一个英文占一个字节,二进制第一位是0
一个中文占两个字节,二进制高位字节的第一位是1
Unicode字符集
Unicode:万国码
研发方:统一码联盟
总部位置:美国加州
研发时间:1990年
发布时间:1994年发布1.0版本,期间不断添加新的文字,最新的版本是2022年9月13日发布的15.0版本。
联盟组成:世界各地主要的电脑制造商、软件开发商、数据库开发商、政府部门、研究机构、国际机构、及个人组成
UTF-16编码规则:用2~4个字节保存
Unicode Transfer Format
UTF-32编码规则:固定使用四个字节保存
UTF-8编码规则:用1~4个字节保存
在UTF-8编码下,英文使用1个字节保存,中文使用3个字节保存。
注意UTF-8并不是字符集,而是Unicode的一种编码方式。
Unicode字符集的UTF-8编码格式
一个英文占一个字节,二进制第一位是0,转成十进制是正数
一个中文占三个字节,二进制第一位是1,第一个字节转成十进制是负数。
Java中编码的方法
String类中的方法 | 说明 |
public byte[] getBytes() | 使用默认方式进行编码 |
public byte[] getBytes(String charsetName) | 使用指定方式进行编码 |
Java中解码的方法
String类中的方法 | 说明 |
String(byte[] bytes) | 使用默认方式进行解码 |
String(byte[] bytes, String charsetName) | 使用指定方式进行解码 |
代码如下:
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
public class CharsetDemo1 {
public static void main(String[] args) throws UnsupportedEncodingException {
// 1.编码
String str = "你好啊~";
// 使用默认字符集编码
byte[] bytesDefault = str.getBytes();
System.out.println(Arrays.toString(bytesDefault));
// [-28, -67, -96, -27, -91, -67, -27, -107, -118, 126]
// 使用GBK字符集编码
byte[] bytesGBK = str.getBytes("GBK");
System.out.println(Arrays.toString(bytesGBK));
// [-60, -29, -70, -61, -80, -95, 126]
// 2.解码
String str2 = new String(bytesDefault); // 使用默认编码方式解码
System.out.println(str2); // 你好啊~
String str3 = new String(bytesDefault, "GBK");
System.out.println(str3); // 浣濂藉~
}
}
字符流
字符流的底层其实就是字节流
字符流=字节流+字符集
特点
输入流:一次读一个字节,遇到中文时,一次读多个字节
使用场景
对于纯文本文件进行读写操作
FileReader类
①创建字符输入流对象
构造方法 | 说明 |
public FileReader(File file) | 创建字符输入流关联本地文件 |
public FileReader(String pathname) | 创建字符输入流关联本地文件 |
如果文件不存在,则直接报错。
②读取数据
成员方法 | 说明 |
public int read() | 读取数据,读到末尾返回-1 |
public int read(char[] buffer) | 读取多个数据,读到末尾返回-1 |
细节1:按字节进行读取。遇到中文,一次读多个字节,读取后解码,返回一个整数。
细节2:读到文件末尾了,read方法返回-1。
③释放资源
成员方法 | 说明 |
public int close() | 释放资源/关流 |
一次读取一个字符:
import java.io.FileReader;
import java.io.IOException;
public class FileReadDemo1 {
public static void main(String[] args) throws IOException {
// 文件
FileReader fr = new FileReader("text/a.txt");
// read()细节:
// 1.read():默认也是一个字节一个字节的读取的,如果遇到中文就会一次读取多个
// 2.在读取之后,方法的底层还会进行解码并转成十进制。
// 最终把这个十进制作为返回值
// 这个十进制的数据也表示在字符集上的数字
// 英文:文件里面二进制数据01100001
// read方法进行读取,解码并转成十进制97
// 中文:文件里面的二进制数据111001101011000110001001
// read方法进行读取,解码并转成十进制27721
// 我想看到中文汉字,就是把这些十进制数据,再进行强转就可以了
// 缓存区,一次读取10个字符
char[] buffer = new char[20];
// 循环读取
for (int r = fr.read(buffer); r != -1; r = fr.read(buffer)){
System.out.print(new String(buffer, 0, r));
}
// 关闭资源
fr.close();
}
}
读取到缓冲数组中:
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FileReadDemo2 {
public static void main(String[] args) throws IOException {
// 文件
FileReader fr = new FileReader("text/a.txt", StandardCharsets.UTF_8);
// read(chars):读取数据,解码,强转三步合并了,
// 把强转之后的字符放到数组当中
// 空参的read +强转类型转换
for (int r = fr.read(); r != -1; r = fr.read()){
System.out.println((char) r);
}
// 关闭资源
fr.close();
}
}
FileWriter类
构造方法
构造方法 | 说明 |
public Filewriter(File file) | 创建字符输出流关联本地文件 |
public Filewriter(String pathname) | 创建字符输出流关联本地文件 |
public Filewriter(File file,boolean append) | 创建字符输出流关联本地文件,续写 |
public Filewriter(String pathname, boolean append) | 创建字符输出流关联本地文件,续写 |
成员方法
成员方法 | 说明 |
void write(int c) | 写出一个字符 |
void write(String str) | 写出一个字符串 |
void write(String str,int off,int len) | 写出一个字符串的一部分 |
void write(char[] cbuf) | 写出一个字符数组 |
void write(char[] cbuf,int off,int len) | 写出字符数组的一部分 |
FileWriter书写细节
①创建字符输出流对象
细节1:参数是字符串表示的路径或者File对象都是可以的
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
细节3:如果文件已经存在,则会清空文件,如果不想清空可以打开续写开关
②写数据
细节:如果write方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符
③释放资源
细节:每次使用完流之后都要释放资源
import java.io.FileWriter;
import java.io.IOException;
public class WriterDemo1 {
public static void main(String[] args) throws IOException {
/* 字节流输出方式
// 1 文件输出流
FileOutputStream fos = new FileOutputStream("text/b.txt", true);
// 2 获取数组
byte[] bytes = "\n续写内容".getBytes();
fos.write(bytes);
// 3 关闭资源
fos.close();
*/
// 1.创建文件写出对象
FileWriter fw = new FileWriter("text/b.txt", true);
// 2.写出字符
//fw.write(97); // 输出一个字符a
//fw.write("\nFileWriter续写的内容"); // 输出字符串
fw.write("\nFileWriter续写的内容", 0, 11); // 输出部分String内容
// 3.续写内容
fw.close();
}
}
字符流原理解析
字符输入流原理
①创建字符输入流对象
底层:关联文件,并创建缓冲区(长度为8192的字节数组)
②读取数据
底层:
1.判断缓冲区中是否有数据可以读取
2.缓冲区没有数据:就从文件中获取数据,装到缓冲区中,每次尽可能装满缓冲区如果文件中也没有数据了,返回-1
3.缓冲区有数据:就从缓冲区中读取。
- 空参的read方法:一次读取一个字节,遇到中文一次读多个字节,把字节解码并转成十进制返回
- 有参的read方法:把读取字节,解码,强转三步合并了,强转之后的字符放到数组中
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CharStreamDemo1 {
public static void main(String[] args) throws IOException {
// 读取器
FileReader fr = new FileReader("files\\a.txt");
fr.read();
// 写出器
// 本Writer会清空文件的内容
FileWriter fw = new FileWriter("files\\a.txt");
// 这里使用fr进行读取
// 是否还会取到数据?
// 答案是可以,因为缓冲区中有8192字节的数据。
// 当缓冲区中数据读取完毕后,无法再读取文件中的内容了。
int ch;
while ((ch = fr.read()) != -1){
System.out.println((char) ch);
}
// 关闭资源
fr.close();
fw.close();
}
}
字符输出流原理
字符输出流也会创建一个8192字节的缓冲区,输出数据先是往缓冲区内写入数据,有3种情况下会将缓冲区中的数据输出到文件中:
- 缓冲区满
- 调用flush方法
- 调用close方法关闭流时
flush和close方法
成员方法 | 说明 |
public void flush() | 将缓冲区中的数据,刷新到本地文件中 |
public void close() | 释放资源/关流 |
- flush刷新:刷新之后,还可以继续往文件中写出数据
- close关流:断开通道,无法再往文件中写出数据
字节流和字符流的使用场景
字节流
拷贝任意类型的文件
字符流
读取纯文本文件中的数据
往纯文本文件中写出数据
练习
拷贝文件夹
需求:拷贝一个文件夹,考虑子文件夹
递归:如果文件就使用FileInputStream和FileOutputStream进行拷贝,如果是文件就在目标地址创建一个同名文件夹。
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DirCopy {
public static void main(String[] args) throws IOException {
File sourPath = new File("files\\dirA");
File destPath = new File("files\\dirB");
dirCopy(sourPath, destPath);
//copydir(sourPath, destPath);
}
/**
* 拷贝文件夹
* @param sourDir 源文件夹
* @param destDir 目标地址
*/
public static void dirCopy(File sourDir, File destDir) throws IOException {
// 1.输入是文件,则直接复制
if (sourDir.isFile()){
// 1-1.创建输入流和输出流
// 创建输入流
FileInputStream fis = new FileInputStream(sourDir);
// 创建输出流
FileOutputStream fos = new FileOutputStream(destDir);
// 创建读取数据的缓冲区,8192B大小
byte[] buffer = new byte[8192];
// 复制文件
int readFlag;
while ((readFlag = fis.read(buffer)) != -1){
fos.write(buffer, 0, readFlag);
}
// 关闭资源
fos.close();
fis.close();
}else {
// 2.输入是文件夹,则目标位置创建同名文件夹,再递归
// 创建同名目标文件夹
destDir.mkdir();
// 读取源文件夹中的文件
File[] files = sourDir.listFiles();
for (File file : files) {
// 递归
// 参数file: 源文件夹中的文件或文件夹
// 参数new File(destDir.getAbsolutePath(), file.getName()):表示目标文件夹或文件名
// file.getName()表示取得当前文件夹或文件名,与源文件夹中的目录结构保持一致。
// 或者也可以使用dirCopy(file, new File(destDir, file.getName()));
// 更加简洁
dirCopy(file, new File(destDir.getAbsolutePath(), file.getName()));
}
}
}
/**
* 此方法有bug,如果传入的参数是文件,listFiles将返回null,则无法进入for循环
* @param src
* @param dest
* @throws IOException
*/
private static void copydir(File src,File dest)throws IOException {
dest.mkdirs();
//递归
//1.进入数据源
File[]files =src.listFiles();
//2.遍历数组
for(File file:files) {
if (file.isFile()) {
//3.判断文件,拷贝
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest, file.getName()));
byte[] bytes = new byte[1024];
int len;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
fos.close();
fis.close();
}
}else{
//4.判断文件夹,递归
copydir(file, new File(dest, file.getName()));
}
}
}
}
文件加密
为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理。
加密原理:对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中。
解密原理:读取加密之后的文件,按照加密的规则反向操作,变成原始文件。
原理:一个数字与另一个数字进行两次异或运算,结果就是数字本身。
加密程序:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Crypto {
public static void main(String[] args) throws IOException {
// 读取的文件
//File in = new File("files/info.txt");
// 源文件
FileInputStream fis = new FileInputStream("files/info.txt");
FileOutputStream fos = new FileOutputStream("files/infoCryto.txt");
// 密钥。key随便写一个数字即可
int key = 284833;
int ch;
while ((ch = fis.read()) != -1){
fos.write(ch ^ key);
}
// 写出
fos.flush();
// 关闭资源
fos.close();
fis.close();
}
}
解密程序:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DeCrypto {
public static void main(String[] args) throws IOException {
// 源文件
FileInputStream fis = new FileInputStream("files/infoCryto.txt");
// 解密后的文件
FileOutputStream fos = new FileOutputStream("files/infoDecryto.txt");
// 密钥
int key = 284833;
int ch;
while ((ch = fis.read()) != -1) {
fos.write(ch ^ key);
}
// 关闭资源
fis.close();
}
}
加解密效果:
原始文件:
加密后文件:
解密后的文件:
修改文件中的数据
文本文件中有以下的数据:
2-1-9-4-7-8
将文件中的数据进行排序,变成以下的数据:
1-2-4-7-8-9