> 参考的优秀文章

1、十分钟搞清字符集和字符编码

2、Java中byte与16进制字符串的互相转换

3、【异常处理】Incorrect string value: '\xF0\x90\x8D\x83...' for column... Emoji表情字符过滤的Java实现

4、Why a surrogate java regexp finds hypen-minus

> 如何检测、替换4个字节的utf-8编码(此范围编码包含emoji)

项目有个需求,是保存从手机端H5页面提交的信息。

大家知道,手机端输入法中经常有自带的表情,其中emoji表情非常流行,如果用户输入emoji表情,由于有部分emoji表情是4个字节的utf-8编码,我们的MySQL数据库在现有版本和编码设置下只能保存3个字节的utf-8编码(如要保存4个字节的utf-8编码则需升级版本和设置另一种编码)。相关信息可见文章《十分钟搞清字符集和字符编码》。

我们的需求不需要支持emoji表情,如果遇到emoji弹出提示或过滤即可。

通过浏览《【异常处理】Incorrect string value: '\xF0\x90\x8D\x83...' for column... Emoji表情字符过滤的Java实现》和《Why a surrogate java regexp finds hypen-minus》,我们得知通过以下代码进行替换:

msg.replaceAll("[\\ud800\\udc00-\\udbff\\udfff\\ud800-\\udfff]", "");

效果是OK的。

但是,由于能力原因,始终没能理解上述代码十六进制正则表达式的原理,自己写了一端代码来检测、替换4个字节的utf-8编码(但未能经过完整测试,仅用于描述大概思路)。

其中UTF-8编码规则阅读自《十分钟搞清字符集和字符编码》,字节与十六进制的转换参考自《Java中byte与16进制字符串的互相转换》。

package com.nicchagil.tc.emojifilter;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class UTF8Utils {

    public static void main(String[] args) {
        String s = "琥珀蜜蜡由于硬度很低,打磨起来非123常简单,需要的工具也非常简单,自己去买蜜蜡是非常不划算的,完全可以自己磨蜜蜡原石的样子";
        System.out.println(UTF8Utils.bytesToHex(s.getBytes()));
        System.out.println(UTF8Utils.bytesToHex(UTF8Utils.remove4BytesUTF8Char(s)));
    }

    public static Map<String, Integer> hexMap = new HashMap<String, Integer>();
    public static Map<String, Integer> byteMap = new HashMap<String, Integer>();

    static {
        hexMap.put("0", 2);
        hexMap.put("1", 2);
        hexMap.put("2", 2);
        hexMap.put("3", 2);
        hexMap.put("4", 2);
        hexMap.put("5", 2);
        hexMap.put("6", 2);
        hexMap.put("7", 2);
        hexMap.put("c", 4);
        hexMap.put("d", 4);
        hexMap.put("e", 6);
        hexMap.put("f", 8);

        byteMap.put("0", 1);
        byteMap.put("1", 1);
        byteMap.put("2", 1);
        byteMap.put("3", 1);
        byteMap.put("4", 1);
        byteMap.put("5", 1);
        byteMap.put("6", 1);
        byteMap.put("7", 1);
        byteMap.put("c", 2);
        byteMap.put("d", 2);
        byteMap.put("e", 3);
        byteMap.put("f", 4);
    }

    /**
     * 是否包含4字节UTF-8编码的字符(先转换16进制再判断)
     * @param s 字符串
     * @return 是否包含4字节UTF-8编码的字符
     */
    public static boolean contains4BytesChar(String s) {
        if (s == null || s.trim().length() == 0) {
            return false;
        }

        String hex = UTF8Utils.bytesToHex(s.getBytes());
        System.out.println("full hex : " + hex);

        String firstChar = null;
        while (hex != null && hex.length() > 1) {
            firstChar = hex.substring(0, 1);
            System.out.println("firstChar : " + firstChar);

            if ("f".equals(firstChar)) {
                System.out.println("it is f start, it is 4 bytes, return.");
                return true;
            }

            if (hexMap.get(firstChar) == null) {
                System.out.println("it is f start, it is 4 bytes, return.");
                // todo, throw exception for this case
                return false;
            }

            hex = hex.substring(hexMap.get(firstChar), hex.length());
            System.out.println("remain hex : " + hex);
        }

        return false;
    }

    /**
     * 是否包含4字节UTF-8编码的字符
     * @param s 字符串
     * @return 是否包含4字节UTF-8编码的字符
     */
    public static boolean contains4BytesChar2(String s) {
        if (s == null || s.trim().length() == 0) {
            return false;
        }

        byte[] bytes = s.getBytes();

        if (bytes == null || bytes.length == 0) {
            return false;
        }

        int index = 0;
        byte b;
        String hex = null;
        String firstChar = null;
        int step;
        while (index <= bytes.length - 1) {
            System.out.println("while loop, index : " + index);
            b = bytes[index];

            hex = byteToHex(b);
            if (hex == null || hex.length() < 2) {
                System.out.println("fail to check whether contains 4 bytes char(1 byte hex char too short), default return false.");
                // todo, throw exception for this case
                return false;
            }

            firstChar = hex.substring(0, 1);

            if (firstChar.equals("f")) {
                return true;
            }

            if (byteMap.get(firstChar) == null) {
                System.out.println("fail to check whether contains 4 bytes char(no firstchar mapping), default return false.");
                // todo, throw exception for this case
                return false;
            }

            step = byteMap.get(firstChar);
            System.out.println("while loop, index : " + index + ", step : " + step);
            index = index + step;
        }

        return false;
    }

    /**
     * 去除4字节UTF-8编码的字符
     * @param s 字符串
     * @return 已去除4字节UTF-8编码的字符
     */
    public static byte[] remove4BytesUTF8Char(String s) {
        byte[] bytes = s.getBytes();
        byte[] removedBytes = new byte[bytes.length];
        int index = 0;

        String hex = null;
        String firstChar = null;
        for (int i = 0; i < bytes.length; ) {
            hex = UTF8Utils.byteToHex(bytes[i]);

            if (hex == null || hex.length() < 2) {
                System.out.println("fail to check whether contains 4 bytes char(1 byte hex char too short), default return false.");
                // todo, throw exception for this case
                return null;
            }

            firstChar = hex.substring(0, 1);

            if (byteMap.get(firstChar) == null) {
                System.out.println("fail to check whether contains 4 bytes char(no firstchar mapping), default return false.");
                // todo, throw exception for this case
                return null;
            }

            if (firstChar.equals("f")) {
                for (int j = 0; j < byteMap.get(firstChar); j++) {
                    i++;
                }
                continue;
            }

            for (int j = 0; j < byteMap.get(firstChar); j++) {
                removedBytes[index++] = bytes[i++];
            }
        }

        return Arrays.copyOfRange(removedBytes, 0, index);
    }

    /**
     * 将字符串的16进制转换为HEX,并按每个字符的16进制分隔格式化
     * @param s 字符串
     */
    public static String splitForReading(String s) {
        if (s == null || s.trim().length() == 0) {
            return "";
        }

        String hex = UTF8Utils.bytesToHex(s.getBytes());
        System.out.println("full hex : " + hex);

        if (hex == null || hex.length() == 0) {
            System.out.println("fail to translate the bytes to hex.");
            // todo, throw exception for this case
            return "";
        }

        StringBuilder sb = new StringBuilder();
        int index = 0;

        String firstChar = null;
        String splittedString = null;
        while (index < hex.length()) {
            firstChar = hex.substring(index, index + 1);

            if (hexMap.get(firstChar) == null) {
                System.out.println("fail to check whether contains 4 bytes char(no firstchar mapping), default return false.");
                // todo, throw exception for this case
                return "";
            }

            splittedString = hex.substring(index, index + hexMap.get(firstChar));
            sb.append(splittedString).append(" ");
            index = index + hexMap.get(firstChar);
        }

        System.out.println("formated sb : " + sb);
        return sb.toString();
    }

    /**
     * 字节数组转十六进制
     * @param bytes 字节数组
     * @return 十六进制
     */
    public static String bytesToHex(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            int r = bytes[i] & 0xFF;

            String hexResult = Integer.toHexString(r);
            if (hexResult.length() < 2) {
                sb.append(0); // 前补0
            }
            sb.append(hexResult);
        }

        return sb.toString();
    }

    /**
     * 字节转十六进制
     * @param b 字节
     * @return 十六进制
     */
    public static String byteToHex(byte b) {
        int r = b & 0xFF;
        String hexResult = Integer.toHexString(r);

        StringBuilder sb = new StringBuilder();
        if (hexResult.length() < 2) {
            sb.append(0); // 前补0
        }
        sb.append(hexResult);
        return sb.toString();
    }

}

在随便看下各种字符的UTF-8编码是什么:

package com.nicchagil.tc.emojifilter;

public class UTF8HexTester {

    public static void main(String[] args) {
        String s = "1";
        System.out.println("the hex of “" + s + "” : " + UTF8Utils.bytesToHex(s.getBytes()));

        s = "a";
        System.out.println("the hex of “" + s + "” : " + UTF8Utils.bytesToHex(s.getBytes()));

        s = "我";
        System.out.println("the hex of “" + s + "” : " + UTF8Utils.bytesToHex(s.getBytes()));

        s = "我很帅";
        System.out.println("the hex of “" + s + "” : " + UTF8Utils.bytesToHex(s.getBytes()));
    }

}

日志:

the hex of “1” : 31
the hex of “a” : 61
the hex of “我” : e68891
the hex of “我很帅” : e68891e5be88e5b885

> 搭建一个测试渠道来测试

由于emoji表情在PC不易输入,最好的输入途径始终在手机上,那么我们搭一个简单的web程序来接收emoji表情吧~

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Emoji</title>
</head>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.12.3.min.js"></script>
<body>

<form id="myform" action="http://192.168.1.3:8080/emoji/EmojiFilterServlet" >

    Input parameter :
    <input type='text' name='msg' />
    <br/>

    <input type='button' value=' ajax submit ' onclick="save();" />
    <input type='submit' value=' form submit ' />
</form>

</body>

<script type="text/javascript">

function save() {
    // alert('start save...');
    var data = $('#myform').serialize();
    // alert(data);

    $.ajax({
        type : "POST",
        url : "http://192.168.1.3:8080/emoji/EmojiFilterServlet",
        data : data,
        success : function(d) {
            alert(d);
        }
    });
}

</script>

</html>
package com.nicchagil.tc.emojifilter;

import java.io.IOException;
import java.nio.charset.Charset;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class EmojiFilterServlet
 */
public class EmojiFilterServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public EmojiFilterServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String msg = request.getParameter("msg");
        System.out.println("msg -> " + msg);
    }

}

【Java】如何检测、替换4个字节的utf-8编码(此范围编码包含emoji表情)的更多相关文章

  1. [Java] - 格式字符串替换方法

    Java 字符串格式替换方法有两种,一种是使用String.format(...),另一种是使用MessageFormat.format(...) 如下: import java.text.Messa ...

  2. Java的IO操作中有面向字节(Byte)和面向字符(Character)两种方式

    解析:Java的IO操作中有面向字节(Byte)和面向字符(Character)两种方式.面向字节的操作为以8位为单位对二进制的数据进行操作,对数据不进行转换,这些类都是InputStream和Out ...

  3. Java 敏感词过滤,Java 敏感词替换,Java 敏感词工具类

    Java 敏感词过滤,Java 敏感词替换,Java 敏感词工具类   =========================== ©Copyright 蕃薯耀 2017年9月25日 http://www ...

  4. Java一个汉字占几个字节(详解与原理)

    1.先说重点: 不同的编码格式占字节数是不同的,UTF-8编码下一个中文所占字节也是不确定的,可能是2个.3个.4个字节: 2.以下是源码: @Test public void test1() thr ...

  5. 【转】【异常处理】Incorrect string value: &#39;\xF0\x90\x8D\x83...&#39; for column... Emoji表情字符过滤的Java实现

    http://blog.csdn.net/shootyou/article/details/44852639 Emoji表情字符现在在APP已经广泛支持了.但是MySQL的UTF8编码对Emoji字符 ...

  6. emoji表情符处理替换成空格

    /**    * 用filterOffUtf8Mb4    * Description: 过滤率四个字节的utf-8字符(emoji表情符),替换成四个空格.    *         四字节utf- ...

  7. java IO之 编码 (码表 编码 解码 转换流)

    编码 什么是编码? 计算机中存储的都是二进制,但是要显示的时候,就是我们看到的却可以有中国 ,a  1 等字符 计算机中是没有存储字符的,但是我们却看到了.计算机在存储这些信息的时候,根据一个有规 则 ...

  8. java代码过滤emoji表情

    可以新建一个过滤器的类,在类中书写如下代码: public static String filterEmoji(String source) {           if(source != null ...

  9. java 20 - 9 带有缓冲区的字节输出流和字节输入流

    由之前字节输入的两个方式,我们可以发现,通过定义数组读取数组的方式比一个个字节读取的方式快得多. 所以,java就专门提供了带有缓冲区的字节类: 缓冲区类(高效类) 写数据:BufferedOutpu ...

随机推荐

  1. Linux 平台GCC使用小结

    gcc -Wall [-I search_headfile_path] [-L search_lib_path] sourcefile -lNAME -o exe-name -Wall选项打开所有最常 ...

  2. Android知识杂记(四)

    1.完整退出activity的设计思路 1.1 封装一个基础activity类 public abstract class RootActivity extends FragmentActivity{ ...

  3. flexBox布局 -- 兼容性

    //中间留空,两侧靠边,多行显示,每行3个,一个li的宽度是30%,最后一行,如果是两个的时候会出现异常布局,可以对最后一行的最后一个li进行right:35%,android4.4以上才支持, 所以 ...

  4. C# 多线程join的用法,等待多个子线程结束后再执行主线程

    等待多个子线程结束后再执行主线程 class MultiThread{ #region join test public void MultiThreadTest() { Thread[] ths = ...

  5. 解决GitLab提交MergeRequest时,提示502 GitLab is not responding.的问题

    最近使用GitLab提交MergeRequest时,提示502 GitLab is not responding. 使用gitlab-ctl tail查看错误信息如下: 2014/10/28 11:5 ...

  6. Free Slideshow, Gallery And Lightboxes Scripts

    http://bootstraphelpers.codeplex.com/SourceControl/list/changesets https://github.com/gordon-matt/Bo ...

  7. java 15 - 6 List的方法

    List集合的特有功能: A:添加功能 void add(int index,Object element):在指定索引处添加元素 B:获取功能 Object get(int index):获取指定索 ...

  8. python + selenium + PhantomJS 获取腾讯应用宝APP评论

    PhantomJS PhantomJS 是一个基于WebKit的服务器端JavaScript API,它无需浏览器的支持即可实现对Web的支持,且原生支持各种Web标准,如DOM 处理.JavaScr ...

  9. openstack中运行定时任务的两种方法及源代码分析

    启动一个进程,如要想要这个进程的某个方法定时得进行执行的话,在openstack有两种方式: 一种是通过继承 periodic_task.PeriodicTasks,另一种是使用loopingcall ...

  10. python xlrd和xlwtxlutils包的使用

    安装xlrd读取模块 首先去官网或者pypi下载安装包,然后解压到任意目录 在dos下进入该目录,执行python setup.py install安装 验证成功进入python,执行import 包 ...