整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Java中的BigDecimal,你真的会用吗?最强

Java中的BigDecimal,你真的会用吗?最强指南

者:LanceToBigData

原文:https://www.cnblogs.com/zhangyinhua/p/11545305.html

一、BigDecimal概述

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。

一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用BigDecimal类来操作。

BigDecimal所创建的是对象,故我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

二、BigDecimal常用构造函数

2.1、常用构造函数

  • BigDecimal(int)

创建一个具有参数所指定整数值的对象

  • BigDecimal(double)

创建一个具有参数所指定双精度值的对象

  • BigDecimal(long)

创建一个具有参数所指定长整数值的对象

  • BigDecimal(String)

创建一个具有参数所指定以字符串表示的数值的对象

2.2、使用问题分析

使用示例:

BigDecimal a =new BigDecimal(0.1);
System.out.println("a values is:"+a);
System.out.println("=====================");
BigDecimal b =new BigDecimal("0.1");
System.out.println("b values is:"+b);

结果示例:

a values is:0.1000000000000000055511151231257827021181583404541015625=====================b values is:0.1

原因分析:

1)参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

2)String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言, 通常建议优先使用String构造方法。

3)当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。Java知音公众号内回复“面试题聚合”,送你一份面试题宝典

三、BigDecimal常用方法详解

3.1、常用方法

  • add(BigDecimal)

BigDecimal对象中的值相加,返回BigDecimal对象

  • subtract(BigDecimal)

BigDecimal对象中的值相减,返回BigDecimal对象

  • multiply(BigDecimal)

BigDecimal对象中的值相乘,返回BigDecimal对象

  • divide(BigDecimal)

BigDecimal对象中的值相除,返回BigDecimal对象

  • toString()

将BigDecimal对象中的值转换成字符串

  • doubleValue()

将BigDecimal对象中的值转换成双精度数

  • floatValue()

将BigDecimal对象中的值转换成单精度数

  • longValue()

将BigDecimal对象中的值转换成长整数

  • intValue()

将BigDecimal对象中的值转换成整数

3.2、BigDecimal大小比较

java中对BigDecimal比较大小一般用的是bigdemical的compareTo方法

int a = bigdemical.compareTo(bigdemical2)

返回结果分析:

a = -1,表示bigdemical小于bigdemical2;
a = 0,表示bigdemical等于bigdemical2;
a = 1,表示bigdemical大于bigdemical2;

举例:a大于等于b

new bigdemica(a).compareTo(new bigdemical(b)) >= 0

四、BigDecimal格式化

由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。

以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用 
NumberFormat percent = NumberFormat.getPercentInstance();  //建立百分比格式化引用 
percent.setMaximumFractionDigits(3); //百分比小数点最多3位 

BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
BigDecimal interestRate = new BigDecimal("0.008"); //利率   
BigDecimal interest = loanAmount.multiply(interestRate); //相乘

System.out.println("贷款金额:\t" + currency.format(loanAmount)); 
System.out.println("利率:\t" + percent.format(interestRate)); 
System.out.println("利息:\t" + currency.format(interest)); 

结果:

贷款金额: ¥15,000.48 利率: 0.8% 利息: ¥120.00

BigDecimal格式化保留2为小数,不足则补0:

public class NumberFormat {

    public static void main(String[] s){
        System.out.println(formatToNumber(new BigDecimal("3.435")));
        System.out.println(formatToNumber(new BigDecimal(0)));
        System.out.println(formatToNumber(new BigDecimal("0.00")));
        System.out.println(formatToNumber(new BigDecimal("0.001")));
        System.out.println(formatToNumber(new BigDecimal("0.006")));
        System.out.println(formatToNumber(new BigDecimal("0.206")));
    }
    /**
     * @desc 1.0~1之间的BigDecimal小数,格式化后失去前面的0,则前面直接加上0。
     * 2.传入的参数等于0,则直接返回字符串"0.00"
     * 3.大于1的小数,直接格式化返回字符串
     * @param obj传入的小数
     * @return
     */
    public static String formatToNumber(BigDecimal obj) {
        DecimalFormat df = new DecimalFormat("#.00");
        if(obj.compareTo(BigDecimal.ZERO)==0) {
            return "0.00";
        }else if(obj.compareTo(BigDecimal.ZERO)>0&&obj.compareTo(new BigDecimal(1))<0){
            return "0"+df.format(obj).toString();
        }else {
            return df.format(obj).toString();
        }
    }
}

结果为:

3.44
0.00
0.00
0.00
0.01
0.21

五、BigDecimal常见异常

5.1、除法的时候出现异常

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result

原因分析:

通过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

解决方法:

divide方法设置精确的小数点,如:divide(xxxxx,2)

六、BigDecimal总结

6.1、总结

在需要精确的小数计算时再使用BigDecimal,BigDecimal的性能比double和float差,在处理庞大,复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal。尽量使用参数类型为String的构造函数。

BigDecimal都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。

6.2、工具类推荐

package com.vivo.ars.util;
import java.math.BigDecimal;

/**
 * 用于高精确处理常用的数学运算
 */
public class ArithmeticUtils {
    //默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;

    /**
     * 提供精确的加法运算
     *
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */

    public static double add(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2).doubleValue();
    }

    /**
     * 提供精确的加法运算
     *
     * @param v1 被加数
     * @param v2 加数
     * @return 两个参数的和
     */
    public static BigDecimal add(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.add(b2);
    }

    /**
     * 提供精确的加法运算
     *
     * @param v1    被加数
     * @param v2    加数
     * @param scale 保留scale 位小数
     * @return 两个参数的和
     */
    public static String add(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的减法运算
     *
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public static double sub(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2).doubleValue();
    }

    /**
     * 提供精确的减法运算。
     *
     * @param v1 被减数
     * @param v2 减数
     * @return 两个参数的差
     */
    public static BigDecimal sub(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.subtract(b2);
    }

    /**
     * 提供精确的减法运算
     *
     * @param v1    被减数
     * @param v2    减数
     * @param scale 保留scale 位小数
     * @return 两个参数的差
     */
    public static String sub(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的乘法运算
     *
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public static double mul(double v1, double v2) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2).doubleValue();
    }

    /**
     * 提供精确的乘法运算
     *
     * @param v1 被乘数
     * @param v2 乘数
     * @return 两个参数的积
     */
    public static BigDecimal mul(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.multiply(b2);
    }

    /**
     * 提供精确的乘法运算
     *
     * @param v1    被乘数
     * @param v2    乘数
     * @param scale 保留scale 位小数
     * @return 两个参数的积
     */
    public static double mul(double v1, double v2, int scale) {
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return round(b1.multiply(b2).doubleValue(), scale);
    }

    /**
     * 提供精确的乘法运算
     *
     * @param v1    被乘数
     * @param v2    乘数
     * @param scale 保留scale 位小数
     * @return 两个参数的积
     */
    public static String mul(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
     * 小数点以后10位,以后的数字四舍五入
     *
     * @param v1 被除数
     * @param v2 除数
     * @return 两个参数的商
     */

    public static double div(double v1, double v2) {
        return div(v1, v2, DEF_DIV_SCALE);
    }

    /**
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
     * 定精度,以后的数字四舍五入
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    public static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
     * 定精度,以后的数字四舍五入
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示需要精确到小数点以后几位
     * @return 两个参数的商
     */
    public static String div(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v1);
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 提供精确的小数位四舍五入处理
     *
     * @param v     需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static double round(double v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(Double.toString(v));
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    /**
     * 提供精确的小数位四舍五入处理
     *
     * @param v     需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static String round(String v, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b = new BigDecimal(v);
        return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 取余数
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 小数点后保留几位
     * @return 余数
     */
    public static String remainder(String v1, String v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
    }

    /**
     * 取余数  BigDecimal
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 小数点后保留几位
     * @return 余数
     */
    public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
    }

    /**
     * 比较大小
     *
     * @param v1 被比较数
     * @param v2 比较数
     * @return 如果v1 大于v2 则 返回true 否则false
     */
    public static boolean compare(String v1, String v2) {
        BigDecimal b1 = new BigDecimal(v1);
        BigDecimal b2 = new BigDecimal(v2);
        int bj = b1.compareTo(b2);
        boolean res;
        if (bj > 0)
            res = true;
        else
            res = false;
        return res;
    }
}


END

1章 引言

1.1 字符编码在信息技术中的地位

1.1.1 从ASCII到Unicode:字符集的发展历程

在信息时代黎明之初,ASCII编码作为最早的标准字符集 ,仅包含128个字符,足以覆盖英文和其他西欧语言。然而,随着互联网的全球化发展,单一的ASCII编码已无法满足多元文化的交流需求。于是,Unicode字符集应运而生 ,它囊括了世界上几乎所有的书写系统,将全球的语言文字统一在一个巨大的编码空间内。Unicode不仅包含ASCII字符 ,还包括拉丁字母变体、东亚汉字、中东阿拉伯文等多种字符,为实现跨文化的信息传递奠定了坚实的基础。

# 示例代码:ASCII与Unicode的对比
ascii_str='Hello, World!'
unicode_str='你好 ,世界!'
print(len(ascii_str.encode('ascii')))  # 输出13,ASCII编码每个字符占一个字节
print(len(unicode_str.encode('utf-8')))  # 输出13,UTF-8编码下英文字符占一个字节 ,中文字符占三个字节

1.1.2 多语种支持与国际化的现实需求

在全球互联的今天,无论是网页浏览、电子邮件收发,还是数据库存储、文件传输,都需要依赖统一的字符编码来确保信息的准确无误。特别是在软件开发领域,为了实现跨平台、跨地区的无缝协作,程序员必须精通字符串编码的相关知识,确保程序能够正确处理各种语言环境下的文本数据。

1.2 Python对字符串编码的支持与规范

1.2.1 Python 2与Python 3的字符串处理差异

在Python 2中,默认字符串类型既可以是ASCII编码的 ,也可以是Unicode编码的,这取决于字符串前是否带有u前缀。而Python 3则更为简化和严谨 ,所有文本字符串均为Unicode编码,以str类型表示,而原始的二进制数据则由新的bytes类型表示。

# Python 2示例
py2_ascii_str='Hello'
py2_unicode_str=u'你好'

# Python 3示例
py3_str='你好'  # 默认为Unicode字符串
py3_bytes=b'Hello'  # 二进制数据,需通过encode()转化为bytes

1.2.2 Python对于Unicode的内建支持

Python以其对Unicode的出色支持而著称,内建的字符串方法如encode()decode()使得在Unicode与指定编码间转换变得简单易行。同时,Python还提供了诸如unicodedata模块,可以查询特定Unicode字符的详细属性,以及处理如规范化、排序等更复杂的问题。

通过深入理解Python对字符串编码的支持,开发者能够在面对多语言环境时游刃有余 ,从而编写出更加健壮、兼容性强的应用程序。接下来的文章将进一步探讨计算机科学基础、编码原理及Python中实际的编码操作。

第2章 计算机科学基础与字符编码原理

2.1 计算机存储与二进制表示

2.1.1 数字、字符与二进制编码的关系

计算机内部采用二进制形式存储和处理信息。数字、字符等数据在计算机中均被转化为一串二进制数。例如,十进制数13转换为二进制为1101 ,字符A在ASCII编码中对应的二进制值为01000001。这种数字化的过程确保了计算机能够高效、准确地处理各类数据。

# 示例代码:数字与字符的二进制表示
import binascii

decimal_number=13
binary_number=bin(decimal_number)[2:]  # 二进制表示 ,去掉前缀'0b'
print(binary_number)  # 输出:1101

char='A'
ascii_value=ord(char)
binary_char=binascii.hexlify(char.encode('ascii')).decode()  # 将ASCII编码的字节转换为十六进制字符串
print(binary_char)  # 输出:41(十六进制表示,对应二进制01000001)

2.1.2 字节、字节序与多字节字符编码

在计算机中,基本的数据存储单元是字节(byte) ,通常包含8位二进制数。对于单字节编码如ASCII,一个字节足以表示一个字符。然而,对于包含大量字符的编码如Unicode ,一个字符可能需要多个字节来存储。此外,字节序(endianness)决定了多字节数据在内存中的排列顺序 ,分为大端序(高位字节在前)和小端序(低位字节在前)。

# 示例代码:多字节字符编码与字节序
unicode_char='汉'
utf8_encoded=unicode_char.encode('utf-8')  # UTF-8编码下,'汉'占用三个字节
print(utf8_encoded)  # 输出:b'\xe6\xb1\x89'

# 字节序演示(此处以大端序为例)
multi_byte_number=0x12345678  # 假设这是一个多字节整数
big_endian_bytes=multi_byte_number.to_bytes(4, byteorder='big')
print(big_endian_bytes)  # 输出:b'\x12\x34\x56\x78'

2.2 字符编码标准与常见编码方式

2.2.1 ASCII编码

ASCII编码是最基础的字符编码标准,包含128个字符 ,包括英文字母、数字、标点符号等 ,每个字符用一个字节表示。由于其简洁性和广泛接受度,ASCII编码至今仍被许多系统和协议作为基础字符集。

# 示例代码:ASCII编码示例
ascii_text='Hello, World!'
ascii_encoded=ascii_text.encode('ascii')
print(ascii_encoded)  # 输出:b'Hello, World!'

2.2.2 ISO-8859系列与地区性扩展

ISO-8859系列编码是对ASCII的扩展,旨在支持更多欧洲语言字符。每个ISO-8859编码(如ISO-8859-1、ISO-8859-2等)覆盖特定区域的语言 ,但总字符数量仍限制在256个以内,每个字符仍占用一个字节。

# 示例代码:ISO-8859-1编码示例
latin1_text='?Hola, mundo!'
latin1_encoded=latin1_text.encode('iso-8859-1')
print(latin1_encoded)  # 输出:b'\xa1Hola, mundo!'

2.2.3 Unicode编码体系

Unicode编码是一个庞大的字符集,包含了世界上几乎所有已知的书写系统。Unicode定义了统一码点(Unicode code point) ,每个码点对应一个字符。常见的Unicode编码方式包括UTF-8、UTF-16和UTF-32,它们以不同的字节数量和方式存储同一Unicode码点。

2.2.4 UTF-8及其他UTF变体

UTF-8是最常用的Unicode编码方式,其特点在于可变长编码 ,英文字符占用一个字节,其他字符根据需要使用1到4个字节。UTF-16和UTF-32则分别使用固定长度的2字节和4字节表示Unicode码点。这些UTF变体的选择主要取决于应用场景和性能需求。

# 示例代码:UTF-8编码示例
utf8_text='你好 ,世界!'
utf8_encoded=utf8_text.encode('utf-8')
print(utf8_encoded)  # 输出:b'\xe4\xbd\xa0\xe5\xa5\xbd,\xe4\xb8\x96\xe7\x95\x8c!\n'

通过深入理解计算机存储原理、字符编码标准及其相互关系,开发者能够更好地应对各种字符编码问题 ,为后续章节中Python中的字符串编码操作奠定坚实基础。

第3章 Python中的字符串类型与编码感知

3.1 Python字符串类型简介

3.1.1str类型与Unicode字符串

在Python中,str类型用于表示文本字符串,自Python 3起 ,str类型默认采用Unicode编码,这意味着它可以容纳全世界范围内的字符。每个Unicode字符都有一个唯一的码点(code point),可以通过\u\U前缀在字符串中直接引用:

# 示例代码:Unicode码点表示
unicode_char='\u4f60\u597d'  # 这两个Unicode码点代表“你好”
print(unicode_char)  # 输出:“你好”

long_unicode_char='\U0001F600'  # 这个Unicode码点代表笑脸表情
print(long_unicode_char)  # 输出:

3.1.2bytes类型与二进制数据

str类型相对的是bytes类型,它表示的是不可变的字节序列 ,主要用于存储原始的二进制数据或经过编码后的文本数据。在处理文件读写、网络通信等场景时尤为关键:

# 示例代码:创建并操作bytes对象
binary_data=b'Hello, World!'  # 创建一个bytes对象
print(binary_data)  # 输出:b'Hello, World!'
encoded_text='你好,世界!'.encode('utf-8')  # 将Unicode字符串编码为bytes
print(encoded_text)  # 输出:b'\xe4\xbd\xa0\xe5\xa5\xbd,\xe4\xb8\x96\xe7\x95\x8c!'

3.2 Python字符串的编码标识与默认编码

3.2.1 文件编码声明与源代码编码

Python源代码文件开头通常有一行特殊的注释来声明文件的编码,例如# -*- coding: utf-8 -*-。这有助于解释器正确解析含有非ASCII字符的源代码:

# encoding=utf-8
message="你好,世界!"
print(message)

对于Python脚本处理的外部文件,也需要明确其编码格式,可通过open()函数的encoding参数指定:

with open('example.txt', 'r', encoding='utf-8') as file:
    content=file.read()
    print(content)

3.2.2 环境变量与系统默认编码

Python运行环境的默认编码可通过sys.getdefaultencoding()获取,但它并不直接影响str类型的字符串,而是影响如何将字符串转换为bytes类型。另外,操作系统环境变量如PYTHONIOENCODING可以在一定程度上影响Python处理I/O时的编码行为。

通过深入了解Python字符串类型与编码感知机制,我们可以更好地掌握字符串在内存中的表示方式 ,并在实际应用中灵活处理各种编码问题 ,为进一步探讨Python字符串的编码操作打下基础。

第4章 Python字符串的编码操作

4.1 字符串到字节序列的编码(encode()方法)

4.1.1encode()方法的基本用法

Python的str对象提供了encode()方法,用于将Unicode字符串转换为指定编码的bytes对象。基本语法如下:

encoded_bytes=unicode_string.encode(encoding, errors='...')

其中,encoding参数指定目标编码方式(如utf-8gbk等),errors参数可选,用于指定遇到无法编码的字符时的处理策略,如strict(抛出异常)、ignore(忽略该字符)、replace(用特殊字符替换)等。

4.1.2 编码参数详解:编码方式、错误处理策略

不同的编码方式决定了Unicode字符如何映射到字节序列。例如,UTF-8是一种变长编码,英文字符占用一个字节,其他字符可能占用多个字节。错误处理策略的选择会影响遇到非法字符或无法编码的字符时程序的行为。

# 示例代码:不同编码方式与错误处理策略的对比
unicode_str='你好 ,世界!'

# 使用UTF-8编码 ,错误处理策略为"strict"
utf8_strict=unicode_str.encode('utf-8', errors='strict')
print(utf8_strict)

# 使用GBK编码,错误处理策略为"ignore"
gbk_ignore=unicode_str.encode('gbk', errors='ignore')
print(gbk_ignore)

# 使用Latin-1编码 ,错误处理策略为"replace"
latin1_replace=unicode_str.encode('latin-1', errors='replace')
print(latin1_replace)

4.1.3 实例演示:不同编码方式下的字符串转换

以下代码展示了同一Unicode字符串使用不同编码方式(UTF-8、GBK、Latin-1)进行编码后的结果差异:

# 示例代码:不同编码方式下的字符串转换
unicode_str='你好,世界!'

utf8_encoded=unicode_str.encode('utf-8')
gbk_encoded=unicode_str.encode('gbk')
latin1_encoded=unicode_str.encode('latin-1')

print('UTF-8编码:', utf8_encoded)
print('GBK编码:', gbk_encoded)
print('Latin-1编码:', latin1_encoded)

4.2 字节序列到字符串的解码(decode()方法)

4.2.1decode()方法的基本用法

encode()方法相对应 ,bytes对象提供了decode()方法,用于将字节序列还原为Unicode字符串。基本语法如下:

decoded_unicode=bytes_sequence.decode(encoding, errors='...')

其中 ,encoding参数指定字节序列的原始编码方式,errors参数同上,用于指定遇到无法解码的字节序列时的处理策略。

4.2.2 解码参数详解:编码识别、错误处理策略

解码时,准确识别字节序列的原始编码至关重要。若编码方式不明,可以尝试使用编码检测工具(如chardet库)。错误处理策略的选择同样影响程序在遇到解码错误时的行为。

# 示例代码:不同编码方式的字节序列解码
utf8_bytes=b'\xe4\xbd\xa0\xe5\xa5\xbd,\xe4\xb8\x96\xe7\x95\x8c!'
gbk_bytes=b'\xc4\xe3\xba\xc3,\xb5\xc4\xcb\xf3!'

utf8_decoded=utf8_bytes.decode('utf-8')
gbk_decoded=gbk_bytes.decode('gbk')

print('UTF-8字节序列解码:', utf8_decoded)
print('GBK字节序列解码:', gbk_decoded)

4.2.3 实例演示:修复未知编码的文本数据

在实际应用中,我们可能会遇到未知编码的文本数据。这时,可以利用编码检测库(如chardet)辅助确定编码,然后使用正确的编码方式进行解码:

import chardet

# 假设这是未知编码的字节数据
unknown_bytes=b'\xc4\xe3\xba\xc3,\xb5\xc4\xcb\xf3!'

# 使用chardet检测编码
detected_encoding=chardet.detect(unknown_bytes)['encoding']

# 根据检测结果解码
decoded_text=unknown_bytes.decode(detected_encoding)
print('修复后的文本:', decoded_text)

熟练掌握Python字符串的编码与解码操作,不仅能帮助我们解决日常编程中的字符编码问题,还能为处理多语言数据、处理遗留数据、以及与其他系统交互提供有力支持。后续章节将进一步探讨编码相关的Python库与工具 ,以及在实际项目开发中的编码最佳实践。

第5章 高级主题:编码相关的Python库与工具

5.1chardet库:自动检测文本编码

5.1.1chardet的基本用法与原理

chardet是一个强大的字符编码检测库,通过统计分析和概率模型识别文本的编码方式。在处理来源不明的文件或网络数据时,这个库能够快速准确地推测出文本的编码类型。

import chardet

# 示例代码:检测未知编码的文本数据
unknown_encoded_text=b'\xef\xbb\xbfHello, \xe4\xb8\x96\xe7\x95\x8c!'
encoding_detected=chardet.detect(unknown_encoded_text)['encoding']
decoded_text=unknown_encoded_text.decode(encoding_detected)
print(decoded_text)  # 输出:'Hello, 世界!'

5.1.2 应用场景:处理未知编码的文件或网络数据

在实际开发中 ,我们经常会遇到需要处理多种编码格式的文本数据。例如,从Web抓取的数据、用户上传的文件或旧系统迁移过来的数据。此时 ,chardet可以帮助我们自动识别文本编码,避免因编码不匹配导致的乱码或错误。

5.2codecs模块:底层编码接口与高级功能

5.2.1codecs模块提供的编码函数与类

Python的codecs模块提供了丰富的编码/解码函数和类,可以进行更为精细和低级别的字符编码控制。例如,codecs.open()可用于打开和读写指定编码的文件;IncrementalDecoderIncrementalEncoder类允许逐块处理编码和解码,适合大数据流的实时处理。

import codecs

# 示例代码:使用codecs模块读取和写入UTF-8编码的文件
with codecs.open('example.txt', 'r', encoding='utf-8') as f:
    content=f.read()
    
with codecs.open('output.txt', 'w', encoding='utf-8') as f:
    f.write(content)

5.2.2 使用codecs处理特殊编码任务

对于一些特殊的编码需求,比如读取带BOM的UTF-8文件或者处理编码边界条件等,codecs模块也能提供有效解决方案。例如,使用StreamReaderStreamWriter可以透明地处理BOM和编码转换。

5.3 其他相关库与工具简介

5.3.1iconv与cchardet等第三方工具

除了Python内置的codecs模块,还有如iconv这样的命令行工具以及cchardet这样的C语言实现的高性能编码检测库,它们在处理大规模数据或追求极致性能时有着独特的价值。

# cchardet示例(假设已经安装)
import cchardet

# 同样检测未知编码的文本数据
result=cchardet.detect(unknown_encoded_text)
print(result['encoding'])  # 输出:'utf-8-sig'

5.3.2textwrap、unicodedata等内置模块在编码处理中的应用

Python内置的textwrap模块常用于文本排版 ,虽然并非专门处理编码,但在显示多语言文本时十分有用。而unicodedata模块提供了访问Unicode字符数据库的功能 ,可用于获取字符的各种属性和分类,有助于处理编码相关的复杂问题。

通过掌握这些Python库与工具 ,开发者可以更高效地处理编码相关任务,提升软件的健壮性和兼容性,在面对编码问题时具备更强的解决能力。在接下来的章节中,我们将通过具体实践案例介绍如何运用这些知识解决实际编码问题。

第6章 实践案例:处理编码问题的策略与技巧

6.1 常见编码问题与故障排除

6.1.1 UnicodeDecodeError与编码不匹配

当尝试解码字节序列时,如果提供的编码与实际编码不符,Python会抛出UnicodeDecodeError。例如,以下代码试图以ASCII编码解码包含中文的UTF-8字节序列:

incorrectly_encoded_bytes=b'\xe4\xbd\xa0\xe5\xa5\xbd'
try:
    decoded_text=incorrectly_encoded_bytes.decode('ascii')
except UnicodeDecodeError as e:
    print(f"解码失败:{e}")

输出:

解码失败:'utf-8' codec can't decode byte 0xe4 in position 0: invalid continuation byte

解决此类问题的关键是确定正确的编码方式,可以借助chardet等工具检测字节序列的编码,或根据数据来源和上下文信息推断。

6.1.2 Mojibake现象与字符乱码

Mojibake(文字化け)是指由于编码转换错误导致的字符显示异常。例如,将UTF-8编码的文本以GBK解码后,原本的中文字符会变成乱码。要修复Mojibake,首先需要识别出导致乱码的原始编码和错误解码方式,然后重新以正确的方式解码:

mojibaked_bytes=b'\xd6\xd0\xce\xc4\xb5\xc4\xcb\xf3!'
correct_encoding='utf-8'  # 假设已确定原始编码为UTF-8
fixed_text=mojibaked_bytes.decode(correct_encoding)
print(fixed_text)  # 输出:你好,世界!

6.1.3 BOM头处理与无BOM的UTF-8文件

UTF-8编码的文件可能包含BOM(Byte Order Mark),它是字节序标记,用于指示UTF-8编码。在处理这类文件时,需要考虑是否保留或去除BOM。无BOM的UTF-8文件在解码时无需特别处理,但有BOM的文件如果不正确处理,可能导致首字符显示异常。codecs模块的open()函数提供了'utf-8-sig'模式 ,可自动识别并去除BOM:

with codecs.open('file_with_bom.txt', 'r', encoding='utf-8-sig') as f:
    content=f.read()

6.2 项目开发中的编码最佳实践

6.2.1 明确项目编码规范与统一编码声明

在项目开始阶段,应明确规定编码规范,如统一使用UTF-8编码,并在代码、配置文件、数据库连接等处明确声明编码。这有助于避免编码问题在整个项目中蔓延。

# 在Python源代码文件顶部声明编码
# -*- coding: utf-8 -*-

# 在数据库连接字符串中指定编码
db_connection='postgresql://user:password@localhost/dbname?charset=utf8'

# 在HTML文档中指定字符集
<meta charset="UTF-8">

6.2.2 数据库连接与存储过程中的编码设置

确保数据库连接的字符集与应用程序一致 ,避免数据存储和检索时的编码问题。在创建表时指定字符集,并在连接字符串中指定客户端字符集:

CREATE TABLE my_table (
    column1 VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci,
    ...
);

# Python SQLAlchemy示例
from sqlalchemy import create_engine

engine=create_engine('mysql+pymysql://user:password@localhost/dbname?charset=utf8')

6.2.3 Web开发中的字符集协商与HTTP头部设定

在Web开发中 ,通过HTTP头部Content-Type字段的charset参数告知浏览器响应内容的编码。同时 ,处理POST请求时,检查Content-Type以确保正确解码请求数据:

# Flask示例
from flask import Flask, request, make_response

app=Flask(__name__)

@app.route('/api', methods=['POST'])
def handle_post():
    if request.content_type=='application/json; charset=utf-8':
        data=request.json
    else:
        data=request.form

    response=make_response(json.dumps(result))
    response.headers['Content-Type']='application/json; charset=utf-8'
    return response

通过遵循编码最佳实践,开发者可以有效地预防和解决编码问题,确保项目在多语言环境中稳定、顺畅地运行。随着编码标准的演进和新挑战的出现,持续学习与适应将是每个技术工作者的必修课。

第7章 结语

编码是信息技术的核心要素之一,贯穿于信息的存储、传输与展示全过程。本文从字符编码的历史沿革至现代Unicode体系的广泛应用,剖析了Python在字符串处理上的独特角色与内建支持。通过深入探讨计算机存储原理与编码标准 ,我们揭示了Python中字符串类型strbytes的本质区别以及如何通过encode()decode()方法进行相互转换。面对编码难题,介绍了诸如chardetcodecs等实用工具,以及在项目实践中处理编码不匹配、Mojibake乱码等问题的最佳策略。

编码问题的妥善解决关乎项目的稳定性和国际化水平 ,强调了明确编码规范、统一编码声明,以及在数据库连接、Web开发等环节注重字符集协商与配置的重要性。面对新兴编码标准与不断扩大的字符集多样性,与时俱进的学习态度和实战经验积累显得尤为重要。最后 ,我们推荐了一系列官方文档、社区资源以及专业教材,鼓励读者持续探索编码世界的深度与广度 ,以适应未来编码领域的挑战与变革。

为一名前端程序员需要了解一些常用的JavaScript代码技巧,这样可以提高代码效率,我们就来看看具体的内容吧。


字符串类

1.比较时间

const time1="2022-03-05 10:00:00";
const time2="2022-03-05 10:00:01";
const overtime=time1 < time2;
// overtime=> true

2.货币格式

const ThousandNum=num=> num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
const cash=ThousandNum(100000000);
// cash=> 100,000,000

3.随机密码

const Randompass=len=> Math.random().toString(36).substr(3, len);
const pass=RandomId(8);
// pass=> "7nf6tgru"

4.随机HEX颜色值

const RandomColor=()=> "#" + Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, "0");
const color=RandomColor();
// color=> "##26330b"

5.评价星星

const StartScore=rate=> "★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);
const start=StartScore(4);
// start=> ★★★★☆

6.获得URL参数

const url=new URL('https://example.com?name=tom&sex=male');
const params=new URLSearchParams(url.search.replace(/\?/ig, ""));
params.has('sex'); // true
params.get("sex"); // "male"


数字类

  1. 数字处理,替代Math.floor() 和 Math.ceil()
const n1=~~ 1.19;
const n2=2.29 | 0;
const n3=3.39 >> 0;
// n1 n2 n3=> 1 2 3

2.补零

const FillZero=(num, len)=> num.toString().padStart(len, "0");
const num=FillZero(123, 5);
// num=> "00123"

3.转换成数值

const num1=+null;
const num2=+"";
const num3=+false;
const num4=+"59";
// num1 num2 num3 num4=> 0 0 0 59

4.时间戳

const timestamp=+new Date("2022-03-07");
// timestamp=> 1646611200000

5.小数

const RoundNum=(num, decimal)=> Math.round(num * 10 ** decimal) / 10 ** decimal;
const num=RoundNum(1.2345, 2);
// num=> 1.23

6.奇偶校验

const YEven=num=> !!(num & 1) ? "no" : "yes";
const num=YEven(1);
// num=> "no"
const num=YEven(2);
// num=> "yes"

7.获得最小值最大值

const arr=[0, 1, 2, 3];
const min=Math.min(...arr);
const max=Math.max(...arr);
// min max=> 0 3

8.生成范围随机数

const RandomNum=(min, max)=> Math.floor(Math.random() * (max - min + 1)) + min;
const num=RandomNum(1, 10); // 6 每次运行可能不一样


待续................