IO快速入门

本文详细介绍了Java中的IO流、字节流、字符流以及它们的区别,包括字节流处理二进制数据、字符流处理文本字符,以及字符编码如UTF-8和GBK的应用。此外,还讲解了缓冲流、转换流、序列化流、打印流和压缩解压缩流的概念和使用方法。
摘要由CSDN通过智能技术生成

目录

基本概念

简单使用

通过字节流案例入门

1、 读取文本内容:

2、写入数据到文本中:

3、文件拷贝:

4、文件加密:

5、文件解密:

其他流介绍

字符流

缓冲流(Buffered Streams)

转换流

序列化流

打印流

压缩流和解压缩流


基本概念

IO流是什么及其作用:IO流是读取和存储数据的解决方法。数据可以简单的理解为本地文件,但其实不仅这些,包括文本数据:(txt文件)、二进制数据(图像、音频、视频)、压缩数据(压缩包)、网络数据、对象数据(序列化)、内存数据等。

IO流的分类:IO流可以分为字节流和字符流。

  •  字节流:字节流以字节为单位处理数据,不关心数据的字符编码,可以处理各种类型的数据。
  • 字符流:字符流以字符为单位处理数据,关注字符编码,适用于处理文本问文件。

字节流和字符流的区别:他们处理数据的方式或者说读取和存储数据的方式不同,一个是以字节为单位,一个是以字符为单位。

扩展字节和字符概念:

在计算机存储规则中,二进制存储时,一个 1或者 0占 1bit。

 字节基本知识:

  • 字节概念:字节是计算机内存中最小的可寻址数据单元,通常由8位二进制数字组成。它是计算机存储和处理数据的基本单位,可以表示数字、字符、图像、音频等各种信息。
  •   字节比特的单位换算:1字节 == 8比特(bit),即 1个字节通常包含 8位二进制,每位可以是0或1,故1个字节有2^8==256个值。     
  • 其他字节单位换算: 
    • 1 EB = 1024 PB 
    • 1 PB = 1024 TB
    • 1 TB = 1024 GB
    • 1 GB = 1024 MB
    • 1 MB = 1024 KB
    • 1 KB = 1024 BYTE
    • 1 字节(Byte : /baɪt/)= 8 位(Bit : /bɪt/)

字符基本知识:

  • 字符是一种抽象的概念,通常表示人类语言中的文本字符,如字母、数字、标点符号等。字符可以由一个或多个字节来表示,具体取决于字符编码方案。

扩展字符编码概念

常见的字符集有ASCII、Unicode、GBK 、BIG5等字符集。这里重点介绍写代码常用到的Unicode、GBK。

  • ASCII 是最早的字符编码标准之一,在计算机和通信中广泛使用。
  • BIG5 是中国台湾省繁体中文标准字符集
  • GBK
    • 为什么叫GBK:汉族首字母拼接,中文名:国家标准扩展字符集。GBK字符集收录21003个汉字,包含了GB和BIG5字符集。GB字符集是早先的国家标准字符集。
    • GBK字符集中,一个英文占1个字节、一个汉字占2个字节。
      • 上述说过一个字节有256个值,根本存不下所有汉字,所以一个汉字要占2个字节(2^16 = 65,536)。为什么不占更多的字节:主要原因是2个字节足够存储所有的汉字并且为了减少空间的浪费。
  • Unicode
    • Unicode 是一个全球字符集,旨在包括世界上所有语言的字符。
    • Unicode字符集的编码方式
      • UTF-8:一个英文占1个字节、一个汉字占3个字节
      • UTF-16:一个汉字占2个字节
      • UTF-32:一个汉字占4个字节

拓展乱码产生的原因和解决方法:

        Java的字节流是以字节为单位进行读取和写入的,它不关心具体的字节码。当你使用字节流读取包包含中文字符的文本时,由于中文字符经常使用多个字节表示,可能会导致乱码的问题。为了正确读取和处理包含中文字符的文本,你应该使用字符流(Reader/Writer)而不是字节流(InputStream/OutputStream),并指定正确的字符编写码。

  基于上述字节、字符、字符编码方式概念的扩展,不难得出以下结论:

        字符流   ==   字节流  +  字符集            (该结论下文中会有进一步解释) 

主要目的是为了进一步理解和区分字节流与字符流。  在用到IO流时需要考虑到这些。

简单使用

通过字节流案例入门

     

1、 读取文本内容:

 思路:告诉程序需要读哪一个文件,并且以什么样的方式读取,最后将读取到的内容输出出来

    文本内容为(该文本的字符编码方式为UTF-8:记事本打开.txt文件,在右下角可以看到编码方式):
    123456
    随着风 花儿摇曳着

    /**
     *  要求:读写纯文本文件
     *  思路:告诉程序需要读哪一个文件,并且以什么样的方式读取,最后将读取到的内容输出出来
     */
    @Test
    public void test1() throws IOException {
        // 创建File对象关联本地需要读取的文本文件:文件名称.txt
        File file = new File("F:\\demoTemp\\ckdemo\\IOTest\\文件名称.txt");
        // 字节输入流绑定要读取的文件
        FileInputStream fileInputStream = new FileInputStream(file);
        // 定义一个变量临时存放读取的字节
        int temp;
        // 循环判断输入流读取到的每一个字节,如果读到最后循环结束
        while ((temp = fileInputStream.read()) > -1){
            // 将每一字节转换成char字符类型并输出
            System.out.print((char)temp);
        }
    }

    
    输出结果为:
    123456
    éçé£ è±å¿ææ³ç
  •  第一次应该有的疑问
    • 为什么会有乱码?
    • 为什么要转换成 char 类型输出,不转换会输出什么
    • 为什么temp变量要定义成 int 类型
    • 为什么循环结束的判断条件是 > -1
  • 解答
    • 为什么会有乱码
      • 上述案例中是通过字节流读取的文件内容,而字节流是以字节为单位读取的。我们知道 .txt 文件是以UTF-8编码方式存储的,一个汉字占3字节。当字节流在读取时,他一次只会读取到一个字节,这一个字节不能映射成正确的汉字,故此形成乱码。解决方法:通常用字符流读取文本文件。
    • 为什么循环结束的判断条件时 > -1  和 为什么temp要定义成 int 类型
      • 请下载附件(Java API)并搜索FileInputStream类查看方法自行得出答案
        • 关注方法的返回类型 和 方法的输入参数
      • fileInputStream.read()读取的是一个字节,值在0-255之间,返回时将其转换成int类型是为了能够表示文件读取结束,该值是-1。 
    • 为什么要转换成char类型输出
      • 因为字节流读取的是一个字节,是8位二进制数,为了转换成我们看的懂的内容,所以这里强转成 char 类型。
2、写入数据到文本中:

将temp变量中内容写到输出到新建的输出文件.txt文件中。思路:内容是什么,写入到哪一个文件中?怎样将内容转换成字节的方式给字节输出流?

/**
  * 要求:写入数据到文本
  */
@Test
public void test2() throws IOException {
   // 字节输出流
   FileOutputStream fileOutputStream = new FileOutputStream("F:\\输出文件.txt");
   String temp = "想像你失落的唇印 想象你失约的旅行";
    // 转换成字节数组
   byte[] b = temp.getBytes();
   fileOutputStream.write(b);
}

这里使用的是字节流的方式将文字写入文本中。通常情况下用字符流完成该功能。

3、文件拷贝:

复制一份文件。思路:通过字节流将内容读取出来暂存到自己定义的变量中,再通过字节输出流将内容输出到新文件中。

 /**
   * 文件拷贝
   */
 @Test
 public void test3() throws Exception{
    File file1 = new File("F:\\原文件.txt");
    File file2 = new File("F:\\原文件-副本.txt");
    FileInputStream fileInputStream = new FileInputStream(file1);
    FileOutputStream fileOutputStream = new FileOutputStream(file2);
    byte[] b = new byte[1024];
    int length;
    while ((length = fileInputStream.read(b)) > -1){
        // 应为b数组元素有默认值是0,这里保证输出的字节都是读取到的,故通过长度条件。
        fileOutputStream.write(b,0,length);
    }
 }

用字节流将原文件内容拷贝一份为源文件-副本。

4、文件加密:

对原文件中的每一个字节数据进行更改,然后将更改以后的数据存储到新的文件中。

/**
* 加密文件后拷贝
*/
@Test
public void test4() throws Exception {
    File file1 = new File("F:\\demoTemp\\ckdemo\\IOTest\\输出文件.txt");
    File file2 = new File("F:\\demoTemp\\ckdemo\\IOTest\\输出文件-加密.txt");
    FileInputStream fileInputStream = new FileInputStream(file1);
    FileOutputStream fileOutputStream = new FileOutputStream(file2);
    int b;
    while ((b = fileInputStream.read()) > -1){
        b = b +1;
        fileOutputStream.write(b);
    }
}

补充:fileInputStream.read()读取的是一个字节,值在0-255之间,返回时将其转换成int类型是为了能够表示文件读取结束,该值是-1。 

5、文件解密:

读取加密之后的文件,按照加密规则反向操作,变成原始文件。

/**
 * 解密加密后的文件
 */
@Test
public void test5() throws Exception {
    File file1 = new File("F:\\demoTemp\\ckdemo\\IOTest\\输出文件-加密.txt");
    File file2 = new File("F:\\demoTemp\\ckdemo\\IOTest\\输出文件-解密.txt");
    FileInputStream fileInputStream = new FileInputStream(file1);
    FileOutputStream fileOutputStream = new FileOutputStream(file2);
    int b;
    while ((b = fileInputStream.read()) > -1){
        b = b - 1;
        fileOutputStream.write(b);
    }
}

其他流介绍

字符流

字符流的使用方法和字节流基本上是一致的。

字符流的底层是使用了字节流,read()方法默认也一个字节一个字节的读取,但如果遇到中文就会一次读取多个字节,GBK一次会读取2个字节,UTF-8一次会读取3个字节。

 /**
 * 字符流的使用
 */
@Test
public void test6() throws Exception {
    FileReader fileReader = new FileReader("F:\\文件名称.txt");
    FileWriter fileWriter = new FileWriter("F:\\文件名称-字符流.txt", true);
    char[] b = new char[1024];
    int bytesRead;
    while ((bytesRead = fileReader.read(b)) > -1) {
        System.out.println(new String(b, 0, bytesRead));
        fileWriter.write(b, 0, bytesRead);
    }
    fileWriter.close();
    fileReader.close();
}

补充:

fileReader.read():默认也是一个字节一个字节读取,遇到中文读取多个字节,读取之后方法底层还会进行解码并转成十进制。最终把这个十进制作为返回值。

fileReader.read(b)方法:包含了读取数据+解码+强转的步骤。 

字符流底层原理:

创建字符输入流对象时,底层会关联文件并且创建缓冲区(长度为8192的字节数组)

字符流读取数据时,底层会先判断缓冲区中是否有数据可读。如果缓冲区没有数据就会从文件中获取并装到缓冲区中,每次尽可能装满。如果文件也没有数据了就返回-1。如果缓冲区有数据,就从换从区中读取。

字节流是没有缓冲区的。

缓冲流(Buffered Streams)

缓冲流用于提高I/O操作的性能。它们通过在内存中分配空间来减少磁盘或网络I/O的次数,从而加速数据的读取读取和写入。缓冲流通常与基础的字节流或字符流一起使用,以提供更高的数据传输。

缓冲流同样分为字节缓存流和字符缓冲流。

字节缓冲输入流:BufferedInputStream

字节缓冲输出流:BufferedOutputStream

字符缓冲输入流:BufferedReader

字符缓冲输出流:BufferedWriter

缓冲流的使用

/**
 * 缓冲流的使用
 */
@Test
public void test7() throws Exception{
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File("F:\\文件名称.txt")));
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(new File("F:\\文件名称-缓冲输出.txt")));
    byte[] b = new byte[1024];
    int length;
    while ((length = bufferedInputStream.read(b)) > -1){
        bufferedOutputStream.write(b,0,length);
    }
    bufferedInputStream.close();
    bufferedOutputStream.close();
}

上述代码可以看出,字节缓冲流的使用与字符流的使用基本上一致。同理字符流也一样。

转换流

Java的转换流(Conversion Streams)是用于在字节流和字符流之间进行转换的I/O流。它们通常用于处理字符数据和字节数据之间的转换,例如从文件读取字节数据并将其转换为字符数据,或者将字符数据写入文件时进行字节到字符的转换。转换流通常用于处理字符编码和字符集转换的需求。

InputStreamReader:用于将字节输入流转换为字符输入流的转换流
OutputStreamWriter:用于将字符输出流转换为字节输出流的转换流

/**
 * 转换流的使用:用UTF-8字符集读取并输出成IOS_8859_1字符集的文件
 */
@Test
public void test8() throws IOException {
    InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream("F:\\文件名称.txt"),StandardCharsets.UTF_8);
    OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("F:\\文件名称-GBK.txt"), StandardCharsets.ISO_8859_1);
    char[] b = new char[1024];
    int length;
    while((length = inputStreamReader.read(b)) > -1){
        outputStreamWriter.write(b,0,length);
    }
    inputStreamReader.close();
    outputStreamWriter.close();
}
序列化流

序列化流(Serialization Streams)是用于将对象转换为字节序列或从字节序列还原对象的I/O流。这是用于对象持久化和数据传输的一种机制,允许将对象转换为字节流,然后将其保存到文件、传输到网络或存储在数据库中,以后可以重新加载并还原为原始对象。序列化主要用于对象的持久化和跨网络传输。

ObjectInputStream:用于从字节流还原对象的流。它可以从输入流中读取字节数据,并将其还原为原始对象。

ObjectOutputStream:用于将对象序列化为字节流的流。它可以将对象写入输出流,从而将对象转换为字节数据。

/**
 * 序列化流简单使用
 */
@Test
public void test9() throws IOException, ClassNotFoundException {
    IOTestObject object = new IOTestObject("周杰伦", "44");
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("F:\\序列化文件.txt"));
    objectOutputStream.writeObject(object);
    objectOutputStream.close();
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("F:\\序列化文件.txt"));
    IOTestObject o = (IOTestObject)objectInputStream.readObject();
    System.out.println(o.toString());
    objectInputStream.close();
}

序列化时注意序列号和transient的使用。

打印流

打印流(Print Stream)是Java I/O中的一种特殊类型的输出流,用于简化文本数据的输出操作。它提供了一种便捷的方式来打印文本数据到控制台或文件,而不必手动构建和格式化文本字符串。

PrintStream:字节打印流

PrintWrite:字符打印流

/**
  * 打印流
  */
@Test
public void test10() throws FileNotFoundException {
    PrintStream printStream = new PrintStream(new FileOutputStream("F:\\字节打印流文件.txt"));
    printStream.println("眼神中飘移 总是在关键时刻清楚洞悉");// 写出+自动刷新+换行
    printStream.close();
    PrintWriter printWriter  = new PrintWriter(new FileOutputStream("F:\\字符打印流文件.txt"));
    printWriter.println("你的不坚定配合我颠沛流离 死去中清醒明白你背着我聪明");
    printWriter.close();
}
压缩流和解压缩流

压缩流:将文件形成压缩包。在压缩流中,每一个文件或者文件夹都是一个zipEntry对象。压缩流也可以理解成将每一个zipEntry对象放入压缩包中。

/**
 * 压缩流,压缩单个文件
 * 思路:定义要压缩的文件和压缩后的文件
 *      新建zipEntry对象并放入压缩包中
 *      将要压缩的文件数据给zipEntry关联
 */
@Test
public void test11() throws IOException {
    File file = new File("F:\\文件名称.txt");
    File zip = new File("F:\\压缩包名称.zip");
    ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zip));
    ZipEntry zipEntry = new ZipEntry("文件名称.txt");
    // 将zipEntry对象放入压缩包中
    zipOutputStream.putNextEntry(zipEntry);
    FileInputStream fileInputStream = new FileInputStream(file);
    int b;
    while ((b = fileInputStream.read()) > -1){
        // 把资源数据写入zipEntry对象
        zipOutputStream.write(b);
    }
    zipOutputStream.closeEntry();
    zipOutputStream.close();
}
/**
 * 压缩流,压缩文件夹
 * 区别与压缩单个文件的关键点在于文件夹内的文件夹怎么压缩处理:关注文件中文件夹的路径处理
 */
@Test
public void test12() throws IOException {
    // 要压缩的文件夹
    File file = new File("F:\\压缩流");
    // 压缩包
    File zip = new File("F:\\压缩流.zip");
    // 压缩流
    ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(zip));
    toZip(file,"",zipOutputStream);
    zipOutputStream.close();
}
 /**
 * 将文件夹压缩成压缩包
 * @param src 资源文件夹
 * @param zipPath 压缩包路径
 * @param zipOutputStream 关联压缩包的压缩流
 * @throws IOException
 */
public void toZip(File src,String zipPath,ZipOutputStream zipOutputStream) throws IOException {
    File[] files = src.listFiles();
    for (File fileTemp:files) {
        if(fileTemp.isFile()){
            // 资源文件在压缩包内的路径
            ZipEntry zipEntry = new ZipEntry("".equals(zipPath)?fileTemp.getName():zipPath+File.separator+fileTemp.getName());
            zipOutputStream.putNextEntry(zipEntry);
            FileInputStream fileInputStream = new FileInputStream(fileTemp);
            int b;
            while ((b = fileInputStream.read()) > -1){
                zipOutputStream.write(b);
            }
            fileInputStream.close();
            zipOutputStream.closeEntry();
        }else {
            // 递归
            toZip(fileTemp,"".equals(zipPath)?fileTemp.getName():zipPath+File.separator+fileTemp.getName(),zipOutputStream);
        }
    }
}

解压缩流:将压缩包解压。将压缩包中的每一个文件看成一个zipEntry,然后将每一个zipEntry复制一份,放到目标路径下。

@Test
public void test14() throws Exception {
    String zipFilePath = "F:\\压缩流解压.zip";
    String extractPath = "F:\\压缩流解压";
    extractZipFile(zipFilePath, extractPath);
}

public void extractZipFile(String zipFilePath, String extractPath) throws Exception {
    ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath));
    File extractDir = new File(extractPath);
    extractDir.mkdirs();
    ZipEntry zipEntry;
    while ((zipEntry = zipInputStream.getNextEntry()) != null) {
        String entryName = zipEntry.getName();
        File entryFile = new File(extractDir, entryName);
        if (zipEntry.isDirectory()) {
            entryFile.mkdirs();
        } else {
            File parentDir = entryFile.getParentFile();
            if (parentDir != null) {
                parentDir.mkdirs();
            }
            try (FileOutputStream fileOutputStream = new FileOutputStream(entryFile)) {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = zipInputStream.read(buffer)) > 0) {
                    fileOutputStream.write(buffer, 0, bytesRead);
                }
              }
            }
           zipInputStream.closeEntry();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值