概述

java读写文件的有很多种方式,基本都是采用java.io的inputStream和各种基于inputstream的封装实现对文件的读写,最原始的接口提供的便是基于byte的读写,而String可以看做是char[],一个char是8个byte。在最原始的ASCII编码中,我们采用一个字节 也就时8位来表示一个字符(图形字符或者控制字符),而后来1个字节不足以表示现实中的所有字符,于是出现了各种各样的编码格式,常见的比如UTF-8,GBK,UNICODE等。java中的string也是遵循jre中定义的默认字符集(基本为UTF-8),而在byte[]转化成String的过程中可能会由于编码字符集问题导致String逆向回来的byte[]与原来的数组不一致。

问题描述

使用Java 读取一个zip文件,转化成String从客户端传输到服务器,客户端保存到本地文件中,发现客户端得到zip文件不能使用,提示文件损坏或者结尾损坏。比较源文件和生成文件的字符,发现字符不一致。

测试程序

public class Test_Keyczar {
    public static void main(String[] args) {
        try {   
            byte[] out = readFileInBytesToString("D:\\下载\\a+b.zip");
            File outfile = new File("D:\\下载\\a-b1221.zip");
            if (!outfile.exists()) {
                outfile.createNewFile();
            }
            DataOutputStream fw = new DataOutputStream(new FileOutputStream(
                    outfile));
            fw.write(new String(out).getBytes());
            fw.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
    public static byte[] readFileInBytesToString(String filePath) {
        final int readArraySizePerRead = 4096;
        File file = new File(filePath);
        ArrayList<Byte> bytes = new ArrayList<>();
        try {
            if (file.exists()) {
                DataInputStream isr = new DataInputStream(new FileInputStream(
                        file));
                byte[] tempchars = new byte[readArraySizePerRead];
                int charsReadCount = 0;

                while ((charsReadCount = isr.read(tempchars)) != -1) {
                    for(int i = 0 ; i < charsReadCount ; i++){
                        bytes.add (tempchars[i]);
                    }
                }
                isr.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return toPrimitives(bytes.toArray(new Byte[0]));
    }
    static byte[] toPrimitives(Byte[] oBytes) {
        byte[] bytes = new byte[oBytes.length];

        for (int i = 0; i < oBytes.length; i++) {
            bytes[i] = oBytes[i];
        }

        return bytes;
    }
}

测试结果

  • 如果以byte[]读入文件 不加修改直接写入文件,源文件和生成文件一致
  • 如果以byte[]读入文件 采用new String(byte[])的方式转化成String,之后在用String.getBytes()的方式转化为byte[]写文件,源文件和生成文件字符不一致

问题原因分析

java的默认字符集为UTF-8,所以在new String(byte[])的时候发生了字符集的转化,在原始的二进制文件中,字符是按照ascii的形式组织的,而utf-8采用的是一种可变长的编码,对于原始的ascii的数据的解码会导致字符数组的改变,而在String.getBytes()也是得到经过UTF-8编码过后的字符数组。

解决方案

为了排除字符集的问题,可以采用以下两种方案
- 以byte数组的方式读取和处理字符数组,不加转化
- 在byte[]转化成String的时候指定字符集为 ISO-8859-1,在String再转化成byte[]的时候也指定字符集为ISO-8859-1