文章目录
- 2.2.3 综合输入和输出方法进行文件拷贝
- 2.2.4 字节流读取时乱码的问题
- 2.3 字符流的方法概述
- 2.3.1 FileReader方法
- 2.3.2 FileWriter方法
- 2.3.3 小结
- 三、高级IO流
- 3.1 缓冲流
- 3.1.1 字节缓冲流
- 3.1.2 字符缓冲流
- 3.2 转换流
- 3.3 序列化流
- 3.3.1 序列化流
- 3.3.2 反序列化流
- 3.4 打印流
- 3.4.1 字节打印流
- 3.4.2 字符打印流
- 3.5 压缩流
- 3.5.1 解压流
- 3.5.2 压缩流
- 3.5.3 将文件夹转换为压缩包
本文接上篇 进阶12所写,继续学习IO流
2.2.3 综合输入和输出方法进行文件拷贝
public static void main(String[] args) {
/*
* 利用try...catch...finally捕获拷贝文件中代码出现的异常
*/
//1.创建对象
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("D:\\itheima\\movie.mp4");
fos = new FileOutputStream("myio\\copy.mp4");
//2.拷贝
int len;
byte[] bytes = new byte[1024 * 1024 * 5];//5M每次的存取
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) {
e.printStackTrace();
}
}
}
}
- 在代码中做了对异常的基本处理,不再是一般的抛出形式
- 此处不管运行过程中代码是否出现异常都需要资源的释放,固应该将资源释放部分放在finally中执行
- 在JAVA的后期版本中有资源自动释放的接口AutoCloseable,可避免麻烦的处理
2.2.4 字节流读取时乱码的问题
字符集概述
- 计算机中的字符存储单位是字节
- 最简单的字符集是ASCII字符集,每个字符占一个字节
- 高位以0开头
- 早期我国针对汉字和日文韩文推出了GBK字符集
- GBK字符集是完全兼容ASCII字符集的
- 每个字符占两个字节,告位以1开头
- 在Uncode字符集中(万国码),包含了大多国家的文字
- 针对该字符集有多种编码方式,最为常见的就是UTF-8编码方式
- 在Unicode字符集的UTF-8编码时,汉字用三个字节进行存储
由上面的描述不难看出,在文件读取时出现乱码的肯能原因主要有:
- 一次读取一个字节的文件信息,并没有完全读取一个字符
- 在写文件和读文件时的编码方式不相同
由以下的字符串案例进行展示
public static void main(String[] args) throws UnsupportedEncodingException {
/*
以下的字符串函数
Java中编码的方法
public byte[] getBytes() 使用默认方式进行编码
public byte[] getBytes(String charsetName) 使用指定方式进行编码
Java中解码的方法
String(byte[] bytes) 使用默认方式进行解码
String(byte[] bytes, String charsetName) 使用指定方式进行解码
*/
//1.编码
String str = "我爱学JAVA";
byte[] bytes1 = str.getBytes();
System.out.println(Arrays.toString(bytes1));
byte[] bytes2 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));
//2.解码
String str2 = new String(bytes1);
System.out.println(str2);
String str3 = new String(bytes1,"GBK");
System.out.println(str3);
}
2.3 字符流的方法概述
2.3.1 FileReader方法
public static void main(String[] args) throws IOException {
/*
第一步:创建对象
public FileReader(File file) 创建字符输入流关联本地文件
public FileReader(String pathname) 创建字符输入流关联本地文件
第二步:读取数据
public int read() 读取数据,读到末尾返回-1
public int read(char[] buffer) 读取多个数据,读到末尾返回-1
第三步:释放资源
public void close() 释放资源/关流
*/
//1.创建对象并关联本地文件
FileReader fr = new FileReader("myio\\a.txt");
//2.读取数据 read()
//如果遇到中文就会一次读取多个,GBK一次读两个字节,UTF-8一次读三个字节
int ch;
while((ch = fr.read()) != -1){
System.out.print((char)ch);//用类型强制转换可以将十进制数转换成对应的中文字符
}
//3.释放资源
fr.close();
}
运行时的细节:
- read():默认也是一个字节一个字节的读取的,如果遇到中文就会一次读取多个
- 在读取之后,方法的底层还会进行解码并转成十进制,并返回十进制数
这个十进制的数据也表示在字符集上的数字
英文:文件里面二进制数据 0110 0001
read方法进行读取,解码并转成十进制97
中文:文件里面的二进制数据 11100110 10110001 10001001
read方法进行读取,解码并转成十进制27721
对于字符流的read方法在底层中是存在一个缓冲区的
- 当首次运行read方法,系统会读取文件,试图将这个数组类型的缓冲区装满
- 每次read就从缓冲区中取出一个字符的数据
- 当缓冲区全部读完后,由会第二次打开文件将剩余的内容再次读入到缓冲区中
2.3.2 FileWriter方法
FileWriter构造方法
FileWriter成员方法
在fileWriter中也存在着缓冲区每次书写并不是直接写入到文件中而是先写在缓冲区中
2.3.3 小结
字符流不同于字节流在读写的时候是以字符为单位的,所有有必要为他们设置一个缓冲区,将原本的字节信息放入到缓冲区中再进行编码操作在进行下一步的处。
字节流:字节信息–>处理
字符流:字节信息–>编码–>处理
三、高级IO流
高级的IO流是主要是对普通文件流的包装使之具有一定的特性
3.1 缓冲流
在普通IO流的基础上加入缓冲区
3.1.1 字节缓冲流
该类是在基本流的基础上创建的
public static void main(String[] args) throws IOException {
//1.创建缓冲流的对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("myio\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myio\\copy.txt"));
//2.循环读取并写到目的地
int b;
//byte[] buytes= new byte[10]
//while ((b = bis.read(bytes)) != -1) {每次多字节读取
while ((b = bis.read()) != -1) {
bos.write(b);
}
//3.释放资源
bos.close();
bis.close();
}
加快读取速度的原理
每次读取在缓冲区中最多读取8192个字节,通过变量b进行转存,避免了每次读取的磁盘访问,而将操作全部转移至内存中进行。
3.1.2 字符缓冲流
构造方法
同字节缓冲流
字符缓冲流的特殊方法
注意:
- readline方法在读取时不会读入换行符
- newline方法为了实现跨平台书写换行符,因为不同的操作系统文件的换行符不一致
缓冲流总结:
- 缓冲流在输出流中要实现续写,应该将续写布尔值写在被包装类中
- 字节缓冲流与字符缓冲流的缓冲区类型和大小不一致,一个是字节(byte)为单位,一个是字符(char)为单位
3.2 转换流
是字节流和字符流之间的转换桥梁,可以让字节流在存储时具有字符流的特性(根据字符集不同一次读取多个字节,读取数据时不会乱码)
注意:在命名时前半部分是字节流命名,后半部分是字符流命名
且转换流本身就是一种字符流,也可以使用字符流的方法
文件类型转换代码示例
public static void main(String[] args) throws IOException {
/*
将本地文件中的GBK文件,转成UTF-8
*/
///1.JDK11以前的方案
InputStreamReader isr = new InputStreamReader(new FileInputStream("myio\\b.txt"),"GBK");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myio\\d.txt"),"UTF-8");
int b;
while((b = isr.read()) != -1){
osw.write(b);
}
osw.close();
isr.close();
//2.替代方案
FileReader fr = new FileReader("myio\\b.txt", Charset.forName("GBK"));
FileWriter fw = new FileWriter("myio\\e.txt",Charset.forName("UTF-8"));
int b;
while ((b = fr.read()) != -1){
fw.write(b);
}
fw.close();
fr.close();
}
public static void main(String[] args) throws IOException {
/*
利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
//1.字节流在读取中文的时候,是会出现乱码的,但是字符流可以搞定
//2.字节流里面是没有读一整行的方法的,只有字符缓冲流才能搞定
*/
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("myio\\a.txt")));
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
}
总结:当字节流中需要使用到字符流的方法时需要引入转换流作为中间的桥梁
3.3 序列化流
序列化流为了将对象存储在文件当中,防止恶意的修改等事件的发生
它是字节流的高级流
3.3.1 序列化流
public static void main(String[] args) throws IOException {
/*
构造方法:
public ObjectOutputStream(OutputStream out) 把基本流变成高级流
成员方法:
public final void writeObject(Object obj) 把对象序列化(写出)到文件中去
*/
//1.创建对象
Student stu = new Student("zhangsan",23);
//2.创建序列化流的对象/对象操作输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
//3.写出数据
oos.writeObject(stu);
//4.释放资源
oos.close();
}
注意:序列化存储对象之前要将对象实现Serializable接口
3.3.2 反序列化流
public static void main(String[] args) throws IOException, ClassNotFoundException {
/*
构造方法:
public ObjectInputStream(InputStream out) 把基本流变成高级流
成员方法:
public Object readObject() 把序列化到本地文件中的对象,读取到程序中来
*/
//1.创建反序列化流的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myio\\a.txt"));
//2.读取数据
Student o = (Student) ois.readObject();
//3.打印对象
System.out.println(o);
//4.释放资源
ois.close();
}
注意:如果多个对象需要读取,每次ois.readObject()读取一个对象
ArrayList<Student> list =(ArryList<Student>) ois.readObject();
for(Student stu : list){
System.out.println(stu);
}
3.4 打印流
打印流不能读只能写,并且可以支持数据的原样写出
3.4.1 字节打印流
基本的构造方法
加上自动刷新可以边打印边查看
成员方法
数据原样写出:是指数据在写入文件时不会将数据转为对应的ASCII写入文件,而是将形参中传递的变量原封不动地直接写入文件
代码举例
public static void main(String[] args) throws FileNotFoundException {
//1.创建字节打印流的对象
PrintStream ps = new PrintStream(new FileOutputStream("进阶13附加//a.txt"), true, Charset.forName("UTF-8"));
//2.写出数据
ps.println(97);//写出 + 自动刷新 + 自动换行 直接写入97
ps.print(true); //直接没写入true
ps.println();
ps.printf("%s爱上了%s","阿珍","阿强");
//3.释放资源
ps.close();
}
3.4.2 字符打印流
构造方法
常用第三个构造方法
成员方法
与字节打印流的成员方法一致
在平时我们最常用的System.out.println()就是一种打印流
System.out就是获取打印流对象,只不过这个对象由虚拟机自动初始化
println便是打印流方法,将传入的参数打印在终端界面上
在使用后一般不会关闭该系统打印流(标准打印流),否则需要重启虚拟机以再次开启该流
3.5 压缩流
压缩流是字节流的子类解压的过程是读,压缩的过程是写
3.5.1 解压流
压缩包中每个文件是一个zipentry对象,解压时就是将该对象转化为原本的类型并存在一个指定的文件夹中
public static void unzip(File src,File dest) throws IOException {
//解压的本质:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地中
//创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
//要先获取到压缩包里面的每一个zipentry对象
//用压缩流来获取压缩包中的对象,并将它转为原文件
ZipEntry entry;
while((entry = zip.getNextEntry()) != null){
System.out.println(entry);//entry对象中不仅包含着当前的文件名称,还有它完整的路径,如aaa/bbb.txt
if(entry.isDirectory()){
//文件夹:需要在目的地dest处创建一个同样的文件夹
File file = new File(dest,entry.toString());
file.mkdirs();
}else{
//文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
FileOutputStream fos = new FileOutputStream(new File(dest,entry.toString()));
int b;
while((b = zip.read()) != -1){
//写到目的地
fos.write(b);
}
fos.close();
//表示在压缩包中的一个文件处理完毕了。
zip.closeEntry();
}
}
zip.close();
}
3.5.2 压缩流
public static void toZip(File src,File dest) throws IOException {
//1.创建压缩流关联压缩包,在其内部中为一个压缩包的文件路径
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest,"a.zip")));
//2.创建压缩包中的内容->创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
//参数:压缩包里面的路径
ZipEntry entry = new ZipEntry("aaa\\bbb\\a.txt");
//3.把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
//4.把src文件中的数据写到压缩包的对象中
//创建输入流读取源文件中字节,用来向内写入数据
FileInputStream fis = new FileInputStream(src);
int b;
while((b = fis.read()) != -1){
zos.write(b);//向entry中写入数据
}
zos.closeEntry();
zos.close();
}
总结:该过程整体分为两步
- 首先是获取压缩包的相关流
- 创建压缩包对象用来接收或向压缩包输入文件
3.5.3 将文件夹转换为压缩包
public static void main(String[] args) throws IOException {
//1.创建File对象表示要压缩的文件夹
File src = new File("D:\\aaa");
//2.创建File对象表示压缩包放在哪里(压缩包的父级路径)
File destParent = src.getParentFile();//D:\\
//3.创建File对象表示压缩包的路径
File dest = new File(destParent,src.getName() + ".zip");
//4.创建压缩流关联压缩包
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));
//5.获取src里面的每一个文件,变成ZipEntry对象,放入到压缩包当中
toZip(src,zos,src.getName());//aaa
//6.释放资源
zos.close();
}
public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
//1.进入src文件夹
File[] files = src.listFiles();
//2.遍历数组
for (File file : files) {
if(file.isFile()){
//3.判断-文件,变成ZipEntry对象,放入到压缩包当中
//此处不能用files.toString()的方法,该方法获取的是绝对路径
//故采用文件名逐级增加的方式
ZipEntry entry = new ZipEntry(name + "\\" + file.getName());//aaa\\no1\\a.txt
zos.putNextEntry(entry);
//读取文件中的数据,写到压缩包
FileInputStream fis = new FileInputStream(file);
int b;
while((b = fis.read()) != -1){
zos.write(b);
}
fis.close();
zos.closeEntry();
}else{
//4.判断-文件夹,递归
toZip(file,zos,name + "\\" + file.getName());
// no1 aaa \\ no1
}
}
}