Java二进制及中文转码和校验

Java二进制及中文转码和校验

  • 自大学那会学习了Java的8大基础数据类型(boolean/byte/short/char/int/long/float/double)后,毕业后的很多年几乎都不涉及基础数据类型了,尽管选的是Java方向;
  • 工作中的前若干年还尽量避免定义基础类型,比如int数组改成List等,心里一直在告诫自己,Java是面向对象的语言,应该尽量使用面向对象的API和语法,少使用原始的基础类型;
  • 又过了一些年,现在对基础类型有了全新的、自洽的认识,不吐不快,还请诸君斧正;
  • 本文重点关注char和byte类型的编码使用、转换,以及实际业务场景的应用;

1.Java基本数据类型

  • 8大基本数据类型列表

    基本类型大小(字节)默认值取值范围后缀示例
    boolean1false{false,true}-boolean sign=true;
    byte10[-128,127]-byte b=125;
    short20[-32768~32767]-short num=10;
    char2‘\u0000’[0,65535]-char ch1=‘k’;
    char ch2=‘\u77be’;
    char ch3=‘瞾’;
    int40[-2^ 31 , 2^31-1]-int count=100;
    long80[-2^63 , 2 ^63-1]L/llong a=100L;
    float40.0f[-2^ 31 , 2^31-1]F/ffloat a=1.2f
    double80.0d[-2^ 63,2^63-1]D/ddouble a=1.2d
    1. char表示一个字符,是相对于utf-8编码而言,但是对unicode却并不是100%适用,因为有部分汉字需要4个字节(2个unicode)来表示,也就是说一个汉字字符实际上是2个char,即上表的示例char ch3='瞾'就不够严谨;
    2. 我们来验证下char ch2='\u77be';,ch2表示1个字符,同时也可以用’\u77be’来定义,而77be就是4个16进制位,1个16进制位表示4个二进制位(4b),则77be表示16个二进制位(16b),即2个字节(2Byte=16bit);

1.1 基本数据类型占用的存储空间

  • 一个byte数字占用1个byte字节应该很好理解;
  • 我们刚才已经说明了1个常规字符(char类型),占用2个字节(2byte);

1.2 Java二进制流

  • Java抽象的Stream流(InputStream/OutputStream)本质上都是都是byte数组的传输;
    • InputStream官方的注释为This abstract class is the superclass of all classes representing an input stream of bytes.
    • OutputStream官方的注释为This abstract class is the superclass of all classes representing an output stream of bytes.
  • 在此基础之上,实现类ArrayByteInputStream/ArrayByteOutputStream/FileInputStream/FileOutputStream等各种数据流,都是不同应用场景上的实现封装,其二进制数据的本质是一样的;
  • 项目中读取二进制流的工具类IoUtil
    public final class IoUtil
    {
        /**
         * 读取文件流
         * <pre>
         * 1.文件支持从class外部读取(class调试模式)和jar内部读取(jar包使用模式)
         * 2.以此class是否在jar包为依据,当不在时,优先从外部读取(证明是class调试模式,根本就没有jar包)
         * 3.如果上述方式读不到文件,则遵从spring的读取规则(使用spring的API读取)
         *
         * @param path 文件路径
         * @return 文件流
         */
        public static InputStream readInputStream(String path)
        {
            try
            {
                String clazzPath = IoUtil.class.getResource(FILE_SPLIT).getPath();
                boolean clazzInJar = clazzPath.startsWith(FILE_TYPE);
                boolean inClazzWithoutJar = clazzPath.startsWith(FILE_SPLIT) && !clazzInJar;
                boolean inJar = path.toLowerCase(Locale.US).startsWith(CLASSPATH) || clazzInJar;
                String realPath = path;
                //非jar包模式时,拼接全路径读取
                if (inClazzWithoutJar && !inJar)
                {
                    if (!realPath.contains(FILE_FULL_PATH_TYPE) && !realPath.startsWith(FILE_SPLIT))
                    {
                        realPath = clazzPath + realPath;
                    }
                    return new FileInputStream(realPath);
                }
                Resource resource = new PathMatchingResourcePatternResolver().getResource(path);
                boolean existFile = resource.exists();
                //jar包模式运行时,通过spring的api去读取
                if (existFile)
                {
                    return resource.getInputStream();
                }
            }
            catch (Exception e)
            {
                throw new EncryptionException("read stream error.", e);
            }
            return null;
        }
    
        /**
         * 私有化构造方法
         */
        private IoUtil()
        {
        }
    
        /**
         * 非jar形式的文件全路径标记
         */
        public static final String FILE_FULL_PATH_TYPE = ":";
    
        /**
         * 文件类型
         */
        public static final String FILE_TYPE = "file:";
    
        /**
         * 文件分割类型
         */
        public static final String FILE_SPLIT = "/";
    
        /**
         * 配置类型
         */
        private static final String CLASSPATH = "classpath:";
    }
    
  • 项目中读写文件二进制数据的工具类FileUtil 代码如下:
    public final class FileUtil
    {
        /**
         * 读取文件的二进制内容
         *
         * @param path 文件路径
         * @return 文件二进制内容
         */
        public static byte[] read(String path)
        {
            return read(IoUtil.readInputStream(path));
        }
    
        /**
         * 读取输入流报文
         *
         * @param in 文件输入流
         * @return 二进制报文
         */
        public static byte[] read(InputStream in)
        {
            return read(in, Integer.MAX_VALUE);
        }
    
        /**
         * 读取限定文件大小的二进制流
         *
         * @param in   二进制流
         * @param size 二进制流的大小上限
         * @return 文件二进制报文
         */
        public static byte[] read(InputStream in, int size)
        {
            try
            {
                if (size <= 0)
                {
                    size = Integer.MAX_VALUE;
                }
    
                byte[] data = IOUtils.toByteArray(in);
                if (null != data && data.length > size)
                {
                    LOGGER.error("failed to read limit data size:{}.", data.length);
                    return null;
                }
    
                FileType fileType = FileType.getType(data);
                LOGGER.info("current read stream file type:{}", fileType);
                return data;
            }
            catch (IOException e)
            {
                LOGGER.error("failed to read input stream.", e);
            }
            finally
            {
                IOUtils.closeQuietly(in);
            }
            return null;
        }
    
        /**
         * 读取指定编码的文件内容(未指定编码格式时,则默认读取UTF-8编码)
         *
         * @param path    文件路径
         * @param charset 文件编码格式
         * @return 文件内容
         */
        public static String read(String path, Charset charset)
        {
            byte[] data = read(path);
            if (null == data)
            {
                LOGGER.error("failed to read input stream:{}.", path);
                return null;
            }
            if (null == charset)
            {
                charset = StandardCharsets.UTF_8;
            }
            return new String(data, charset);
        }
    
        /**
         * 把内容写入文件
         *
         * @param data 二进制文件内容
         * @param path 目标文件路径
         */
        public static void write(byte[] data, String path)
        {
            OutputStream out = null;
            try
            {
                File file = new File(path).getCanonicalFile();
                if (!file.isFile() || !file.exists())
                {
                    FileUtils.forceMkdirParent(file);
                    LOGGER.info("current file path[{}] force created.", file.getParentFile().getCanonicalPath());
                }
                FileType fileType = FileType.getType(data);
                LOGGER.info("current write stream file type:{}", fileType);
                out = new FileOutputStream(file);
                IOUtils.write(data, out);
            }
            catch (IOException e)
            {
                LOGGER.error("failed to write file.", e);
            }
            finally
            {
                IOUtils.closeQuietly(out);
            }
        }
    
        private FileUtil()
        {
        }
    
        /**
         * 日志句柄
         */
        private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class);
    }
    

1.3 二进制转换说明

  • 在了解了Java世界的二进制本质之后,还需要了解下二进制在不同业务场景下的具体转换应用。比如:图片转换、数据加解密等;
1.3.1 二进制与Base64互转
  • Base64本质上是二进制数据的封装,用于显示表示二进制的场景含义;
  • 图片二进制转成Base64源码ImageUtil 如下:
    public final class ImageUtil
    {
        /**
         * 从bas464中转换出图片二进制数据
         *
         * @param base64 图片base64
         * @return 图片二进制数据
         */
        public static byte[] toBytes(String base64)
        {
            if (StringUtils.isEmpty(base64))
            {
                LOGGER.error("invalid base64.");
                return null;
            }
            try
            {
                if (base64.contains(Const.SPLIT))
                {
                    //去掉base64中的文件头前缀(如:'data:image/png;base64,')
                    base64 = base64.substring(base64.lastIndexOf(Const.SPLIT));
                }
                byte[] data = Base64.getMimeDecoder().decode(base64);
                LOGGER.info("current image file type:{}", FileType.getType(data));
                return data;
            }
            catch (Exception e)
            {
                LOGGER.error("failed to get byte[] from base64.", e);
            }
            return null;
        }
    
        /**
         * 二进制图片数据转换成base64
         *
         * @param data 图片二进制数据
         * @return 图片base64
         */
        public static String toBase64(byte[] data)
        {
            if (null == data)
            {
                LOGGER.error("invalid image file data.");
                return null;
            }
    
            try
            {
                LOGGER.info("current image file type:{}", FileType.getType(data));
                String base64 = Base64.getEncoder().encodeToString(data);
                if (!StringUtils.isEmpty(base64) && base64.contains(Const.SPLIT))
                {
                    //去掉base64中的文件头前缀(如:'data:image/png;base64,')
                    base64 = base64.substring(base64.lastIndexOf(Const.SPLIT));
                }
                return base64;
            }
            catch (Exception e)
            {
                LOGGER.error("failed to get base64 by byte[].", e);
            }
            return null;
        }
    
        /**
         * 图片文件转base64
         *
         * @param path 文件路径
         * @return 文件的base64
         */
        public static String toBase64(String path)
        {
            return toBase64(FileUtil.read(path));
        }
    }
    

    在图片场景下,Base64表示的图片字符串完全等同于图片文件;

  • 秘钥二进制转成对应的Base64秘钥字符串,如:RSA/PGP生成的cer /pem /asc 等秘钥文件,形如效果:
    -----BEGIN RSA PUBLIC KEY-----
    MIIBCgKCAQEAjsWd3QKjDCVU+H9jkkMlOAxAKpG/nT7N+0LOQ75/SxjNaVdmLOhj
    oLAtzFOnY72HoJvd62fFNllU0AkpQuotp2Ajt7W9bHfvtxS8N2EXyShSdBqr1eLo
    zNwgeRqeU/uZmGVi5ehdgBTliQKeUs80mbnBwGzP6e/FUSVlXRXxyn4Pl3hclAD6
    ZM3vP6vSCXIhM4LoI4c...
    -----END RSA PUBLIC KEY-----
    
1.3.2 二进制与十六进制互转
  • 在Java世界中,经常要用到二进制数组(byte[]),但是在接口传输、存储时,又基本上没有,二进制去哪儿了?
  • 除了上面说的Base64外,还经常用到把二进制数组转换成十六进制(Hex),比如说SHA512/SHA256/MD5等摘要,也需要把加密密文由二进制转成十六进制,解密时把十六进制反转成二进制,示例代码SecurityFacade 如下:
    public class SecurityFacade extends BaseEncryptorFacade
    {
        /**
         * 本地不可逆加密或者hash
         *
         * @param data 原始数据
         * @return 摘要数据
         */
        @Override
        public String hash(String data)
        {
            byte[] encBytes = this.getEncryptSecurity().hash(data.getBytes(StandardCharsets.UTF_8));
            return Hex.toHexString(encBytes);
        }
        
        /**
         * 本地可逆加密
         *
         * @param data 原始报文
         * @return 加密后的报文
         */
        @Override
        public String encrypt(String data)
        {
            byte[] encBytes = this.getEncryptSecurity().encrypt(data.getBytes(StandardCharsets.UTF_8));
            return Hex.toHexString(encBytes);
        }
    
        /**
         * 本地解密
         * <p>
         * 与上面加密对应
         *
         * @param data 加密后的数据
         * @return 解密后的数据
         */
        @Override
        public String decrypt(String data)
        {
            byte[] decBytes = this.getEncryptSecurity().decrypt(Hex.decode(data));
            return new String(decBytes, StandardCharsets.UTF_8);
        }    
    }    
    

2.Java魔数应用

  • 在了解Java底层基本上都是二进制数据之后,还有一个概念和二进制息息相关:魔数魔数是用于识别文件格式或者协议类型的一段常量或者字符串。
  • 此处讲的魔数仅限于上述的命名规范,不涉及Java语言魔法数字的检验规则。

2.1 文件魔数

  • 文件魔数就是文件二进制/十六进制数据的特定起始标识位,此处封装常用文件检验的FileType 代码如下:
    public enum FileType
    {
        /**
         * JEPG.
         */
        JPEG("FFD8FF"),
    
        /**
         * PNG.
         */
        PNG("89504E47"),
    
        /**
         * GIF.
         */
        GIF("47494638"),
    
        /**
         * TIFF.
         */
        TIFF("49492A00"),
    
        /**
         * Windows Bitmap.
         */
        BMP("424D"),
    
        /**
         * CAD.
         */
        DWG("41433130"),
    
        /**
         * Adobe Photoshop.
         */
        PSD("38425053"),
    
        /**
         * Rich Text Format.
         */
        RTF("7B5C727466"),
    
        /**
         * XML.
         */
        XML("3C3F786D6C"),
    
        /**
         * HTML.
         */
        HTML("68746D6C3E"),
    
        /**
         * Email [thorough only].
         */
        EML("44656C69766572792D646174653A"),
    
        /**
         * Outlook Express.
         */
        DBX("CFAD12FEC5FD746F"),
    
        /**
         * Outlook (pst).
         */
        PST("2142444E"),
    
        /**
         * MS Word/Excel(兼容格式).
         */
        XLS_DOC("D0CF11E0"),
    
        /**
         * XLSX(excel新版本)
         */
        XLSX("504B030414000600080000002100"),
    
        /**
         * DOCX(word新版本)
         */
        DOCX("504B03041400060008000000210077"),
    
        /**
         * MS Access.
         */
        MDB("5374616E64617264204A"),
    
        /**
         * WordPerfect.
         */
        WPD("FF575043"),
    
        /**
         * Postscript.
         */
        EPS("252150532D41646F6265"),
    
        /**
         * Adobe Acrobat.
         */
        PDF("255044462D312E"),
    
        /**
         * Quicken.
         */
        QDF("AC9EBD8F"),
    
        /**
         * Windows Password.
         */
        PWL("E3828596"),
    
        /**
         * ZIP Archive.
         */
        ZIP("504B0304"),
    
        /**
         * RAR Archive.
         */
        RAR("52617221"),
    
        /**
         * Wave.
         */
        WAV("57415645"),
    
        /**
         * AVI.
         */
        AVI("41564920"),
    
        /**
         * Real Audio.
         */
        RAM("2E7261FD"),
    
        /**
         * Real Media.
         */
        RM("2E524D46"),
    
        /**
         * MPEG (mpg).
         */
        MPG("000001BA"),
    
        /**
         * Quicktime.
         */
        MOV("6D6F6F76"),
    
        /**
         * Windows Media.
         */
        ASF("3026B2758E66CF11"),
    
        /**
         * MIDI.
         */
        MID("4D546864");
    
        /**
         * 获取最长的二进制文件格式对应的内容
         *
         * @return 文件格式对应的最长的二进制长度
         */
        public static int getMaxBytes()
        {
            return MAX_LEN.get();
        }
    
        /**
         * 获取文件类型
         *
         * @param data 文件二进制
         * @return 文件类型对象
         */
        public static FileType getType(byte[] data)
        {
            if (null == data)
            {
                LOGGER.warn("unknown file type by stream.");
                return null;
            }
    
            //1.截取最长的文件格式的二进制位数
            int maxLen = Math.min(data.length, MAX_LEN.get());
            byte[] suffixBytes = Arrays.copyOf(data, maxLen);
            //2.把最长的长度的二进制转成十六进制
            String suffixTypes = Hex.toHexString(suffixBytes);
            for (FileType fileType : SORTED_TYPES)
            {
                if (suffixTypes.toUpperCase(Locale.US).contains(fileType.type.toUpperCase(Locale.US)))
                {
                    LOGGER.info("current file type by stream:{}.", fileType.name());
                    return fileType;
                }
            }
            LOGGER.warn("unknown file type by stream.");
            return null;
        }
    
        /**
         * 获取到十六进制的类型
         *
         * @return 十六进制的类型
         */
        public String getType()
        {
            return this.type;
        }
    
        @Override
        public String toString()
        {
            return this.name().toLowerCase(Locale.US);
        }
    
        /**
         * 构造方法
         *
         * @param type 文件类型
         */
        FileType(String type)
        {
            this.type = type;
        }
    
        /**
         * 最大长度的文件二进制数位
         */
        private static final AtomicInteger MAX_LEN = new AtomicInteger();
    
        /**
         * 排序后的文件类型集合
         */
        private static final List<FileType> SORTED_TYPES = Lists.newArrayList();
    
        /**
         * 日志句柄
         */
        private static final Logger LOGGER = LoggerFactory.getLogger(FileType.class);
    
        /**
         * 文件类型
         */
        private String type;
    
        //初始化
        static
        {
            //1.获取最长的文件格式的二进制长度
            int maxHexLen = 0;
            int maxBytesLen = 0;
            for (FileType fileType : values())
            {
                int len = fileType.type.length();
                if (len > maxHexLen)
                {
                    maxBytesLen = Hex.decode(fileType.type.getBytes(StandardCharsets.UTF_8)).length;
                }
                maxHexLen = Math.max(maxHexLen, len);
            }
    
            //2.把遍历的最长的二进制前缀长度保存下来
            MAX_LEN.set(maxBytesLen);
    
            //3.把所有格式枚举保存起来
            SORTED_TYPES.addAll(Lists.newArrayList(values()));
    
            //4.把所有的格式枚举重新排序(从长到短,为了避免文件格式存在包含而取错的情况,比如:docx格式应该包含了doc)
            Collections.sort(SORTED_TYPES, new Comparator<FileType>()
            {
                @Override
                public int compare(FileType o1, FileType o2)
                {
                    return o2.type.length() - o1.type.length();
                }
            });
        }
    }
    

    注意:

    • 文件魔数可能会存在长的16进制编码包含了短的16进制编码的情况,本逻辑判断文件格式时,会优先匹配长的16进制编码,这样就不会误判;
    • 为了避免大文件解析,其实并不需要解析出整个文件的二进制/十六进制内容,仅需解析大概前200个byte即可判断文件格式。

2.2 协议魔数

  • 在实际项目中,编写SM2协议实现时,就存在基于协议魔数来判断秘钥是否压缩的情况,Sm2Encryption 代码如下:
    public class Sm2Encryption extends BaseSingleSignature
    {
        @Override
        public PublicKey toPubKey(byte[] pubKey)
        {
            try
            {
                String hexKey = Hex.toHexString(pubKey);
                KeyFactory kf = KeyFactory.getInstance(ALGORITHM, this.getProvider());
                if (hexKey.startsWith(STANDARD_HEX_KEY_PREFIX))
                {
                    return kf.generatePublic(new X509EncodedKeySpec(pubKey));
                }
                else
                {
                    // 获取SM2相关参数
                    X9ECParameters ecParam = GMNamedCurves.getByName(SM2_VERSION);
                    // 将公钥HEX字符串转换为椭圆曲线对应的点
                    ECCurve ecCurve = ecParam.getCurve();
                    ECPoint ecPoint = ecCurve.decodePoint(pubKey);
                    // 椭圆曲线参数规格
                    ECParameterSpec ecSpec = new ECParameterSpec(ecCurve, ecParam.getG(), ecParam.getN(), ecParam.getH());
                    // 将椭圆曲线点转为公钥KEY对象
                    return kf.generatePublic(new ECPublicKeySpec(ecPoint, ecSpec));
                }
            }
            catch (Exception e)
            {
                throw new EncryptionException("failed to get sm2 pub key.", e);
            }
        }
        
        /**
         * 标准的秘钥hex前缀
         */
        private static final String STANDARD_HEX_KEY_PREFIX = "30";
    }
    

3.Java Unicode编码应用

  • unicode与char的关系

    • Unicode,全称为Unicode标准(The Unicode Standard),官方机构使用的中文名称为统一码;
    • unicode是个编码方案,Java语言默认的UTF-8字符集就是unicode编码方案的一种实现,后面所有unicode编码均指UTF-8的unicode实现;
    • UTF-8的unicode是变长的,可以由2个或者4个字节来表示1个字符;
    • 1个字节需要2个16进制字符来表示;

    结论: unicode是变长的,可以是2个字节,也可以是4个字节来表示一个字符,unicode也可以说是4/8个16进制位来表示;

  • GB18030-2022字符规范 正式生效在即,也有必要和大家好好分析下中文字符在UTF-8编码的Unicode规范应用;

  • UTF-8 unicode中文字符集 如下:

    字符集字数Unicode 编码
    基本汉字20902字4E00-9FA5
    基本汉字补充90字9FA6-9FFF
    扩展A6592字3400-4DBF
    扩展B42720字20000-2A6DF
    扩展C4154字2A700-2B739
    扩展D222字2B740-2B81D
    扩展E5762字2B820-2CEA1
    扩展F7473字2CEB0-2EBE0
    扩展G4939字30000-3134A
    扩展H4192字31350-323AF
    康熙部首214字2F00-2FD5
    部首扩展115字2E80-2EF3
    兼容汉字472字F900-FAD9
    兼容扩展542字2F800-2FA1D
    汉字笔画36字31C0-31E3
    汉字结构12字2FF0-2FFB
    汉语注音43字3105-312F
    注音扩展32字31A0-31BF
    1字3007

3.1 Java中文字符转换

  • 专门写了个CharUtil 来处理汉字的unicode与汉字转换:
    public final class CharUtil
    {
        /**
         * 把unicode转换成字符串
         *
         * @param ch unicode字符
         * @return 字符对应的字符串显示
         */
        public static String fromUnicode(char[] ch)
        {
            StringBuilder result = new StringBuilder(StringUtils.EMPTY);
            for (char c : ch)
            {
                result.append(fromUnicode(c));
            }
            return result.toString();
        }
    
        /**
         * 把unicode转换成字符串
         *
         * @param ch unicode字符
         * @return 字符对应的字符串显示
         */
        public static String fromUnicode(char ch)
        {
            return StringUtils.EMPTY + ch;
        }
    
        /**
         * 获取范围内的连续字符集合
         *
         * @param start 起始字符位置
         * @param end   结束字符位置
         * @return 字符对应的文本
         */
        public static List<String> fromUnicode(char[] start, char[] end)
        {
            List<String> lists = Lists.newArrayList();
    
            char minChar = start[start.length - Const.ONE];
            char maxChar = end[end.length - Const.ONE];
            boolean multiUnicode = (start.length > Const.ONE);
            for (int index = minChar; index <= maxChar; index++)
            {
                char[] chars = {(char)index};
                if (multiUnicode)
                {
                    chars = new char[] {start[0], (char)index};
                }
                String ch = CharUtil.fromUnicode(chars);
                lists.add(ch);
            }
            return lists;
        }
    
        /**
         * 把字符转换成unicode
         *
         * @param ch 字符
         * @return 字符对应的unicode
         */
        public static String toUnicode(String ch)
        {
            StringBuilder result = new StringBuilder(StringUtils.EMPTY);
            if (!StringUtils.isEmpty(ch))
            {
                char[] chars = ch.toCharArray();
                for (char c : chars)
                {
                    String hex = Integer.toHexString(c);
                    int fillLen = CHAR_HEX_LEN - hex.length();
                    if (fillLen > 0)
                    {
                        hex = StringUtils.repeat(StringUtils.EMPTY + 0, fillLen) + hex;
                    }
                    result.append(UNICODE_TAG).append(hex);
                }
            }
            return result.toString();
        }
    
        /**
         * 是否是中文
         *
         * @param ch 中文字符集
         * @return true表示是中文
         */
        public static boolean isChinese(String ch)
        {
            return RegexUtil.match(ch, CHINESE_REGEX);
        }
    
        private CharUtil()
        {
        }
    
        /**
         * 一个字符对应的16进制位个数
         */
        private static final int CHAR_HEX_LEN = 4;
    
        /**
         * UNICODE起始标记
         */
        private static final String UNICODE_TAG = "\\u";
    
        /**
         * 中文名字等字符串匹配表达式
         */
        private static final String CHINESE_REGEX =
            "^([\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff\\x{20000}-\\x{323af}\\u2180-\\u2ef3\\u2f00-\\u2fd5\\u2ff0-\\u2ffb\\u3105-\\u312f\\u31a0-\\u31bf\\u31c0-\\u31e3\\u3007·])+$";
    }
    
  • 根据上述工具类,专门验证下 unicode中文字符集 中的编码;
    • 测试代码CharUtilTest 如下:
      public class CharUtilTest
      {
      
          @Test
          public void toUnicode2()
          {
              char[] starts2 = {'\u9FA6'};
              char[] ends2 = {'\u9FFF'};
              toUnicode(starts2, ends2);
          }
      
          private void toUnicode(char[] starts, char[] ends)
          {
              List<String> chars = CharUtil.fromUnicode(starts, ends);
              int i = 0;
              for (String ch : chars)
              {
                  System.out.println(String.format("[%s]%s=%s", i, ch, CharUtil.toUnicode(ch)));
                  i++;
              }
              System.out.println("******************************************total=" + chars.size());
          }
      }
      
    • 测试效果如下,与标准的数量一致:
    [0]龦=\u9fa6
    [1]龧=\u9fa7
    [2]龨=\u9fa8
    [3]龩=\u9fa9
    [4]龪=\u9faa
    [5]龫=\u9fab
    [6]龬=\u9fac
    [7]龭=\u9fad
    [8]龮=\u9fae
    [9]龯=\u9faf
    [10]龰=\u9fb0
    [11]龱=\u9fb1
    [12]龲=\u9fb2
    [13]龳=\u9fb3
    [14]龴=\u9fb4
    [15]龵=\u9fb5
    [16]龶=\u9fb6
    [17]龷=\u9fb7
    [18]龸=\u9fb8
    [19]龹=\u9fb9
    [20]龺=\u9fba
    [21]龻=\u9fbb
    [22]龼=\u9fbc
    [23]龽=\u9fbd
    [24]龾=\u9fbe
    [25]龿=\u9fbf
    [26]鿀=\u9fc0
    [27]鿁=\u9fc1
    [28]鿂=\u9fc2
    [29]鿃=\u9fc3
    [30]鿄=\u9fc4
    [31]鿅=\u9fc5
    [32]鿆=\u9fc6
    [33]鿇=\u9fc7
    [34]鿈=\u9fc8
    [35]鿉=\u9fc9
    [36]鿊=\u9fca
    [37]鿋=\u9fcb
    [38]鿌=\u9fcc
    [39]鿍=\u9fcd
    [40]鿎=\u9fce
    [41]鿏=\u9fcf
    [42]鿐=\u9fd0
    [43]鿑=\u9fd1
    [44]鿒=\u9fd2
    [45]鿓=\u9fd3
    [46]鿔=\u9fd4
    [47]鿕=\u9fd5
    [48]鿖=\u9fd6
    [49]鿗=\u9fd7
    [50]鿘=\u9fd8
    [51]鿙=\u9fd9
    [52]鿚=\u9fda
    [53]鿛=\u9fdb
    [54]鿜=\u9fdc
    [55]鿝=\u9fdd
    [56]鿞=\u9fde
    [57]鿟=\u9fdf
    [58]鿠=\u9fe0
    [59]鿡=\u9fe1
    [60]鿢=\u9fe2
    [61]鿣=\u9fe3
    [62]鿤=\u9fe4
    [63]鿥=\u9fe5
    [64]鿦=\u9fe6
    [65]鿧=\u9fe7
    [66]鿨=\u9fe8
    [67]鿩=\u9fe9
    [68]鿪=\u9fea
    [69]鿫=\u9feb
    [70]鿬=\u9fec
    [71]鿭=\u9fed
    [72]鿮=\u9fee
    [73]鿯=\u9fef
    [74]鿰=\u9ff0
    [75]鿱=\u9ff1
    [76]鿲=\u9ff2
    [77]鿳=\u9ff3
    [78]鿴=\u9ff4
    [79]鿵=\u9ff5
    [80]鿶=\u9ff6
    [81]鿷=\u9ff7
    [82]鿸=\u9ff8
    [83]鿹=\u9ff9
    [84]鿺=\u9ffa
    [85]鿻=\u9ffb
    [86]鿼=\u9ffc
    [87]鿽=\u9ffd
    [88]鿾=\u9ffe
    [89]鿿=\u9fff
    ******************************************total=90
    

    注意:在屏幕上能看到多少个字符,是和你的终端操作系统装的字库集有关系;

  • unicode与中文字符互转验证
    • 字符转换测试代码CharUtilTest
      public class CharUtilTest
      {
          @Test
          public void fromUnicode()
          {
              char ch = '\u77be';
              System.out.println(CharUtil.fromUnicode(ch) + "=" + CharUtil.toUnicode("" + ch));
          }
      
          @Test
          public void toUnicode()
          {
              String ch = "瞾";
              String format = "%s[%s]=%s=>%s";
              String unicode = CharUtil.fromUnicode(ch.toCharArray());
              System.out.println(String.format(format, ch, ch.toCharArray().length, CharUtil.toUnicode(ch), unicode));
      
              String ch2 = "\uD84C\uDC7E";
              String unicode2 = CharUtil.fromUnicode(ch2.toCharArray());
              System.out.println(String.format(format, ch2, ch2.toCharArray().length, CharUtil.toUnicode(ch2), unicode2));
          }
      }
      
    • 验证效果依次如下:
      瞾=\u77be
      ——————————————————————————————
      瞾[1]=\u77be=>瞾
      𣁾[2]=\ud84c\udc7e=>𣁾
      

3.2 Java中文字符校验

  • unicode与中文字符互转验证
    • 中文字符校验测试代码CharUtilTest
      public class CharUtilTest
      {
          @Test
          public void isChinese()
          {
              String ch1 = "\uD84C\uDC7E";
              Assert.assertTrue(CharUtil.isChinese(ch1));
      
              String ch2 = "\u1000";
              Assert.assertTrue(!CharUtil.isChinese(ch2));
      
              String ch3 = "\u77be";
              Assert.assertTrue(CharUtil.isChinese(ch3));
      
              String ch4 = "\u77be\u1000";
              Assert.assertTrue(!CharUtil.isChinese(ch4));
      
              String ch5 = "\u77be\uD84C\uDC7E";
              Assert.assertTrue(CharUtil.isChinese(ch5));
      
              String ch6 = "\u1000\u77be\uD84C\uDC7E";
              Assert.assertTrue(!CharUtil.isChinese(ch6));
      
              String ch7 = "\u77be\u1000\uD84C\uDC7E";
              Assert.assertTrue(!CharUtil.isChinese(ch7));
          }
      }
      
    • 断言全部成功,表示中文字符,非法中文字符都能识别出来。

4.参考资料

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值