【Java常用类】1-7 Math, Random 和 UUID

§1-7 Math, RandomUUID

1-7.1 Math 类概述及常用方法

Math 类位于 java.lang 下,该类包含了用于执行基本数值运算的方法,例如基本指数、对数、平方根和三角函数。

该类由关键字 final 修饰,不可被继承。该类中的所有方法为静态方法,意味着可以在无需创建对象的情况下直接调用这些方法。

值得注意的是,该类的构造方法私有化,无法在外部实例化。

字段摘要

静态常量 描述
E 自然对数的底数
PI 圆周率

常用方法

静态方法 描述
int/long/float/double abs(int/long/float/double a) 返回绝对值
double ceil(double a) 向上取整
double floor(double a) 向下取整
int round(float a) 四舍五入
double signum(double d) 返回参数的符号函数
double pow(double a, double b) 返回次幂,\(a^b\)
double exp(double a) 返回自然指数 \(e^a\)
double sqrt(double a) 返回平方根
double cbrt(double a) 返回立方根
double random() 返回随机值,范围位于 \([0,1)\)
double max/min(double a, double b) 返回最值

注意

  • 查看源码,会发现所有静态方法实际上调用了来自 StrictMath 类中的同名方法,这是默认行为,使用了 IEEE 754 的推荐运算;
  • abs() 方法返回的值若不能够在指定类型的范围内表示,则可能会产生意外结果;
  • 使用 absExact() 方法会在返回值溢出时抛出异常 ArithmeticException
  • 实际上,调用 random() 方法,就是在通过 Random 类的一个对象的 nextDouble() 方法实现的;

1-7.2 Math 类的实例练习

要求:编写程序,满足

  1. 判断数 1617 是否为素数;
  2. 遍历所有三位数,判断是否为自幂数(三位自幂数又称为水仙花数),输出自幂数并统计个数;
  3. 遍历所有四位数、五位数,按上一条条件输出;
  4. 利用遍历的方法,判断是否存在两位数的自幂数。

思路

  1. 由于一个数一定由一个大于等于其平方根和一个小于等于其平方根的数相乘而得。对于一个给定的数,从 2 开始,一直遍历到其平方根,判断这个数能否被迭代变量整除即可;
  2. 自幂数,每一位数的位数次幂之和。以水仙花数为例:\(100x + 10y + z = x^3 + y^3 + z^3\)

代码实现

public class Practices {
    public static void main(String[] args) {
        //判断素数
        System.out.println("16是素数吗?" + isPrime(16));
        System.out.println("17是素数吗?" + isPrime(17));
        
        //统计水仙花数
        int total = 0;
        for (int i = 100; i < 1000; i++) {
            if (isNarcissistic(i)) {
                System.out.print(i + "\t");
                total++;

                if (i % 10 == 0 && i != 100) {
                System.out.println();
                }
            }
        }
        System.out.println("\n共计" + total + "个水仙花数。");

        //统计四叶玫瑰数
        total = 0;
        for (int i = 1000; i < 10000; i++) {
            if (isNarcissistic(i)) {
                System.out.print(i + "\t");
                total++;

                if (i % 10 == 0 && i != 1000) {
                    System.out.println();
                }
            }
        }
        System.out.println("\n共计" + total + "个四叶玫瑰数。");

        //统计五角星数
        total = 0;
        for (int i = 10000; i < 100000; i++) {
            if (isNarcissistic(i)) {
                System.out.print(i + "\t");
                total++;

                if (i % 10 == 0 && i != 10000) {
                    System.out.println();
                }
            }
        }
        System.out.println("\n共计" + total + "个五角星数。");

        System.out.println("存在二位自幂数吗?" + isSelfPoweredTwoDigits());
    }

    //判断素数
    public static boolean isPrime(int a) {
        boolean res = true;
        for (int i = 2; i <= Math.sqrt(a); i++) {
            if (a % i == 0) {
                res = false;
                break;
            }
        }

        return res;
    }

    //判断自幂数
    public static boolean isNarcissistic(int a) {
        boolean res = true;

        //获取每一位以及位数
        int digits = digits(a);
        int digit = 0;

        //重构
        int reconstruct = 0;
        int copy = a;

        //逐位判断
        for (int i = 0; i < digits; i++) {
            digit = copy % 10;
            reconstruct += Math.pow(digit, digits);
            copy /= 10;
        }

        if (reconstruct != a)
        {
            res = false;
        }

        return res;
    }

    //获取位数
    public static int digits(int a) {
        int digits = 1;

        while (a >= 10) {
            digits++;
            a /= 10;
        }

        return digits;
    }

    //证明不存在二位自幂数
    public static boolean isSelfPoweredTwoDigits() {
        boolean res = false;

        //遍历
        for (int i = 10; i < 100; i++) {
            int copy = i;
            int digit = 0;
            int reconstruct = 0;

            for (int j = 0; j < 2; j++) {
                digit = copy % 10;
                reconstruct += Math.pow(digit, 2);
                copy /= 10;
            }

            if (reconstruct == i) {
                res = true;
                break;
            }
        }

        return res;
    }
}

编译运行,得到

16是素数吗?false
17是素数吗?true
153     370
371     407
共计4个水仙花数。
1634    8208    9474
共计3个四叶玫瑰数。
54748   92727   93084
共计3个五角星数。
存在二位自幂数吗?false

1-7.3 Random

Random 类位于 java.util 包下,实现了接口 RandomGeneratorSecureRandom

该类的一个实例用于生成一个伪随机数,其周期只有 \(2^{48}\)。该类使用 48 位的种子,该种子由线性同余公式修改(见 Donald E. Knuth, The Art of Computer Programming,第二卷,第三版:Seminumerical Algorithms)。

构造方法

构造方法 描述
Random() 创建一个新的随机数生成器
Random(long seed) 使用种子创建一个随机数生成器

建议方法

  1. 若使用的种子相同,调用相同方法所得到的随机数序列将会完全相同;

  2. 一般而言,可以使用当前的系统时间作为种子,创建随机数生成器;

    使用基于当前系统时间的种子创建 RNG:

    Random rd = new Random(System.currentTimeMillis());
    
  3. 部分情况下,使用 Math.random() 可能会更好;

  4. 该类实例是线程安全的。但是,跨线程并发使用同一个 Random 实例可能会引发竞争而导致性能低下。在多线程设计中考虑使用 ThreadLocalRandom

  5. 该实例不是加密安全的,考虑改用 SecureRandom 获取密码安全的伪随机数生成器,以供对安全敏感的应用程序使用;

常用方法

方法 描述
int nextInt() 返回随机的 int
int/long nextInt/nextLong(int/long bound) 返回随机的 int/long 值,范围位于 \([0, \text{bound})\)
double nextDouble() 返回随机的 double 值,范围位于 \([0.0,1.0)\)
long nextLong() 返回随机的 long
double nextGaussian() 返回一个随机数,以高斯分布(正态分布),平均值为 0.0,标准差为 1.0
boolean nextBoolean() 返回随机的 boolean
void setSeed(long seed) 为此随机数生成器设置新的种子

注意

  1. 一般而言,nextXXX() 方法所返回的伪随机数,都是均匀分布的;
  2. 生成的伪随机数范围,一般而言,都位于一个左闭右开区间 \([0.0,1.0)\)
  3. nextInt(int bound) 方法的参数必须为正,否则抛出异常 IllegalArgumentException

1-7.4 UUID

1-7.4.1 UUID

UUID,即通用唯一识别码(Universal Unique Identifier),是一个 128 位的长数字。

UUID 的表示方法有以下几种:

  • 带有连字符的十六进制(hyphenated hexadecimal):连字符将 UUID 分割成 8-4-4-4-12 的形式;

    示例f81d4fae-7dec-11d0-a765-00a0c91e6bf6

    由于每一部分单独作为一个数字处理,因此每个部分开头可为空。

    示例00000001-0002-0003-0004-0000000000051-2-3-4-5 相同。

  • 十六进制(hexadecimal):同带有连字符的十六进制表示,但没有连字符分隔;

    由于没有连字符分隔,因此 0 不可被省略。

    示例00000001000200030004000000000005

  • 高低位(most/least):将高 64 位与低 64 位分开表示,用 long 存储;

  • 整型数组(int-array):用 4 个 32 位整型数字表示,每一部分存储在由高到低的整型数组中。

每个 UUID 的存储数值范围为 \([-2^{127}, 2^{127}-1\)] ,其作用是让分布式系统中的所有元素都能有唯一的辨识信息。

1-7.4.2 java.util.UUID

UUID 类位于 java.util 包下,用于表示一个不可变的 UUID,一个 UUID 表示一个 128 位的值。

全局识别码有许多变种,该类中的方法操作的是 Leach-Salz 变种(变种 2),即使构造器允许创建其他变种的 UUID。

构造方法

构造方法 描述
UUID(long mostSigBits, long leastSigBits) 使用指定数据,创建一个新的 UUID

变种 2 的布局

高位长整型包含以下无符号字段

字段 描述
0xFFFFFFFF00000000 time_low
ox00000000FFFF0000 time_mid
0x000000000000F000 version
0x0000000000000FFF time_hi

低位长整型包含以下无符号字段

字段 描述
0xC000000000000000 variant
0x3FFF000000000000 clock_seq(14位)
0x0000FFFFFFFFFFFF node

静态方法

静态方法 描述
UUID fromString(String name) 使用 toString() 方法所描述的字符串标准表示,创建 UUID
UUID randomUUID() 静态工厂方法,获得一个版本 4 (伪随机 UUID)的 UUID

注意

  1. variant 字段包含了指定该 UUID 布局的值。上述布局仅对于变种 2,即 Leach-Salz 变种的 UUID 有效;
  2. version 字段包含了描述该 UUID 类型的值。有 4 种不同类型的 UUID:基于时间的、DCE 安全、基于名字的和随机生成的 UUID,对应的 version 值分别为 1, 2, 3, 4
  3. 版本 1 和 2 的 UUID 适合应用于分布式计算环境下;版本 4 的 UUID 建议最好不要用;

常用方法

方法 描述
int compareTo(UUID val) 与指定的 UUID 作比较,大于返回 1,等于返回 0,小于返回 -1
boolean equals(Object obj) 与指定的对象作比较
int variant() 返回与此 UUID 关联的变种
int version() 返回与此 UUID 关联的版本
long timestamp() 返回与此 UUID 关联的时间戳
String toString() 返回该 UUID 的带分隔符十六进制字符串表示

注意

  • 调用 compareTo() 方法时,当高位字段不同时,若第一个 UUID 的高位大于第二个 UUID,则第一个 UUID 大于 第二个;
  • equals() 方法当且仅当参数不为 null,且是一个 UUID 对象,并具有相同的变种、含有相同数值时返回 true
  • toString() 方法返回的带分隔符十六进制表示格式为 <time_low> "-" <time_mid> "-" <time_high_and_version> "-" <variant_and_sequence> "-" <node>
  • 当 UUID 不属于基于时间的类型时,调用 timestamp() 方法会抛出异常 UnsupportedOperationException
posted @ 2023-07-20 21:53  Zebt  阅读(78)  评论(0)    收藏  举报