IO流详解

IO流

存储和读取数据的解决方案,用于读写数据(本地文件、网络)
  • IO流按照流向可以分类为

  • 输出流:程序---->文件

  • 输入流:文件---->程序

  • IO流按照操作文件的类型可以分类为

  • 字节流:可以操作所有类型文件

  • 字符流:只能操作纯文本文件(纯文本文件就是用Window系统自带的记事本打开并且能读懂的文件)

  • 字节流和字符流的使用场景

  • 字节流:拷贝任意类型的文件

  • 字符流:

  1. 读取纯文本文件中的数据

  1. 往纯文本文件中写出数据

字节流

基本流

FileOutputStream
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中
  • 书写步骤

  1. 创建字节输出流对象

  • 参数是字符串表示的路径或者是File对象都是可以的

  • 如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的

  • 如果文件已经存在,则会清空文件

  1. 写数据

  • write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符

  1. 释放资源

  • 每次使用完流之后都要释放资源

FileOutputStream写数据的3种方式

方法名称

说明

void write(int b)

一次写一个字节数据

void write(byte[] b)

一次写一个字节数组数据

void write(byte[] b,int off,int len)

一次写一个字节数组的部分数据

FileOutputStream fos = new FileOutputStream("src\\a.txt");
String s ="abcd";
byte[] bytes = s.getBytes();
fos.write(bytes);
fos.close();

off表示起始索引,len表示字节个数

  • 换行写

再次写出一个换行符

Window: \r\n

Linux: \n

Mac: \r

细节:在windows操作系统中,java对回车换行进行了优化,虽然完整的是\r\n,但是我们写其中一个\r或者\n java也可以实现换行,因为java底层会补全

 FileOutputStream fos = new FileOutputStream("src\\a.txt");
String s = "abcd";
byte[] bytes = s.getBytes();
fos.write(bytes);

fos.write("\r\n".getBytes());

String s1 = "111";
byte[] bytes1 = s1.getBytes();
fos.write(bytes1);
fos.close();
  • 续写

如果想要续写,打开续写开关即可,即创建对象的第二个参数,默认是false,表示关闭续写,此时创建对象会清空文件,手动传递true表示打开续写,此时创建对象不会清空文件

FileInputStream
操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中
  • 书写步骤

  1. 创建字节输入流对象

  • 如果文件不存在,就直接报错

  1. 读数据

  • 一次读一个字节,读出来的是数据在ASCII上对应的数字

  • 读到文件末尾了,read方法返回-1

  1. 释放资源

  • 每次使用完流必须要释放资源

FileInputStream循环读取

FileInputStream fis = new FileInputStream("src\\a.txt");
int b;
while ((b = fis.read()) != -1){
    System.out.print((char) b);
}
fis.close();
}

FileInputStream一次读多个字节

FileInputStream fis = new FileInputStream("src\\a.txt");
byte[] bybes = new byte[2];

int len = fis.read(bybes);
System.out.println(new String(bybes,0,len));

int len1 = fis.read(bybes);
System.out.println(new String(bybes,0,len1));

int len2 = fis.read(bybes);
System.out.println(new String(bybes,0,len2));
fis.close();
文件拷贝
FileInputStream fis = new FileInputStream("src\\a.txt");
FileOutputStream fos = new FileOutputStream("src\\a.txt");
        
int len;
byte[] b = new byte[1024 * 1024 * 5];
while ((len = fis.read(b)) != -1){
    fos.write(b,0,len);
}
        
fos.close();
fis.close();

缓冲流

原理:底层自带了长度为8192的缓冲区提高性能
把基本流包装成高级流,提高读写数据的性能
  • 用缓冲流拷贝文件

BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src\\a.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\b.txt"));

int len;
byte[] bytes = new byte[2];
while ((len = bis.read(bytes)) != -1){
    bos.write(bytes,0,len);
}

bos.close();
bis.close();
  • 缓冲流的读写原理

序列化流(对象操作输出流)

将java中的对象写到本地文件中,并且看不懂
  • 使用对象操作输出流将对象保存到文件时会出现NotSerializableException异常

解决方案:需要让JavaBean类实现Serializable接口,该接口中没有方法(标志性接口)

反序列化流(对象操作输入流)

可以把序列化到本地文件中的对象,读取到程序中来
  • 序列化流写到文件中的数据是不能修改的,一旦修改就无法再次读回来

  • 序列化对象后,修改了Javabean类,再次序列化会有问题,会抛出InvalidClassException异常

解决方案:给Javabean类添加serialVersionUID(序列号)

  • 如果一个对象中的某个成员变量的值不想被序列化,给该成员变量添加transient关键字修饰,该关键字标记的成员变量不参与序列化过程

打印流(PrintStream)

打印流只操作文件目的地,不操作数据源
字节流底层没有缓冲区,开不开自动刷新都一样

成员方法

说明

public void write(int b)

常规方法:规则和之前一样,将指定的字节写出

public void println(XXX xxx)

特有方法:打印任意数据,自动刷新,自动换行

public void print(XXX xxx)

特有方法:打印任意数据,不换行

public void printf(String format,Object... args)

特有方法:带有占位符的打印语句,不换行

解压缩流

ZipInputStream
解压本质:把每一个ZipEntry按照层级拷贝到本地另一个文件夹中
压缩包里面的每一个文件或文件夹都是一个ZipEntry对象
public static void main(String[] args) throws IOException {
        //创建一个File表示要解压的压缩包
        File src = new File("D:\\aaa.zip");
        //创建一个File表示解压的目的地
        File dest = new File("D:\\aaa\\");

        unzip(src,dest);
    }

    //定义一个方法用来解压
    public static void unzip(File src,File dest) throws IOException {
        //创建一个解压缩流用来读取压缩包中的数据
        ZipInputStream zip = new ZipInputStream(new FileInputStream(src), Charset.forName("GBK"));

        dest.mkdirs();
        //表示当前在压缩包中获取到的文件或者文件夹
        ZipEntry entry;
        while ((entry = zip.getNextEntry()) != null){
            //文件夹:需要在目的地dest处创建一个同样的文件夹
            if (entry.isDirectory()){
                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();
    }

压缩流

ZipOutputStream
压缩本质:把每一个(文件/文件夹)看成ZipEntry对象放到压缩包中
public static void main(String[] args) throws IOException {
        //创建File对象表示要压缩的文件
        File src = new File("D:\\aaa");
        //创建File对象表示压缩包的位置
        File dest = new File(src.getParentFile(),src.getName() + ".zip");

        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));

        toZip(src,zos,src.getName());

    }

    public static void toZip(File src,ZipOutputStream zos,String name) throws IOException {
        File[] files = src.listFiles();
        for (File file : files) {
            if (file.isFile()){
                ZipEntry zipEntry = new ZipEntry(name + "\\" + file.getName());
                zos.putNextEntry(zipEntry);
                //读取文件中的数据,写到压缩包
                FileInputStream fis = new FileInputStream(file);
                int b;
                while ((b = fis.read()) != -1){
                    zos.write(b);
                }
                fis.close();
                zos.closeEntry();
            }else {
                toZip(file,zos,name + "\\" +file.getName());
            }
        }
    }

字符集

计算机的存储规则(GBK)

英文
  • 英文用一个字节存储,完全兼容ASCII

  • GBK英文编码规则:不足8位,前面补0

汉字
  • 汉字两个字节存储

  • 高位字节二进制一定以1开头(区分英文和中文)

计算机的存储规则(Unicode)

UTF-8编码规则:用1~4个字节保存
黑色部分用查询到的数字的二进制形式补全
英文1个字节,中文3个字节

乱码

  • 乱码出现的原因

  • 读取数据时未读完整个汉字

  • 编码和解码时的方式不统一

  • 如何不产生乱码

  • 不要使用字节流读取文本文件

  • 编码解码时使用同一个码表,同一种编码方式

Java中编码的方法

String类中的方法

说明

public byte[] getByte()

使用默认方式进行编码

public byte[] getByte(String charsetName)

使用指定方式进行编码

Java中解码的方法

String类中的方法

说明

String(byte[] bytes)

使用默认方式进行编码

String(byte[] bytes,String charsetName)

使用指定方式进行编码

字符流

Reader和Writer是一个抽象类,FileReader和FileWriter分别是其子类
字符流的底层其实就是字节流
字符流 = 字节流 + 字符集
特点:
输入流:一次读一个字节,遇到中文时,一次读多个字节
输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中

基本流

FileReader
  • read()细节

  1. 默认也是一个字节一个字节的读取的,如果遇到中文就会一次读取多个

  1. 在读取后,方法的底层还会进行解码并转成十进制,最终这个十进制作为返回值,这个十进制数据也表示在字符集上的数字

  • 英文:文件里面二进制数据 0110 0001

read()方法进行读取,解码并转成十进制97

  • 中文:文件里面的二进制数据 11100110 10110001 10001001

read()方法进行读取,解码并转成十进制27721

FileReader fr = new FileReader("src\\a.txt");
int ch;
while ((ch = fr.read()) != -1){
    System.out.print((char) ch);
}

fr.close();
FileWriter

成员方法

说明

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)

写出一个字符数组的一部分

缓冲流

  • 字符缓冲流特有方法

输入流方法

说明

public String readLine()

读取一行数据,如果没有数据可读了,会返回null

输出流方法

说明

public void newLine()

跨平台的换行

BufferedReader br = new BufferedReader(new FileReader("src\\a.txt"));
String line;
while ((line = br.readLine()) != null){
    System.out.println(line);
}
br.close();

BufferedWriter bw = new BufferedWriter(new FileWriter("src\\b.txt"));
bw.write("aaa");
bw.newLine();
bw.write("bbb");
bw.close();

转换流

是字节流与字符流之间的桥梁
转换流本质上就是字符流
作用:字节流想要使用字符流中的方法

打印流(PrintWriter)

字符流底层有缓冲区,想要自动刷新需要开启
其他和字节打印流基本一致

案例

案例一

拷贝一个文件夹,考虑子文件夹
public static void main(String[] args) throws IOException {
    File src = new File("D:\\aaa\\bbb");
    File dest = new File("D:\\aaa\\ddd");

    copyDir(src,dest);
}

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()){
            //判断文件,拷贝
            FileOutputStream fos = new FileOutputStream(new File(dest,file.getName()));
            FileInputStream fis = new FileInputStream(file);
            byte[] bytes = new byte[1024];
                int len;
                while ((len = fis.read(bytes)) != -1){
                    fos.write(bytes,0,len);
                }
            fis.close();
            fos.close();
        }else {
            //判断文件夹,递归
            copyDir(file,new File(dest,file.getName()));
        }
    }
}

案例二

为了保证文件的安全性,就需要对原始文件进行加密存储,再使用的时候再对其进行解密处理
加密原理:对原始文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中
解密原理:读取加密之后的文件,按照加密的规则反向操作,变成原始文件
//加密处理
FileInputStream fis = new FileInputStream("D:\\aaa\\1.jpg");
FileOutputStream fos = new FileOutputStream("D:\\aaa\\ency.jpg");
int b;
while ((b = fis.read()) != -1){
    fos.write(b ^ 10);
}
fos.close();
fis.close();

//解密处理
FileInputStream fis1 = new FileInputStream("D:\\ency.jpg");
FileOutputStream fos1 = new FileOutputStream("D:\\redu.jpg");
int b1;
while ((b1 = fis1.read()) != -1){
    fos1.write(b1 ^ 10);
}
fos1.close();
fis1.close();

案例三

文件中有以下的数据:2-1-8-9-3-4
将文件中的数据进行排序,变成一下的数据:1-2-3-4-8-9
//读取数据
FileReader fr =new FileReader("src\\a.txt");
StringBuffer sb = new StringBuffer();
int ch;
while ((ch = fr.read()) != -1){
    sb.append((char)ch);
}
fr.close();

//排序
Integer[] arr = Arrays.stream(sb.toString().split("-"))
    .map(Integer::parseInt)
    .sorted()
    .toArray(Integer[]::new);
        
//写出
FileWriter fw = new FileWriter("src\\a.txt");
String s = Arrays.toString(arr).replace(", ", "-");
String result = s.substring(1, s.length() - 1);
fw.write(result);
fw.close();
  • 细节:

  • 文件中的数据不要换行

  • 文件以UTF-8形式存储不要带bom头

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值