Java版Eval <转>

本文介绍了一种改进的表达式求值算法,优化了负号的处理方式,解决了原有算法中可能出现的越界问题。通过引入BigDecimal和DecimalFormat类,实现了更精确的数值计算和字符串格式化,确保了算法的稳定性和准确性。

转自http://blog.youkuaiyun.com/laxsong/article/details/6873024 ,优化了负号的支持。 

原文中,对负号的支持不是太好,并且使用

  1. Double.toString(Double.parseDouble(d1)  
  2.                         / Double.parseDouble(d2)); 
  3. 在一些数据下运算可能会出现越界的情况。 
  4. 在此 也感谢 laxsong 的分享 。 

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Stack;

/**
 * Created by Heavenick on 2015/12/10.
 */
public class WinsonEval {
    private final static String ERR_NOT_END_VALID = "The last character of your expression MUST be '#'!";
    private final static String ERR_PARENTHESE_NOT_PAIR = "'(' & ')' should be used in pair!";
    private final static String ERR_CHAR_NOT_SUPPORT = "Not supported character";
    private final static String ERR_OPERATION_NOT_SUPPORTED = "Not supported operation";
    private final static String ERR_OPERATOR_NOT_VALID = " doesn't support double data!";
    private final static String ERR_UNKNOWN = "An unknown error!";

    private static boolean flag_double;

    /*
     * expression must be end with #
     */
    public static String eval(String expression) {

        ArrayList<String> list;
        try {
            list = toSuffixSequence(expression);
            System.out.println(list);
            return calculate(list);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static boolean isResultDouble() {
        return flag_double;
    }

    private static String calculate(ArrayList<String> list) throws Exception {
        Stack<String> stack = new Stack<String>();
        for (String s : list) {
            if (isOperator(s)) {
                String d1 = stack.pop();
                String d2 = stack.pop();
                String res = doCalc(d2, d1, s);
                stack.push(res);
            } else
                stack.push(s);
        }

        if (stack.size() == 1)
            return stack.pop();
        else
            throw new Exception(ERR_UNKNOWN);
    }

    private static String doCalc(String d1, String d2, String oper) throws Exception {
        if (oper != null && oper.length() > 1)
            throw new Exception(ERR_OPERATION_NOT_SUPPORTED + ":'" + oper + "'");

        switch (oper.charAt(0)) {
            case '+':
                return new BigDecimal(d1).add(new BigDecimal(d2)).toPlainString();
            case '-':
                return new BigDecimal(d1).subtract(new BigDecimal(d2)).toPlainString();
            case '*':
                return new BigDecimal(d1).multiply(new BigDecimal(d2)).toPlainString();
            case '/':
                return new DecimalFormat("####.#").format(new BigDecimal(d1).divide(new BigDecimal(d2),4,BigDecimal.ROUND_HALF_UP).doubleValue());
            case '%':
                return new BigDecimal(d1).divideAndRemainder(new BigDecimal(d2))[0].toPlainString();
            default:
                throw new Exception(ERR_OPERATION_NOT_SUPPORTED + ":'" + oper + "'");
        }
    }

    private static boolean isDoubleNeeded(String d1, String d2, String oper) {
        if (d1.contains(".") || d2.contains("."))
            return true;
        if (oper != null && oper.equals("/")) {
            int left = Integer.parseInt(d1) % Integer.parseInt(d2);
            if (left != 0)
                return true;
        }
        return false;
    }

    private static boolean isOperator(String str) {
        if (str != null
                && (str.equals("+") || str.equals("-") || str.equals("*")
                || str.equals("/") || str.equals("%")))
            return true;
        return false;
    }

    private static ArrayList<String> toSuffixSequence(String expression)
            throws Exception {

        if (!expression.endsWith("#"))
            throw new Exception(ERR_NOT_END_VALID);

        ArrayList<String> list = new ArrayList<String>();
        Stack<String> stack = new Stack<String>();
        stack.push("#");
        char last, ch;
        StringBuffer sb = null;
        for (int i = 0; i < expression.length(); i++) {
            ch = expression.charAt(i);
            switch (ch) {

                case '+':
                case '-':
                case '*':
                case '/':
                case '%':
                    last = stack.peek().charAt(0);

                    //TODO  如果当前字符不是“-” 并且前一个不是运算符,或者堆里面的数据数大于等于2个,才将 运算符压入堆中
                    if( ! ( ch == '-' && isOperator(last+"") ) ||  list.size() >=2 ) {
                        if (last != '(' && priority(last) >= priority(ch))
                            list.add(stack.pop());
                    }
                    stack.push("" + ch);
                    break;
                case '(':
                    stack.push("(");
                    break;
                case ')':
                    while (!stack.isEmpty() && stack.peek().charAt(0) != '(')
                        list.add(stack.pop());
                    if (stack.isEmpty() || stack.size() == 1)
                        throw new Exception(ERR_PARENTHESE_NOT_PAIR);
                    stack.pop();
                    break;
                case '#':
                    while (stack.size() > 1 && stack.peek().charAt(0) != '(')
                        list.add(stack.pop());
                    if (stack.size() > 1)
                        throw new Exception(ERR_PARENTHESE_NOT_PAIR);
                    break;
                default:
                    if (Character.isDigit(ch) || '.' == ch) {
                        sb = new StringBuffer();
                        sb.append(ch);
                        while (Character.isDigit(expression.charAt(i + 1)) || expression.charAt(i + 1) == '.')
                            sb.append(expression.charAt(++i));

                        String ls = stack.pop();
                        String n = sb.toString();

                        // 如果前一个字符是“-” 将进行判断,是不是负号
                        if("-".equals(ls)){
                            if( ("#".equals(stack.peek()) && list.size() == 0) || (isOperator(stack.peek()) )) {
                                n = "-" + n;
                            }else{
                                stack.push("-");
                            }
                        }else{
                            stack.push(ls);
                        }

                        list.add(n);

                        break;
                    } else
                        throw new Exception(ERR_CHAR_NOT_SUPPORT + ":'" + ch + "'");

            }
        }
        return list;
    }

    private static int priority(char ch) {
        switch (ch) {
            case '+':
            case '-':
                return 1;
            case '*':
            case '/':
            case '%':
                return 2;
            case '#':
                return 0;
            default:
                return 0;
        }
    }

    public static void main(String[] args) {
        String expression = "2+-7#";
        try {
            System.out.println(WinsonEval.eval("(2*2+6)/5+3#").toString());
            System.out.println(WinsonEval.eval("2+-7#"));
            System.out.println(WinsonEval.eval("2-7#"));
            System.out.println(WinsonEval.eval("-2+7#"));
            System.out.println(WinsonEval.eval("3/6#"));
            System.out.println(WinsonEval.eval("3/2#"));
            System.out.println(WinsonEval.eval("2/3#"));
            System.out.println(WinsonEval.eval("12*15.12-12#"));
            System.out.println(WinsonEval.eval("(2*-2+6)/5+3#"));

//            && !StringUtils.containsAny(stack.peek(),new char[]{'*','/'})
        } catch (Exception e) {
            e.printStackTrace();
        }

//      System.out.println(Character.getType('.'));
//      System.out.println(Character.getType('1'));
    }


<think>我们正在处理用户的问题:关于CryptoJS.enc.Utf8.stringify方法报错“Uncaught Error: Malformed UTF-8 data”的原因。 根据之前的引用和用户的问题,我们可以总结出以下几个可能的原因: 1. **数据不是有效的UTF-8编码**: - 当使用`CryptoJS.enc.Utf8.stringify`方法时,如果传入的WordArray中包含的字节序列不符合UTF-8编码规则,就会抛出这个错误。 - 例如,在解密过程中,如果解密得到的字节序列不是有效的UTF-8字符串(可能是解密密钥错误导致解密出来的数据乱码),那么在尝试换为UTF-8字符串时就会报错。 2. **解密失败导致的数据损坏**: - 引用[2]中提到,在Java加密、JS解密的场景中,如果加密和解密的密钥、模式或填充方式不一致,就会导致解密失败,得到的字节数据不是原始明文字节,因此无法被正确解析为UTF-8字符串[^2]。 3. **数据中包含空格或其他不可见字符**: - 引用[4]中提到,当加密后的数据被存储到文本文件再读取时,可能会引入空格或其他不可见字符,导致数据被破坏。在解密前,使用正则表达式`replace(/\s/g, '')`去除空格后,解密成功[^4]。 4. **编码方式不一致**: - 在加密和解密过程中,如果编码方式不一致,比如加密时使用Base64,解密时却用UTF-8去解析,就会导致错误。同样,在解密后,将字节数据换为字符串时,如果字节数据不是有效的UTF-8,也会报错。 5. **密钥解析错误**: - 引用[3]中提到,在解析密钥时,如果密钥字符串的编码方式不正确,也会导致错误。例如,如果密钥原本是UTF-8字符串,却被错误地解析为Hex格式,那么解密就会失败,进而导致后续的字符串换失败[^3]。 针对用户提供的代码: `var key = CryptoJS.enc.Utf8.stringify(key_wordArray);` `var iv = CryptoJS.enc.Utf8.stringify(iv_wordArray);` 报错的原因是:`key_wordArray`或`iv_wordArray`中的字节数据不是有效的UTF-8编码。 具体分析如下: - `key_wordArray`和`iv_wordArray`应该是通过CryptoJS解密或加密过程中生成的WordArray对象。 - 当我们使用`CryptoJS.enc.Utf8.stringify`将WordArray换为字符串时,CryptoJS会尝试将WordArray中的字节按照UTF-8编码规则解析成字符串。 - 如果这些字节序列不符合UTF-8编码规则(例如,解密失败得到了错误的字节,或者这些字节原本就是二进制数据而不是字符串),那么就会抛出“Malformed UTF-8 data”错误。 解决方案建议: 1. **检查解密过程**:确保解密操作正确,解密后的数据是原始明文字节。可以通过检查解密密钥、模式、填充方式等是否与加密端一致。 2. **确认数据编码**:如果原始数据不是UTF-8编码的字符串(例如可能是二进制数据),则不应该使用`Utf8.stringify`,而应该使用其他方式处理,比如直接使用`CryptoJS.enc.Hex`或`Base64`进行换。 3. **去除额外字符**:如果数据是从外部(如文件、网络)读取的,可能包含空格或换行符,需要先清除这些非数据字符。 4. **调试**:在换之前,可以先输出WordArray的内容(例如通过`CryptoJS.enc.Hex.stringify(key_wordArray)`为十六进制字符串),查看其内容是否符合预期。 示例调试代码: ```javascript // 将WordArray为十六进制字符串,以便查看原始字节 var keyHex = CryptoJS.enc.Hex.stringify(key_wordArray); console.log("Key in Hex: ", keyHex); // 同样对iv_wordArray var ivHex = CryptoJS.enc.Hex.stringify(iv_wordArray); console.log("IV in Hex: ", ivHex); ``` 5. **确保密钥和IV的正确性**:检查生成密钥和IV的源码,确保它们是以正确的编码方式解析的。例如,如果密钥是十六进制字符串,应该用`CryptoJS.enc.Hex.parse`,而不是`CryptoJS.enc.Utf8.parse`。 6. **使用Base64编码**:如果数据是二进制的,可以考虑使用Base64编码来避免UTF-8解析错误: ```javascript var keyBase64 = CryptoJS.enc.Base64.stringify(key_wordArray); var ivBase64 = CryptoJS.enc.Base64.stringify(iv_wordArray); ``` 最后,根据引用[3]的示例,正确的密钥解析方式很重要,比如: ```javascript // 正确:使用Utf8解析UTF-8字符串密钥 var key = CryptoJS.enc.Utf8.parse("Dt8j9wGw%6HbxfFn"); ``` 总结:错误的原因是WordArray中包含的字节数据不符合UTF-8编码规范,可能由于解密失败、数据损坏、编码不一致等原因造成。 </think>`CryptoJS.enc.Utf8.stringify()` 报错 **`Uncaught Error: Malformed UTF-8 data`** 的根本原因是**输入数据包含无效的 UTF-8 字节序列**。结合引用内容和代码逻辑,具体原因可分为以下四类: --- ### 一、核心原因:无效的字节编码 1. **非 UTF-8 原始数据** 当 `key_wordArray` 或 `iv_wordArray` 包含非 UTF-8 编码的二进制数据时: ```javascript // 错误示例:WordArray 包含 AES 解密后的二进制数据(非 UTF-8) const bytes = CryptoJS.AES.decrypt(ciphertext, key); const decrypted = bytes.toString(CryptoJS.enc.Utf8); // ❌ 可能报错 ``` - 解密结果可能是任意二进制数据(如加密的密钥/IV),不一定是 UTF-8 字符串 - **解决方案**:改用二进制或十六进制编码输出: ```javascript const keyHex = CryptoJS.enc.Hex.stringify(key_wordArray); // ✅ 安全 ``` 2. **字节序列不完整** UTF-8 要求多字节字符的序列完整(如中文需 3 字节)。若 WordArray 被截断: - 例如:`[0xE4, 0xB8]` 是 "中" 字的前 2 字节(缺少 1 字节) - **验证方法**:检查字节长度是否 3 的倍数(常见于中文场景) --- ### 二、数据污染问题(引用[4]) 1. **不可见字符混入** - 从文件/网络读取数据时,可能混入空格、换行符等(尤其 Base64 数据): ```javascript // 错误示例:从文件读取的加密数据含空格 const dataFromFile = "U2FsdGVkX1 8v4s5..."; // 含空格 const bytes = CryptoJS.AES.decrypt(dataFromFile, key); // ❌ 解密失败 ``` - **解决方案**:清除空白字符(引用[4]): ```javascript const cleanData = encryptedData.replace(/\s/g, ''); // ✅ 移除所有空格 ``` 2. **编码不一致** - 加密用 Base64,解密误用 UTF-8: ```java // Java 加密端(引用[2]) Base64.encodeBase64String(cipher.doFinal(data)); // ✅ Base64 输出 ``` ```javascript // JS 解密端(错误) CryptoJS.enc.Utf8.parse(encryptedData); // ❌ 应使用 Base64 ``` --- ### 三、密钥/IV 处理错误(引用[3]) 1. **密钥解析方式错误** ```javascript // 错误:Hex 密钥误用 UTF-8 解析(引用[3]) const key = CryptoJS.enc.Utf8.parse("4a656665"); // ❌ "4a656665" 是 Hex 字符串 ``` **正确做法**: ```javascript const key = CryptoJS.enc.Hex.parse("4a656665"); // ✅ ``` 2. **密钥包含特殊字符** ```javascript // 含 % 的密钥需用 UTF-8 解析(引用[3]) const key = CryptoJS.enc.Utf8.parse("Dt8j9wGw%6HbxfFn"); // ✅ ``` --- ### 四、解决方案总结 | 场景 | 错误示例 | 正确方案 | |------|----------|----------| | **解密二进制数据** | `Utf8.stringify(decryptedBytes)` | `Hex.stringify(decryptedBytes)` | | **读取外部数据** | 未清洗含空格数据 | `data.replace(/\s/g, '')` | | **Hex 密钥处理** | `Utf8.parse("4a656665")` | `Hex.parse("4a656665")` | | **含特殊字符密钥** | `Hex.parse("key%123")` | `Utf8.parse("key%123")` | > **关键调试建议**: > 使用 `CryptoJS.enc.Hex.stringify(wordArray)` 输出原始字节,验证是否为合法 UTF-8 序列。 --- ### 相关问题 1. 如何检测 CryptoJS 的 WordArray 是否包含有效 UTF-8 数据? 2. CryptoJS 中不同类型的编码(Hex/Base64/UTF-8)在解析密钥时有何差异? 3. 当 AES 解密后得到乱码数据时,如何区分是密钥错误还是编码错误? 4. 如何处理从文件读取加密数据时引入的非可见字符污染问题?[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值