File类、字节流、字符流、编码解读整理

2 篇文章 0 订阅

摘要:

        介绍了字节流、字符流的关系,讲解了字符与字节之间的编码解码过程,以及相关类如File、FileInputStream和FileOutputStream、OutputStreamWriter和InputStreamReader、FileRead、FileWrite、BufferdReader、BufferdWriter的应用

1、位、字节、字、字符的关系

  1. 位是最基本的数据单位,在二进制码中,一个0或1就是一个位

  2. 字节-byte:由八个位(bit)组成的单元,也是一种单位,常用于字符,如一个ASCII有一个字节,8个位

  3. 字:计算机进行数据存储和运算的单位,跟数据本身无关,如32位计算机中:1字=32位=4字节,64位计算机中:1字=64位=8字节

  4. 字符:数据的一种表达形式,'0'、'我',都是字符,float也是数据的一种表达形式:1.5f,2.6f。计算机的底层运算都是基于二进制的,字符、float是数据的一种表达,所以数据表达与二进制之间需要存在映射关系,这种关系就是编码规范

关系图:

数据大小单位扩展:

  1. 计算机中的存储单位有:bit、B、KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB等

  2. 1 Byte(B)= 8 bit;

  3. 1 Kilo Byte(KB) = 1024B;

  4. 1 Mega Byte(MB) = 1024 KB;

  5. 1 Giga Byte (GB)= 1024 MB;

  6. 1 Tera Byte(TB)= 1024 GB;

  7. 1 Peta Byte(PB) = 1024 TB;

2、编码与编码规范介绍:

  1. 编码

    1. 简单来说就是把人能看懂的转换成计算机能懂得,把计算机处理的转换成人能看懂的,举例:(下面长2进制用16进制展示)

    2. 我们看的(字符):‘0’,计算机处理的(字节):0000 0000(ASCLL编码),0110 0000(GBK),0110 0000(UTF-8)

    3. 我们看的(字符):’你好!‘,计算机处理的(字节):c4 e3 ba c3 21(GBK),e4 bd a0 e5 a5 bd 21(UTF-8)

  2. 编码规范

    1. 可以理解为二进制格式是计算机所能读懂的密语,那么这份密语的读取规则就是编码规范,计算机根据编码规则来读取字符或生成字符

    2. 例如:在UTF-8的规则下,c4 e3 ba c3 21 代表 ’你好!‘,但是在GBK的规则下,’你好‘ 的16进制格式却是e4 bd a0 e5 a5 bd 21

    3. 编码规范不是简单的规则,其内有复杂的运算原理,可以参考字符集和字符编码(Charset & Encoding) - 吴秦 - 博客园

  3. 乱码的产生

    1. 采用错误的编码规范读取字节流(后续进行验证)

3.File对象介绍,主要用于文件处理

  1. File类对象不但可以表示文件还可以表示目录

  2. 创建File对象后可以对文件或目录的属性进行操作,如文件名等,记住是对属性进行操作

  3. File对象不能直接对文件进行读/写操作

File类方法介绍:

boolean exists()

判断文件是否存在,存在返回true,否则返回false

boolean isFile()

判断是否为文件,是文件返回true,否则返回false

boolean isDirectory()

判断是否为目录,是目录返回true,否则返回false 

String getName() 

获得文件的名称

String getAbsolutePath() 

获得文件的绝对路径

long length()

获得文件的长度(字节数)

boolean createNewFile() throws IOException

创建新文件,创建成功返回true,否则返回false,有可能抛出IOException异常,必须捕捉

boolean delete()

删除文件,删除成功返回true,否则返回false

File[] listFiles()

返回文件夹内的子文件与子文件夹的数组

 代码应用介绍

public static void main(String[] args) {
    File file = new File("test.txt");
    System.out.println("文件或目录是否存在:"+file.exists());
    System.out.println("是文件吗:"+file.isFile());
    System.out.println("是目录吗:"+file.isDirectory());
    System.out.println("名称:"+file.getName());
    System.out.println("绝对路径:"+file.getAbsolutePath());
    System.out.println("文件大小:"+file.length());
    System.out.println("获取自由空间的大小"+file.getFreeSpace());
    System.out.println("有文件夹吗"+file.mkdirs());
    System.out.println("有父文件吗"+file.getParentFile());
    System.out.println("可以写吗"+file.canWrite());
    System.out.println("是可执行文件吗"+file.setExecutable(false));
}

4、流处理介绍:字节流和字符流

  1. 根据流动的方向,分为输入流和输出流,以内存为参考点,把数据写入内存则为输入流,数据从内存读出则为输出流

  2. 字节流处理类,指8位的通用字节流,以字节为基本单位,大部分继承于以下两类

    1. InputStream、OutputStream

  3. 字符流处理类,如Unicode字符流,以字符(两个字节)为基本单位,适合处理字符串和文本,因为涉及字符的处理,所以在处理过程中的编码规范尤其重要,大部分继承于以下两类:Reader、Writer

    1. FileReader、FileWriter

    2. InputStreamReader、OutputStreamReader

    3. FileReader、FileWriter对象在解码时采用默认编码规范,InputStreamReader、OutputStreamReader对象在解码时采用自定义编码规范,所以前者可能会产生乱码问题

5、FileInputStream和FileOutputStream,是字节流处理类,是进行文件读写操作的基本类

  1. 继承于InputStream和OutputStream,

  2. FileInputStream,将文件中的数据读取到内存中,因为是字节流,所以读取Unicode字符(如中文)可能会出现问题、FileOutputStream,将内存中的数据写入到文件中去,

  3. 由于采用字节流的方式,不考虑数据格式,这两个类对文件的操作效率较高

文件复制示例

public static void FileCopy(String sourcePath,String targetPath ){
    try {
        File sourceFile =new File(sourcePath);
        if(sourceFile.exists()){
            File targetFile=new File(targetPath);
            if(!targetFile.exists()){
                targetFile.createNewFile();
            }
            FileInputStream fin=new FileInputStream(sourceFile);
            FileOutputStream fos=new FileOutputStream(targetFile);
            //创建缓冲区,一次读取buffers大小
            byte[] buffers=new byte[1024];
            while (fin.read(buffers)!=-1){
                fos.write(buffers);
            }
            fin.close();
            fos.close();
        }else {
            return ;
        }
    }catch (IOException io){
        io.printStackTrace();
    }
}

6、FileInputStream和FileOutputStream虽然高效,但是对于Unicode编码的文件,使用可能会出现乱码,可采用字符流处理

  1. FileRead、FileWrite

  2. 这两个类会从文件中逐个获取字符,效率会比较低下,因此一版将该类对象包装到缓冲流中进行操作

  3. BufferedReader、BufferWriter

代码使用示例 FileRead、FileWrite、BufferdReader、BufferdWriter

/**
* 字符流方式copy文件 读取和输出均采用系统默认的编码规范
* @param sourcePath    源文件地址
* @param targetPath    目标文件地址
*/
public static void FileCopyByCharacter(String sourcePath,String targetPath ){
    try {
        File sourceFile = new File(sourcePath);
        File targetFile = new File(targetPath);
        if (sourceFile.exists()) {
            targetFile.delete();
            targetFile.createNewFile();
            FileReader fr=new FileReader(sourceFile);
            FileWriter fw=new FileWriter(targetFile);
            BufferedReader br=new BufferedReader(fr);
            BufferedWriter bw=new BufferedWriter(fw);
            while (br.readLine()!=null){
                bw.write(br.readLine());
            }
            br.close();
            bw.close();
            fr.close();
            fw.close();
        }
    }catch (IOException io){
        io.printStackTrace();
    }
}

//BufferdReader源码
private static int defaultCharBufferSize = 8192;


public BufferedReader(Reader in) {
    this(in, defaultCharBufferSize);
}


public BufferedReader(Reader in, int sz) {
    super(in);
    if (sz <= 0)
        throw new IllegalArgumentException("Buffer size <= 0");
    this.in = in;
    cb = new char[sz];
    nextChar = nChars = 0;
}
//由源码可见,采用的类似缓存区的方式进行读取,后续深入研究

由于FileRead、FileWrite不能设置编码规范,需要设置编码规范时可采用InputStreamReader、OutputStreamWriter

替换为OutputStreamWriter和InputStreamReader并指定编码类型

/**
* 字符流方式copy文件-可设置读取编码规范和输出编码规范
* @param sourcePath    源文件地址
* @param targetPath    模板文件地址
* @param inputCharset  源文件读取编码规范
* @param outputCharset 目标文件输出编码规范
*/
public static void fileCopyByCharacter(
        String sourcePath,String targetPath,String inputCharset,String outputCharset){
    try {
        File sourceFile = new File(sourcePath);
        File targetFile = new File(targetPath);
        if (sourceFile.exists()) {
            targetFile.delete();
            targetFile.createNewFile();
            BufferedReader br=new BufferedReader(new InputStreamReader(new FileInputStream(sourceFile),Charset.forName(inputCharset)));
            BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile),Charset.forName(outputCharset)));
            String str;
            while ((str=br.readLine())!=null){
                bw.write(str);
                bw.newLine();
            }
            br.close();
            bw.close();
        }
    }catch (IOException io){
        io.printStackTrace();
    }
}

7、结合字节流、字符流,实践数据的编码和复现乱码

  1. 字符到字节的编码
  2. 字节到字符的解码
  3. 乱码的产生
1.字符的编码
  1. 创建字符串“你好!”
  2. 基于字符串创建两个文件:utf8File、gbkFile,分别为UTF-8和GBK的编码格式
  3. 这时候可能还看不出区别
  4. 接下来我们对刚刚创建的文件进行字节获取,注意,这里是获取字节
代码实践
public static void main(String[] args){
    //步骤一、创建两个文件
    String str="你好!";
    File utf8File=new File("F:/Excel文件/utf8.txt");
    utf8File.delete();
    utf8File.createNewFile();
    BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(utf8File),"utf-8"));
    bw.write(str);
    bw.close();
    File gbkFile=new File("F:/Excel文件/gbk.txt");
    gbkFile.delete();
    gbkFile.createNewFile();
    bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(gbkFile),"gbk"));
    bw.write(str);
    bw.close();
    //步骤二、获取两个文件的字节
    //获取utf8File的字节
    FileInputStream fi=new FileInputStream(utf8File);
    byte[] utf8Byte=new byte[16];
    while (fi.read(utf8Byte)!=-1){
    }
    fi.close();
    System.out.print("utf8File文件的字节:");
    for(byte b: utf8Byte){
        System.out.print(Integer.toHexString(b&0xff)+" ");
    }
    //获取gbkFile的字节
    byte[] gbkByte=new byte[16];
    fi=new FileInputStream(gbkFile);
    while (fi.read(gbkByte)!=-1){
    }
    fi.close();
    System.out.println();
    System.out.print("gbkFile文件的字节:");
    for(byte b: gbkByte){
        System.out.print(Integer.toHexString(b&0xff)+" ");
    }
}

输出结果:

utf8File文件的字节:e4 bd a0 e5 a5 bd 21 0 0 0 0 0 0 0 0 0
gbkFile文件的字节:c4 e3 ba c3 21 0 0 0 0 0 0 0 0 0 0 0

结论:

  1. 根据数据结果可得:虽然两个文件的字符都是“你好!”,但是字节数据却是不一样的(大小也不一样),这是因为utf-8和gbk的编码规则不一样,比如utf-8用三个字节编码中文,而gbk用两个字节编码中文

  2. 可以看出中文编码后的字节是不一样的,但是标点符号!编码后的字节是一样的,这也是经常只有中文出现乱码的原因

流程图:

2.字节的解码

  1. 我们对上述获取的两个字符utf8Byte、gbkByte进行字符解码,分别采用utf-8和gbk的解码方式

代码实践:添加代码

System.out.println("");
System.out.println("utf-8解码:"+new String(utf8Byte,"utf-8"));
System.out.println("utf-8解码:"+new String(gbkByte,"utf-8"));
System.out.println("gbk解码:"+new String(utf8Byte,"gbk"));
System.out.println("gbk解码:"+new String(gbkByte,"gbk"));

输出结果:

utf-8解码:你好!         
utf-8解码:���!           
gbk解码:浣犲ソ!         
gbk解码:你好!     

结论:

  1. 采用不同的解码方式,输出的结果不一样

  2. 当解码规范与编码规范不一致时,就会产生乱码

流程图:

3、乱码的产生
  1. 由上述的验证可知,乱码的产生主要是由解码时采用了不一致的编码规范导致的
  2. 在其他场景,编码也会导致乱码,比如系统默认的编码方式是utf-8,如果此时生成文件时采用了gbk的方式进行编码,那么这个文件在系统打开就会乱码。不过归根结底也是系统在打开文件时采用了不一致的编码规范进行解码

 学习内容整理记录,如有错误感谢指正,互相交流,共同进步

参考

https://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html

https://blog.csdn.net/qq_34681261/article/details/70988758

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值