当前位置: 首页 > news >正文

java学习笔记之基础:核心类

Java 核心类

String

在 Java 中,String 是一个引用类型,实际上字符串在 String 内部是通过一个 char[] 数组表示的,因此按下面的写法也是可以的:String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'}); 。因为 String 太常用了,所以 Java 提供了 "hello" 这种字符串字面量表示方法。

Java 字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的 private final char[] 字段,以及没有任何修改 char[] 的方法实现的。

字符串比较

当我们想要比较两个字符串是否相同时,我们实际上是想比较字符串的内容是否相同。必须使用 equals() 方法而不能用 ==

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);//true
System.out.println(s1.equals(s2));//true

从表面上看,两个字符串用 ==equals() 比较都为 true,但实际上那只是 Java 编译器在编译期,会自动把所有相同的字符串当作一个对象放入常量池,自然 s1 和 s2 的引用就是相同的。所以这种 == 比较返回 true 纯属巧合。换一种写法,== 比较就会失败:

String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1 == s2);//false
System.out.println(s1.equals(s2));//true
字符串方法
//忽略大小写比较
"Hello".equalsIgnoreCase("hello");  // true// 搜索子串:
"Hello".contains("ll"); // true
"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true// 提取子串
"Hello".substring(2); // "llo"
"Hello".substring(2, 4); // "ll"// 去除首尾空白字符
"  \tHello\r\n ".trim(); // "Hello"
"\u3000Hello\u3000".strip(); // "Hello" 中文的空格字符\u3000也会被去除
" Hello ".stripLeading(); // "Hello "
" Hello ".stripTrailing(); // " Hello"// 判断字符串是否为空和空白字符串
"".isEmpty(); // true,因为字符串长度为0
"  ".isEmpty(); // false,因为字符串长度不为0
"  \n".isBlank(); // true,因为只包含空白字符
" Hello ".isBlank(); // false,因为包含非空白字符// 替换子串
"hello".replace('l', 'w'); // "hewwo",所有字符'l'被替换为'w'
"hello"..replace("ll", "~~"); // "he~~o",所有子串"ll"被替换为"~~"
"A,,B;C ,D".replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"// 分割字符串,参数为正则表达式
"A,B,C,D".split("\\,");// 拼接字符串
String.join("***", {"A", "B", "C"}); // "A***B***C"// 格式化字符串:有几个占位符,后面就传入几个参数。参数类型要和占位符一致。
"Hi %s, your score is %d!".formatted("Alice", 80);
String.format("Hi %s, your score is %.2f!", "Bob", 59.5);
类型转换

要把任意基本类型或引用类型转换为字符串,可以使用静态方法 valueOf() 。这是一个重载方法,编译器会根据参数自动选择合适的方法:

String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似java.lang.Object@636be97c

要把字符串转换为其他类型,就需要根据情况。

int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false

转换为 char[]

char[] cs = "Hello".toCharArray(); // String -> char[]
String s = new String(cs); // char[] -> String

如果修改了 char[] 数组,String 并不会改变,这是因为通过 new String(char[]) 创建新的 String 实例时,它并不会直接引用传入的 char[] 数组,而是会复制一份,所以修改外部的 char[] 数组不会影响 String 实例内部的 char[] 数组,因为这是两个不同的数组。

从 String 的不变性设计可以看出,如果传入的对象有可能改变,我们需要复制而不是直接引用。

字符编码

在早期的计算机系统中,为了给字符编码,ANSI 制定了一套英文字母、数字和常用符号的编码,它占用一个字节,编码范围从 0 到 127,最高位始终为 0,称为 ASCII 编码。例如字符 'A' 的编码是 0x41,字符 '1' 的编码是 0x31。如果要把汉字也纳入计算机编码,很显然一个字节是不够的。GB2312 标准使用两个字节表示一个汉字,其中第一个字节的最高位始终为 1,以便和 ASCII 编码区分开。例如汉字'中'的 GB2312 编码是 0xd6d0。类似地日文有 Shift_JIS 编码,韩文有 EUC-KR 编码,这些编码因为标准不统一,同时使用就会产生冲突。

为了统一全球所有语言的编码,全球统一码联盟发布了 Unicode 编码,它把世界上主要语言都纳入同一个编码,这样中文、日文、韩文和其他语言就不会冲突。Unicode 编码需要两个或者更多字节表示。英文字符的 Unicode 编码就是简单地在 ASCII 编码添加一个 00 字节。因为英文字符的 Unicode 编码高字节总是 00,包含大量英文的文本会浪费空间,所以出现了 UTF-8 编码,它是一种变长编码,用来把固定长度的 Unicode 编码变成 1~4 字节的变长编码。通过 UTF-8 编码,英文字符 'A' 的 UTF-8 编码变为 0x41,正好和 ASCII 码一致,而中文 '中' 的 UTF-8 编码为 3 字节 0xe4b8ad。UTF-8 编码的另一个好处是容错能力强。如果传输过程中某些字符出错,不会影响后续字符,因为 UTF-8 编码依靠高字节位来确定一个字符究竟是几个字节,它经常用来作为传输编码。

在 Java 中,char 类型实际上就是两个字节的 Unicode 编码。如果我们要手动把字符串转换成其他编码,可以这样做。注意:转换编码后,就不再是 char 类型,而是 byte 类型表示的数组。

byte[] b1 = "Hello".getBytes(); // 按系统默认编码转换,不推荐
byte[] b2 = "Hello".getBytes("UTF-8"); // 按UTF-8编码转换
byte[] b2 = "Hello".getBytes("GBK"); // 按GBK编码转换
byte[] b3 = "Hello".getBytes(StandardCharsets.UTF_8); // 按UTF-8编码转换

如果要把已知编码的 byte[] 转换为 String,可以这样做:

byte[] b = ...
String s1 = new String(b, "GBK"); // 按GBK转换
String s2 = new String(b, StandardCharsets.UTF_8); // 按UTF-8转换

始终牢记:Java 的 String 和 char 在内存中总是以 Unicode 编码表示。

StringBuilder

Java 编译器对 String 做了特殊处理,使得我们可以直接用 + 拼接字符串。虽然可以直接拼接字符串,但是在循环中,每次循环都会创建新的字符串对象,然后扔掉旧的字符串。这样绝大部分字符串都是临时对象,不但浪费内存,还会影响 GC 效率。

String s = "";
for (int i = 0; i < 1000; i++) {s = s + "," + i;
}

为了能高效拼接字符串,Java 标准库提供了 StringBuilder,它是一个可变对象,可以预分配缓冲区,这样往 StringBuilder 中新增字符时,不会创建新的临时对象:

StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {sb.append(',');sb.append(i);
}
String s = sb.toString();

StringBuilder 还可以进行链式操作:进行链式操作的关键是,定义的 append() 方法会返回 this,这样就可以不断调用自身的其他方法。

var sb = new StringBuilder(1024);
sb.append("Mr ").append("Bob").append("!").insert(0, "Hello, ");
System.out.println(sb.toString());

StringBuffer,这是 Java 早期的一个 StringBuilder 的线程安全版本,它通过同步来保证多个线程操作 StringBuffer 也是安全的,但是同步会带来执行速度的下降。StringBuilder 和 StringBuffer 接口完全相同。

StringJoiner

Java 标准库提供了 StringJoiner 来用分隔符拼接数组。

String[] names = {"Bob", "Alice", "Grace"};
// var sj = new StringJoiner(", "); // 指定分隔符
var sj = new StringJoiner(", ", "Hello ", "!");// 指定分隔符、开头和结尾
for (String name : names) {sj.add(name);
}
System.out.println(sj.toString());

String 还提供了一个静态方法 join() ,这个方法在内部使用了 StringJoiner 来拼接字符串,在不需要指定“开头”和“结尾”的时候,用 String.join() 更方便:

String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(", ", names);

包装类型

我们已经知道,Java 的数据类型分两种:

  • 基本类型:byte,short,int,long,boolean,float,double,char;
  • 引用类型:所有 class 和 interface 类型。

引用类型可以赋值为 null,表示空,但基本类型不能赋值为 null。

Java 核心库为每种基本类型都提供了对应的包装类型:

基本类型 对应的引用类型
boolean java.lang.Boolean
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
char java.lang.Character

通过基本类型创建包装类型:

int i = 100;
// 通过new操作符创建Integer实例(不推荐使用,会有编译警告):
Integer n1 = new Integer(i);
// 通过静态方法valueOf(int)创建Integer实例:
Integer n2 = Integer.valueOf(i);
// 通过静态方法valueOf(String)创建Integer实例:
Integer n3 = Integer.valueOf("100");
System.out.println(n3.intValue());
Auto Boxing
Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用Integer.intValue()

这种直接把 int 变为 Integer 的赋值写法,称为自动装箱(Auto Boxing)。反过来,把 Integer 变为 int 的赋值写法,称为自动拆箱(Auto Unboxing)。

自动装箱和自动拆箱只发生在编译阶段,目的是为了少写代码。装箱和拆箱会影响代码的执行效率,因为编译后的 class 代码是严格区分基本类型和引用类型的。并且自动拆箱执行时可能会报 NullPointerException。

不变类

所有的包装类型都是不变类。Integer 的核心代码如下:

public final class Integer {private final int value;
}

因此一旦创建了 Integer 对象,该对象就是不变的。对两个 Integer 实例进行比较要特别注意:绝对不能用 == 比较,因为 Integer 是引用类型,必须使用 equals() 比较。

进制转换
int x1 = Integer.parseInt("100"); // 100
int x2 = Integer.parseInt("100", 16); // 256,因为按16进制解析System.out.println(Integer.toString(100)); // "100",表示为10进制
System.out.println(Integer.toString(100, 36)); // "2s",表示为36进制
System.out.println(Integer.toHexString(100)); // "64",表示为16进制
System.out.println(Integer.toOctalString(100)); // "144",表示为8进制
System.out.println(Integer.toBinaryString(100)); // "1100100",表示为2进制
静态变量

Java 的包装类型还定义了一些有用的静态变量。

// boolean只有两个值true/false,其包装类型只需要引用Boolean提供的静态字段:
Boolean t = Boolean.TRUE;
Boolean f = Boolean.FALSE;// int可表示的最大/最小值:
int max = Integer.MAX_VALUE; // 2147483647
int min = Integer.MIN_VALUE; // -2147483648// long类型占用的bit和byte数量:
int sizeOfLong = Long.SIZE; // 64 (bits)
int bytesOfLong = Long.BYTES; // 8 (bytes)
通过包装类型获取各种基本类型

所有的整数和浮点数的包装类型都继承自 Number,因此可以非常方便地直接通过包装类型获取各种基本类型。

// 向上转型为 Number:
Number num = new Integer(999);
// 获取 byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();
处理无符号整型

在 Java 中并没有无符号整型。无符号整型和有符号整型的转换在 Java 中就需要借助包装类型的静态方法完成。

byte x = -1;
byte y = 127;
System.out.println(Byte.toUnsignedInt(x)); // 255
System.out.println(Byte.toUnsignedInt(y)); // 127

类似的,可以把一个 short 按 unsigned 转换为 int,把一个 int 按 unsigned 转换为 long。

JavaBean

在 Java 中,有很多 class 的定义都符合这样的规范:

  • 若干 private 实例字段;
  • 通过 public 方法来读写实例字段。

如果读写方法符合以下这种命名规范:

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

那么这种 class 被称为 JavaBean。上面的字段是 xyz,那么读写方法名分别以 get 和 set 开头,并且后接大写字母开头的字段名 Xyz,因此两个读写方法名分别是 getXyz()setXyz()

boolean 字段比较特殊,它的读方法一般命名为 isXyz()

// 读方法:
public boolean isChild()
// 写方法:
public void setChild(boolean value)

我们通常把一组对应的读方法(getter)和写方法(setter)称为属性(property)。只有 getter 的属性称为只读属性(read-only)。只有 setter 的属性称为只写属性(write-only)。属性只需要定义 getter 和 setter 方法,不一定需要对应的字段。

public class Person {private String name;private int age;public String getName() { return this.name; }public void setName(String name) { this.name = name; }public int getAge() { return this.age; }public void setAge(int age) { this.age = age; }public boolean isChild() {return age <= 6;}
}
枚举 JavaBean 属性

要枚举一个 JavaBean 的所有属性,可以直接使用 Java 核心库提供的 Introspector。

import java.beans.*;public class Main {public static void main(String[] args) throws Exception {BeanInfo info = Introspector.getBeanInfo(Person.class);for (PropertyDescriptor pd : info.getPropertyDescriptors()) {System.out.println(pd.getName());System.out.println("  " + pd.getReadMethod());System.out.println("  " + pd.getWriteMethod());}}
}

运行上述代码,可以列出所有的属性,以及对应的读写方法。注意 class 属性是从 Object 继承的 getClass() 方法带来的。

枚举类

定义枚举类是通过关键字 enum 实现的,依次列出枚举的常量名。

// enum
public class Main {public static void main(String[] args) {Weekday day = Weekday.SUN;if (day == Weekday.SAT || day == Weekday.SUN) {System.out.println("Work at home!");} else {System.out.println("Work at office!");}}
}enum Weekday {SUN, MON, TUE, WED, THU, FRI, SAT;
}

和 int 定义的常量相比,使用 enum 定义枚举有如下好处:

  • enum 常量本身带有类型信息,即 Weekday.SUN 类型是 Weekday,编译器会自动检查出类型错误。
  • 不可能引用到非枚举的值,因为无法通过编译。
  • 不同类型的枚举不能互相比较或者赋值,因为类型不符。
枚举类的比较

枚举类是一种引用类型。引用类型比较要使用 equals() 方法,如果使用 == 比较,它比较的是两个引用类型的变量是否是同一个对象。因此引用类型比较,要始终使用 equals()方法,但 enum 类型可以例外。这是因为 enum 类型的每个常量在 JVM 中只有一个唯一实例,所以可以直接用 == 比较:

Weekday day = Weekday.SUN;
if (day == Weekday.FRI) { // ok!
}
if (day.equals(Weekday.SUN)) { // ok, but more code!
}
枚举类特点

通过 enum 定义的枚举类,和其他的 class 没有任何区别。enum 定义的类型就是 class,只不过它有以下几个特点:

  • 定义的 enum 类型总是继承自 java.lang.Enum,且无法被继承;
  • 只能定义出 enum 的实例,而无法通过 new 操作符创建 enum 的实例;
  • 定义的每个实例都是引用类型的唯一实例;
  • 可以将 enum 类型用于 switch 语句。
public enum Color {RED, GREEN, BLUE;
}

编译器编译出的 class 大概就像这样:

public final class Color extends Enum { // 继承自Enum,标记为final class// 每个实例均为全局唯一:public static final Color RED = new Color();public static final Color GREEN = new Color();public static final Color BLUE = new Color();// private构造方法,确保外部无法调用new操作符:private Color() {}
}
枚举类的方法
// 返回常量名
String s = Weekday.SUN.name(); // "SUN"// 返回定义的常量的顺序,从0开始计数
int n = Weekday.MON.ordinal(); // 1

改变枚举常量定义的顺序就会导致 ordinal() 返回值发生变化。如果不小心修改了枚举的顺序,编译器是无法检查出这种逻辑错误的。要编写健壮的代码,就不要依靠 ordinal() 的返回值。因为 enum 本身是 class,所以我们可以定义 private 的构造方法,并且给每个枚举常量添加字段:

enum Weekday {MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6), SUN(0);public final int dayValue;private Weekday(int dayValue) {this.dayValue = dayValue;}
}

默认情况下,对枚举常量调用 toString() 会返回和 name() 一样的字符串。但是 toString() 可以被覆写,而 name() 则不行。覆写 toString()的目的在于输出更有可读性。我们可以给 Weekday 添加 toString() 方法:

enum Weekday {MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");public final int dayValue;private final String chinese;private Weekday(int dayValue, String chinese) {this.dayValue = dayValue;this.chinese = chinese;}@Overridepublic String toString() {return this.chinese;}
}
枚举类应用在 switch 语句中

枚举类可以应用在 switch 语句中。因为枚举类天生具有类型信息和有限个枚举常量,所以比 int、String 类型更适合用在 switch 语句中:

// switch
public class Main {public static void main(String[] args) {Weekday day = Weekday.SUN;switch (day) {case MON:case TUE:case WED:case THU:case FRI:System.out.println("Today is " + day + ". Work at office!");break;case SAT:case SUN:System.out.println("Today is " + day + ". Work at home!");break;default:throw new RuntimeException("cannot process " + day);}}
}enum Weekday {MON, TUE, WED, THU, FRI, SAT, SUN;
}

记录类

使用 String、Integer 等类型的时候,这些类型都是不变类,一个不变类具有以下特点:

  • 定义 class 时使用 final,无法派生子类;
  • 每个字段使用 final,保证创建实例后无法修改任何字段。

假设我们希望定义一个 Point 类,有 x、y 两个变量,同时它是一个不变类,可以这么写:

public final class Point {private final int x;private final int y;public Point(int x, int y) {this.x = x;this.y = y;}public int x() {return this.x;}public int y() {return this.y;}
}

为了保证不变类的比较,还需要正确覆写 equals()hashCode() 方法,这样才能在集合类中正常使用。

record 类

从 Java 14 开始,引入了新的 Record 类。我们定义 Record 类时,使用关键字 record。使用 record 关键字,可以一行写出一个不变类。把上述 Point 类改写为 Record 类,代码如下:

// Record
public class Main {public static void main(String[] args) {Point p = new Point(123, 456);System.out.println(p.x());System.out.println(p.y());System.out.println(p);}
}record Point(int x, int y) {}
构造方法

编译器默认按照 record 声明的变量顺序自动创建一个构造方法,并在方法内给字段赋值。如果我们要检查参数,我们就得给 Point 的构造方法加上检查逻辑:

public record Point(int x, int y) {public Point {if (x < 0 || y < 0) {throw new IllegalArgumentException();}}
}

注意到方法 public Point {...} 被称为 Compact Constructor,它的目的是让我们编写检查逻辑。

作为 record 的 Point 仍然可以添加静态方法。一种常用的静态方法是 of() 方法,用来创建 Point:

public record Point(int x, int y) {public static Point of() {return new Point(0, 0);}public static Point of(int x, int y) {return new Point(x, y);}
}

这样我们可以写出更简洁的代码:

var z = Point.of();
var p = Point.of(123, 456);

常用工具类

Math

Math 类就是用来进行数学计算的,它提供了大量的静态方法来便于我们实现数学计算:

// 求绝对值
Math.abs(-100); // 100
Math.abs(-7.8); // 7.8// 取最大或最小值:
Math.max(100, 99); // 100
Math.min(1.2, 2.3); // 1.2// 计算x的y次方
Math.pow(2, 10); // 2的10次方=1024// 计算开方
Math.sqrt(2); // 1.414...// 计算e的x次方
Math.exp(2); // 7.389...// 计算以e为底的对数
Math.log(4); // 1.386...// 计算以 10 为底的对数:
Math.log10(100); // 2// 三角函数
Math.sin(3.14); // 0.00159...
Math.cos(3.14); // -0.9999...
Math.tan(3.14); // -0.0015...
Math.asin(1.0); // 1.57079...
Math.acos(1.0); // 0.0// 数学常量
double pi = Math.PI; // 3.14159...
double e = Math.E; // 2.7182818...// 生成一个随机数x,x的范围是0 <= x < 1:
Math.random(); // 0.53907... 每次都不一样

Java 标准库还提供了一个 StrictMath,它提供了和 Math 几乎一模一样的方法。这两个类的区别在于,由于浮点数计算存在误差,不同的平台(例如 x86 和 ARM)计算的结果可能不一致(指误差不同),因此,StrictMath 保证所有平台计算结果都是完全相同的,而 Math 会尽量针对平台优化计算速度,所以绝大多数情况下使用 Math 就足够了。

HexFormat

要将 byte[] 数组转换为十六进制字符串,可以用 formatHex() 方法:

import java.util.HexFormat;public class Main {public static void main(String[] args) throws InterruptedException {byte[] data = "Hello".getBytes();HexFormat hf = HexFormat.of();String hexData = hf.formatHex(data); // 48656c6c6f}
}

如果要定制转换格式,则使用定制的 HexFormat 实例:

// 分隔符为空格,添加前缀0x,大写字母:
HexFormat hf = HexFormat.ofDelimiter(" ").withPrefix("0x").withUpperCase();
hf.formatHex("Hello".getBytes())); // 0x48 0x65 0x6C 0x6C 0x6F

从十六进制字符串到 byte[] 数组转换,使用 parseHex() 方法:byte[] bs = HexFormat.of().parseHex("48656c6c6f");

Random

Random 用来创建伪随机数。所谓伪随机数是指只要给定一个初始的种子,产生的随机数序列是完全一样的。要生成一个随机数,可以使用 nextInt()nextLong()nextFloat()nextDouble()

Random r = new Random();
r.nextInt(); // 2071575453,每次都不一样
r.nextInt(10); // 5,生成一个[0,10)之间的int
r.nextLong(); // 8811649292570369305,每次都不一样
r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
r.nextDouble(); // 0.3716...生成一个[0,1)之间的double

每次运行程序,生成的随机数都是不同的,没看出伪随机数的特性来。这是因为我们创建 Random 实例时,如果不给定种子,就使用系统当前时间戳作为种子,因此每次运行时种子不同,得到的伪随机数序列就不同。如果我们在创建 Random 实例时指定一个种子,就会得到完全确定的随机数序列:

import java.util.Random;public class Main {public static void main(String[] args) {Random r = new Random(12345);for (int i = 0; i < 10; i++) {System.out.println(r.nextInt(100));}// 51, 80, 41, 28, 55...}
}

前面我们使用的 Math.random() 实际上内部调用了 Random 类,所以它也是伪随机数,只是我们无法指定种子。

SecureRandom

有伪随机数,就有真随机数。实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom 就是用来创建安全的随机数的:

SecureRandom sr = new SecureRandom();
System.out.println(sr.nextInt(100));

SecureRandom 无法指定种子,它使用 RNG(random number generator)算法。JDK 的 SecureRandom 实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器:

import java.util.Arrays;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;public class Main {public static void main(String[] args) {SecureRandom sr = null;try {sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器} catch (NoSuchAlgorithmException e) {sr = new SecureRandom(); // 获取普通的安全随机数生成器}byte[] buffer = new byte[16];sr.nextBytes(buffer); // 用安全随机数填充bufferSystem.out.println(Arrays.toString(buffer));}
}

SecureRandom 的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过 CPU 的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”。在密码学中,安全的随机数非常重要。如果使用不安全的伪随机数,所有加密体系都将被攻破。因此时刻牢记必须使用 SecureRandom 来产生安全的随机数。

http://www.sczhlp.com/news/17832/

相关文章:

  • 高端品牌网站建设百度关键词优化快速排名软件
  • wordpress怎么批量修改分类济南新站seo外包
  • 网站app建设需要资源怎么做好seo推广
  • 站长工具alexa排名电商平台开发需要多少钱
  • 网站的备案在哪备案吗沪深300指数基金排名
  • 知名网站建设怎么样台湾搜索引擎
  • 生成特定下载工具(如迅雷、快车、QQ旋风)的专用链接规则
  • LOJ #2392. 「JOISC 2017 Day 1」烟花棒 题解
  • 传统 Bug 追踪系统正面临淘汰,原因何在?
  • vue vxe-gantt 甘特图实现子任务拖拽排序
  • 美女做暖暖暖视频网站百度推广优化怎么做的
  • 购物网网站建设开题报告所有的竞价托管公司
  • 老师让做网站怎么做搜索关键词分析
  • 专业营销型网站建设公司推广通
  • 网站数据库建设方案站长之家收录查询
  • 南昌网站制作公司沪深300指数
  • 上海seo方案产品优化是什么意思
  • 如东网站开发爱站网关键词挖掘机
  • 网站推广策划案关键词搜狗网页搜索
  • 安徽党组织标准化建设网站上海网站推广服务公司
  • 语音模型初创「宇生月伴」获数千万元融资;游戏 AI 陪伴逗逗发布 1.0 版,引入 RTC 实时通讯丨日报
  • 瓷爹专场!!
  • 潍坊智能建站模板推广普通话黑板报
  • 网站降权原因举三个成功的新媒体营销案例
  • 做盗版网站 国外服务器吗推广赚钱的app
  • 长春网站建设托管长沙本地推广联系电话
  • 网站开发项目可行性商业推广
  • 做团购网站需要什么资质新手小白怎么做跨境电商
  • 固安建设行政主管部门官方网站2024最火的十大新闻
  • 东莞排名seo网站关键词优化优化教程