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

Java String 类深度解析:内存模型、常量池与核心机制

目录

一、String初识

1. 字符串字面量的处理流程

(1) 编译阶段

(2) 类加载阶段

(3) 运行时阶段

2. 示例验证

示例 1:字面量直接赋值

示例 2:使用 new 创建字符串

示例 3:显式调用 intern()

注意点1:

⑴. String s1 = "abc";

⑵. String s2 = new String("hello");

总结

注意点2:

new String("hello") 创建对象的情况

intern() 方法的作用

① 动态创建的字符串

②节省内存和提高比较效率

注意点3:字符串的拼接

注意点1和注意点2总结

①String s1 = "abc" 和 String s2 = new String("hello") 的内存位置

② 为什么需要 intern() 方法?

(1) 动态生成的字符串不会自动入池

(2) 节省内存和加速比较

(3) 避免重复创建常量池对象

③intern() 的核心作用

④ 最终结论

3. 字符串常量池的位置

4. 关键特性

(1) 不可变性(String为什么不可变?(很重要))

(2) 唯一性保证

5. 注意事项

二、String类常用的构造方法

1. 基于字符数组的构造方法

⑴. String(char[] value)

⑵.String(char[] value, int offset, int count)

2. 基于字节数组的构造方法

⑴.String(byte[] bytes)

(2) String(byte[] bytes, int offset, int length)

(3) String(byte[] bytes, Charset charset)

(4) String(byte[] bytes, String charsetName)

3. String(String original) 构造方法

(1) 功能与行为

(2) @IntrinsicCandidate 注解说明


一、String初识

Java 中所有使用双引号("")直接定义的字符串字面量,在 编译阶段 会被识别并记录到 class 文件的常量池中。当程序运行时,JVM 会将这些字符串字面量加载到 字符串常量池(String Pool)中,并确保每个唯一的字符串字面量在池中仅保留 一份实例。以下是详细说明:


1. 字符串字面量的处理流程

(1) 编译阶段

  • 当 Java 源代码被编译为字节码(.class 文件)时,所有双引号包围的字符串字面量(如 "hello")会被记录在 class 文件的 常量池表(Constant Pool) 中。

  • 目的:为后续的类加载和运行阶段提供字符串的元数据。

(2) 类加载阶段

  • 当类被 JVM 加载时,字符串字面量会被加载到 运行时常量池(Runtime Constant Pool)

  • 关键点:此时尚未创建实际的 String 对象,仅记录字面量的引用。

(3) 运行时阶段

  • 首次使用字符串字面量时:JVM 会检查字符串常量池中是否存在相同内容的字符串:

    • 存在:直接返回池中对象的引用。

    • 不存在:在堆内存的字符串常量池中创建新对象,并返回其引用。

  • 驻留(Interning):此过程称为字符串的驻留,确保相同内容的字符串字面量共享同一实例。


2. 示例验证

示例 1:字面量直接赋值

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true(指向同一对象)
  • 结果分析s1 和 s2 指向字符串常量池中的同一实例

示例 2:使用 new 创建字符串

显式使用 new 创建字符串对象时,会在堆中生成新对象,绕过常量池。

String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s3 == s4);       // false(堆中不同对象)
System.out.println(s3.equals(s4));  // true(内容相同)
  • 结果分析new 会强制在堆中创建新对象,即使内容相同。

示例 3:显式调用 intern()

String s5 = new String("hello").intern();
String s6 = "hello";
System.out.println(s5 == s6); // true(s5 被驻留到池中)
  • 结果分析intern() 方法将字符串手动添加到常量池(若池中不存在)。

注意点1:

public class StringTest1 {public static void main(String[] args) {String s1 = "hello";String s2 = "hello";System.out.println(s1 == s2); // true(指向同一对象)/*String s1 = "abc";String s2 = new String("hello");*/}
}

String s1 = "abc"; String s2 = new String("hello");这两个是不是都在字符串常量池?

运行结果:

⑴. String s1 = "abc";

当使用双引号直接创建字符串时,Java 会首先检查字符串常量池(String Constant Pool)中是否已经存在相同内容的字符串对象。如果存在,就直接返回常量池中该对象的引用;如果不存在,则在常量池中创建一个新的字符串对象,并返回其引用

对于代码 String s1 = "abc";,由于使用了双引号直接创建字符串,所以 "abc" 会被存储在字符串常量池中,变量 s1 引用的就是常量池中的这个 "abc" 对象。

以下是示例代码验证:

public class StringConstantPoolExample {public static void main(String[] args) {String s1 = "abc";String s3 = "abc";System.out.println(s1 == s3); // 输出 true,说明 s1 和 s3 引用的是同一个对象}
}
⑵. String s2 = new String("hello");

当使用 new 关键字创建字符串对象时,会在堆内存中创建一个新的字符串对象。同时,双引号中的 "hello" 部分会像上面一样先检查字符串常量池,如果常量池中不存在 "hello",则会在常量池中创建一个 "hello" 对象;如果已经存在,则不会再创建。

也就是说,new String("hello"); 会创建至少一个对象,如果常量池中原本没有 "hello",则会创建两个对象,一个在常量池中,一个在堆内存中。变量 s2 引用的是堆内存中的那个新创建的字符串对象,而不是常量池中的对象。

以下是示例代码验证:

public class StringConstantPoolExample {public static void main(String[] args) {String s2 = new String("hello");String s4 = "hello";System.out.println(s2 == s4); // 输出 false,说明 s2 和 s4 引用的不是同一个对象}
}

运行结果:

总结
  • String s1 = "abc"; 中的 "abc" 在字符串常量池中。
  • String s2 = new String("hello"); 中,"hello" 会在字符串常量池中存在一份,而 s2 引用的是堆内存中通过 new 关键字创建的新对象,不在常量池中。

注意点2:

如果常量池中原本没有 "hello",则会创建两个对象,一个在常量池中,一个在堆内存中。那为什么还会有intern() 方法将字符串手动添加到常量池(若池中不存在)?

解释:

使用 new String("hello") 这种方式在常量池中原本没有 "hello" 时会在常量池和堆内存各创建一个对象,但这和 intern() 方法的作用并不冲突,下面详细解释:

new String("hello") 创建对象的情况

当执行 String s = new String("hello"); 时:

  • 常量池部分:双引号中的 "hello" 会先检查字符串常量池,若池中没有 "hello",则在常量池中创建 "hello" 对象。
  • 堆内存部分:使用 new 关键字会在堆内存中创建一个新的 String 对象,该对象的字符序列内容和常量池中的 "hello" 相同,但它是一个独立的对象实例。s 引用的就是堆内存中的这个对象。
intern() 方法的作用

intern() 方法是 String 类的一个实例方法,其作用是手动将字符串添加到常量池中(若池中不存在该字符串),并返回常量池中的字符串引用。虽然 new String("hello") 已经会在常量池创建对象,但有些情况下还是需要使用 intern() 方法,主要原因如下:

① 动态创建的字符串

new String("hello") 这种方式是在编译期就确定了字符串内容,而有些字符串是在运行时动态生成的,这些动态生成的字符串默认不会放入常量池。例如:

public class StringInternExample {public static void main(String[] args) {String str1 = "hello";String str2 = "world";String dynamicStr = str1 + str2; // 动态生成 "helloworld"String internedStr = dynamicStr.intern();String literalStr = "helloworld";System.out.println(internedStr == literalStr); // 输出 true}
}

运行结果:

在上述代码中,dynamicStr 是通过字符串拼接动态生成的,它只存在于堆内存中,不会自动放入常量池。调用 intern() 方法后,会检查常量池中是否有 "helloworld",若没有则将其添加到常量池,并返回常量池中的引用。

②节省内存和提高比较效率

由于常量池中的字符串对象是唯一的,使用 intern() 方法可以让多个内容相同的字符串引用指向常量池中的同一个对象,从而节省内存。同时,使用 == 比较常量池中的字符串引用比使用 equals() 方法比较字符串内容要快。例如:

public class StringInternMemoryExample {public static void main(String[] args) {String s1 = new String("test");String s2 = new String("test");System.out.println(s1 == s2); // 输出 false,因为 s1 和 s2 是堆内存中的不同对象String internedS1 = s1.intern();String internedS2 = s2.intern();System.out.println(internedS1 == internedS2); // 输出 true,因为它们都指向常量池中的同一个对象}
}

运行结果:

注意点3:字符串的拼接

使用 + 进行拼接的生成的新的字符串不会被放到字符串常量池中。(+两边至少有一个是变量。)
*
* 当 + 两边都是字符串字面量的时候,编译器会进行自动优化。在编译阶段进行拼接。
/*** 使用 + 进行拼接的生成的新的字符串不会被放到字符串常量池中。(+两边至少有一个是变量。)** 当 + 两边都是字符串字面量的时候,编译器会进行自动优化。在编译阶段进行拼接。*/
public class StringConstantPoolExample {public static void main(String[] args) {String s1 = "abc";String s2 = "def";String s3 = s1 + s2;//s1和s2是变量String s4 = "abcdef";// s3 指向的对象,没有在字符串常量池中。在堆中。// 底层实际上在进行 + 的时候(这个 + 两边至少有一个是变量),会创建一个StringBuilder对象,进行字符串的拼接。// 最后的时候会自动调用StringBuilder对象的toString()方法,再将StringBuilder// 转换成String对象。//System.out.println(s3 == s4); // false// 以下程序中 + 两边都是字符串字面量,这种情况java对其进行优化:// 在编译的时候就完成了字符串的拼接。String x = "java" + "test"; // 等同于:String x = "javatest";String y = "javatest";System.out.println(x == y); // true// 以上程序中s3指向了堆中的一个字符串对象,并没有在常量池中。// 如果这个字符串使用比较频繁,希望将其加入到字符串常量池中,怎么办?String s5 = s3.intern();System.out.println(s4 == s5); // trueString m = "m";String f = m + "e";String str = f.intern(); // 将"me"放入字符串常量池中,并且将"me"对象的地址返回。System.out.println(str == "me"); // true}
}

运行结果:

注意点1和注意点2总结

String s1 = "abc" 和 String s2 = new String("hello") 的内存位置
  • 直接赋值 "abc"

    • 编译期行为"abc" 是字面量,编译时会被放入字符串常量池(如果池中不存在)。

    • 变量 s1 的引用:直接指向常量池中的 "abc" 对象。

    • 验证:通过 s1 == "abc" 为 true 可确认。

  • new String("hello")

    • 编译期行为"hello" 作为字面量,编译时会检查并放入常量池(若不存在)。

    • 运行时行为new 会在堆中创建一个新对象,其内容与常量池中的 "hello" 相同。

    • 变量 s2 的引用:指向堆中的新对象,而非常量池中的 "hello"

    • 验证:通过 s2 == "hello" 为 false 可确认。


② 为什么需要 intern() 方法?

new String("hello") 已经在常量池中存入了 "hello",但 intern() 的用途远不止于此:

(1) 动态生成的字符串不会自动入池
  • 问题:运行时通过拼接、计算生成的字符串(如 new String("he") + "llo")默认仅存在于堆中,不会自动进入常量池。

  • 示例

String s3 = new String("he") + "llo";  // 堆中的新对象,内容为 "hello"
String s4 = "hello";
System.out.println(s3 == s4);  // false,s3在堆,s4在常量池

解决:调用 s3.intern() 会将 "hello" 手动添加到常量池(若池中没有),并返回池中引用:

String s5 = s3.intern();
System.out.println(s5 == s4);  // true,s5指向常量池中的"hello"

运行结果:

执行完上述代码后,各个变量的指向情况如下:

  • s3:指向堆内存中通过字符串拼接创建的内容为 "hello" 的对象。
  • s4:指向字符串常量池中的 "hello" 对象。
  • s5:指向字符串常量池中的 "hello" 对象。
(2) 节省内存和加速比较
  • 内存优化:若程序中有大量重复字符串(如从文件、网络读取的动态数据),使用 intern() 可让相同内容的字符串共享常量池的唯一实例,减少内存占用。

  • 性能提升:用 == 代替 equals() 比较字符串引用,效率更高。

(3) 避免重复创建常量池对象
  • 误区澄清new String("hello") 仅在编译时检查常量池,而运行时生成的字符串不会触发此机制。intern() 是运行时主动入池的唯一方式。

  • 示例

// 常量池中已有"hello",new String("hello") 不会重复创建常量池对象
String s6 = new String("hello").intern();  // 直接返回常量池中的引用
System.out.println(s6 == "hello");  // true

intern() 的核心作用

  • 行为:检查常量池中是否存在与当前字符串内容相同的对象:

    • 存在:直接返回池中对象的引用。

    • 不存在:将当前字符串的引用添加到池中(注意:JDK7+ 后,常量池位于堆中,直接保存引用,而非拷贝对象)。

  • 关键场景

    • 动态字符串入池(如 StringBuilder 拼接的结果)。

    • 复用高频字符串(如数据库查询结果的重复字段)。

④ 最终结论

  • "abc" 在常量池,new String("hello") 的堆对象和常量池对象共存

  • intern() 的核心价值:处理动态生成的字符串,确保其引用能被常量池管理,从而优化内存和比较效率。即使 new String("hello") 在编译期将字面量存入常量池,intern() 仍是运行时控制字符串入池的唯一手段。


3. 字符串常量池的位置

  • Java 8 之前:位于永久代(PermGen),可能导致 OutOfMemoryError: PermGen space

  • Java 8 及之后:移至堆内存,便于垃圾回收,避免内存溢出。


4. 关键特性

(1) 不可变性(String为什么不可变?(很重要))

  • 底层实现:String 使用 private final byte[] value(Java 9+)或 char[](Java 8 及之前)存储数据,内容不可修改

byte[ ]数组创建长度一旦确定不可变

  • 操作影响任何修改字符串的操作(如拼接、替换)都会生成新对象,原对象不变

(2) 唯一性保证

  • 编译时确定性:所有双引号定义的字符串字面量在编译时确定其唯一性,运行时保证池中仅一份实例。

  • 动态字符串处理运行时通过拼接生成的字符串(如 s1 + s2)默认不在池中,需调用 intern() 手动驻留。


5. 注意事项

  1. 避免滥用 new String("...")

    • 直接使用字面量赋值更高效,避免不必要的堆内存分配。

// 推荐
String s = "hello";// 不推荐(除非明确需要新对象)
String s = new String("hello");
  1. 性能优化与内存管理

    • 频繁拼接字符串时使用 StringBuilder 或 StringBuffer

    • 谨慎使用 intern(),可能导致常量池膨胀。

  2. 字符串比较原则

    • 使用 equals() 比较内容,而非 ==(除非明确需要地址比较)。

二、String类常用的构造方法

1. 基于字符数组的构造方法

⑴. String(char[] value)

此构造方法根据字符数组创建一个新的字符串对象。

public class StringConstructorExample {public static void main(String[] args) {// 定义一个字符数组char[] charArray = {'H', 'e', 'l', 'l', 'o'};// 使用字符数组创建字符串对象String str = new String(charArray);System.out.println(str); // 输出: Hello}
}

运行结果:

⑵.String(char[] value, int offset, int count)

功能:截取字符数组的指定部分生成字符串。

参数:

offset:起始索引。

count:截取长度。

示例:

public class StringConstructorExample {public static void main(String[] args) {// 定义一个字符数组char[] charArray = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};// 从索引2开始,取5个字符创建字符串对象String str = new String(charArray, 2, 5);System.out.println(str); // 输出: llo W}
}

运行结果:

2. 基于字节数组的构造方法

⑴.String(byte[] bytes)

此构造方法根据字节数组创建一个新的字符串对象,默认使用平台默认的字符集进行解码。

public class Main {public static void main(String[] args) {byte[] bytes = {72, 101, 108, 108, 111}; // "Hello" 的 ASCII 编码String str = new String(bytes); // 结果:"Hello"System.out.println(str); }
}

运行结果:

(2) String(byte[] bytes, int offset, int length)

该构造方法根据字节数组的指定部分创建一个新的字符串对象,默认使用平台默认的字符集进行解码。

  • 功能:截取字节数组的指定部分,按默认字符集解码。

public class Main {public static void main(String[] args) {byte[] bytes = {65, 66, 67, 68, 69};String str = new String(bytes, 1, 3); // 解码字节 66,67,68 → "BCD"System.out.println(str);}
}

运行结果:

(3) String(byte[] bytes, Charset charset)

此构造方法根据字节数组和指定的字符集创建一个新的字符串对象

在 Java 里,字符串在内存中是以 Unicode 编码形式存储的。而字节数组则可能是按照不同的字符编码规则存储的数据,比如 UTF - 8、GBK 等。此构造方法的目的就是依据给定的字符集(Charset 对象),把字节数组中的字节数据解码成对应的 Unicode 字符,进而构建一个新的 String 对象。

  • 功能:使用指定字符集解码字节数组。

方法签名

public String(byte[] bytes, Charset charset)
  • 参数
    • bytes:要进行解码的字节数组。
    • charset:用于解码字节数组的字符集,类型为 java.nio.charset.Charset

示例:

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;public class StringConstructorExample {public static void main(String[] args) {// 定义一个字符串String originalString = "你好,世界!";// 使用 UTF-8 字符集将字符串编码为字节数组byte[] utf8Bytes = originalString.getBytes(StandardCharsets.UTF_8);// 使用 UTF-8 字符集将字节数组解码为字符串String decodedUtf8String = new String(utf8Bytes, StandardCharsets.UTF_8);System.out.println("使用 UTF-8 解码后的字符串: " + decodedUtf8String);// 使用 GBK 字符集将字符串编码为字节数组byte[] gbkBytes = originalString.getBytes(Charset.forName("GBK"));// 使用 GBK 字符集将字节数组解码为字符串String decodedGbkString = new String(gbkBytes, Charset.forName("GBK"));System.out.println("使用 GBK 解码后的字符串: " + decodedGbkString);}
}

运行结果:

(4) String(byte[] bytes, String charsetName)

功能概述

当你有一个字节数组,并且知道这些字节是按照某种特定字符编码规则存储的,就可以使用这个构造方法,传入字节数组和对应的字符集名称,将字节数组转换为 String 对象。该构造方法会在内部查找指定名称的字符集,并使用它来完成解码操作。

方法签名

public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException
  • 参数
    • bytes:需要进行解码的字节数组。
    • charsetName:用于解码字节数组的字符集名称,例如 "UTF-8""GBK" 等。
  • 异常:如果指定的字符集名称无效,也就是 JVM 不支持该字符集,会抛出 UnsupportedEncodingException 异常。

示例代码

import java.io.UnsupportedEncodingException;public class StringConstructorWithCharsetName {public static void main(String[] args) {// 定义原始字符串String originalString = "Hello, 世界!";try {// 使用 UTF-8 编码成字节数组byte[] utf8Bytes = originalString.getBytes("UTF-8");// 使用 UTF-8 字符集名称解码字节数组String decodedUtf8String = new String(utf8Bytes, "UTF-8");System.out.println("使用 UTF-8 解码后的字符串: " + decodedUtf8String);// 使用 GBK 编码成字节数组byte[] gbkBytes = originalString.getBytes("GBK");// 使用 GBK 字符集名称解码字节数组String decodedGbkString = new String(gbkBytes, "GBK");System.out.println("使用 GBK 解码后的字符串: " + decodedGbkString);// 尝试使用一个不存在的字符集名称try {String nonExistentCharset = new String(utf8Bytes, "NonExistentCharset");} catch (UnsupportedEncodingException e) {System.out.println("不支持的字符集名称: " + e.getMessage());}} catch (UnsupportedEncodingException e) {e.printStackTrace();}}
}

运行结果:

代码解释

  1. 定义原始字符串:创建一个包含英文和中文字符的字符串 originalString
  2. 使用 UTF - 8 编码和解码
    • 调用 getBytes("UTF-8") 方法,将 originalString 按照 UTF - 8 字符集编码为字节数组 utf8Bytes
    • 使用 String(byte[] bytes, String charsetName) 构造方法,传入 utf8Bytes 和 "UTF-8",将字节数组解码为新的字符串 decodedUtf8String,并打印结果。
  3. 使用 GBK 编码和解码
    • 调用 getBytes("GBK") 方法,将 originalString 按照 GBK 字符集编码为字节数组 gbkBytes
    • 使用 String(byte[] bytes, String charsetName) 构造方法,传入 gbkBytes 和 "GBK",将字节数组解码为新的字符串 decodedGbkString,并打印结果。
  4. 异常处理:尝试使用一个不存在的字符集名称 "NonExistentCharset" 进行解码,捕获 UnsupportedEncodingException 异常并打印错误信息。

注意事项

  • 字符集名称的正确性:要确保传入的字符集名称是 JVM 支持的,否则会抛出 UnsupportedEncodingException 异常。常见的字符集名称如 "UTF-8""GBK""ISO-8859-1" 等通常是被支持的。
  • 异常处理:由于该构造方法可能会抛出 UnsupportedEncodingException 异常,在使用时必须进行异常处理,可使用 try-catch 块来捕获并处理该异常,避免程序因异常而崩溃。
  • 优先使用 Charset 对象:在 Java 中,更推荐使用 String(byte[] bytes, Charset charset) 构造方法,因为使用 Charset 对象可以避免因字符集名称拼写错误而导致的异常,并且代码可读性和安全性更高。

3. String(String original) 构造方法

功能概述

String(String original) 构造方法用于创建一个新的 String 对象,该对象是传入的 original 字符串的副本。简单来说,就是复制一个已有的字符串,新创建的字符串和原始字符串在内容上是相同的,但它们是不同的对象实例。

方法签名

public String(String original)

参数original 是一个已有的 String 对象,作为新字符串的内容来源

示例代码

public class StringConstructorExample {public static void main(String[] args) {// 原始字符串String original = "Hello, World!";// 使用 String(String original) 构造方法创建新的字符串对象String copy = new String(original);// 输出原始字符串和新字符串System.out.println("原始字符串: " + original);System.out.println("新创建的字符串: " + copy);// 比较两个字符串的内容System.out.println("两个字符串内容是否相同: " + original.equals(copy));// 比较两个字符串的引用System.out.println("两个字符串是否为同一对象: " + (original == copy));}
}

运行结果:

代码解释

  1. 创建原始字符串:定义一个字符串 original,其内容为 "Hello, World!"
  2. 使用构造方法创建新字符串:通过 new String(original) 调用 String(String original) 构造方法,创建一个新的字符串对象 copy,该对象是 original 的副本。
  3. 输出字符串内容:分别打印原始字符串和新创建的字符串。
  4. 比较字符串内容:使用 equals() 方法比较 original 和 copy 的内容是否相同,结果为 true,因为它们的字符序列是一样的。
  5. 比较字符串引用:使用 == 运算符比较 original 和 copy 是否为同一对象,结果为 false,因为它们是不同的对象实例,只是内容相同。

(1) 功能与行为

作用:基于现有字符串创建一个新对象(内容与原字符串相同)。

内存影响:

String s1 = "hello";          // s1 指向常量池中的 "hello"
String s2 = new String(s1);   // s2 指向堆中的新对象,但内容为 "hello"

验证:

System.out.println(s1 == s2);          // false(地址不同)
System.out.println(s1.equals(s2));     // true(内容相同)

(2) @IntrinsicCandidate 注解说明

该标注是让方法成为候选,不建议用

实际代码:在 OpenJDK 源码中,String(String original) 构造方法确实标记了 @IntrinsicCandidate。

注解作用:提示 JVM 可以对该方法进行底层优化(如内联、替换为本地代码),不影响开发者使用。

是否建议使用: 无性能问题,但大多数场景直接使用字面量赋值即可,无需显式调用此构造方法。

相关文章:

Java String 类深度解析:内存模型、常量池与核心机制

目录 一、String初识 1. 字符串字面量的处理流程 (1) 编译阶段 (2) 类加载阶段 (3) 运行时阶段 2. 示例验证 示例 1:字面量直接赋值 示例 2:使用 new 创建字符串 示例 3:显式调用 intern() 注意点1: ⑴. String s1 &q…...

不到一个月,SQLite 3.49.0来了

距离 SQLite 3.48.0 发布不到一个月,SQLite 开发团队于 2025 年 2 月 6 日发布了 SQLite 3.49.0 版本。这更新速度的确让人感动,那么这个版本又有哪些更新呢? 查询优化器 新版本改进了自动索引(query-time index)优化…...

探索顶级汽车软件解决方案:驱动行业变革的关键力量

在本文中,将一同探索当今塑造汽车行业的最具影响力的软件解决方案。从设计到制造,软件正彻底改变车辆的制造与维护方式。让我们深入了解这个充满活力领域中的关键技术。 设计软件:创新车型的孕育摇篮 车辆设计软件对于创造创新型汽车模型至…...

ThreadPoolExecutor 详解

一、ThreadPoolExecutor 核心参数 构造函数如下: public ThreadPoolExecutor(int corePoolSize, // 核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 非核心线程空闲存活时间TimeUnit unit, // 存活时间单位BlockingQueue…...

TikTok走红全球:中国短视频平台以全新姿态登陆海外市场

在数字化浪潮中,短视频已经成为全球年轻人表达自我、分享生活的重要方式。TikTok,这个起源于中国的短视频平台,以其独特的魅力和创新的功能在全球范围内迅速走红。本文将探讨TikTok如何以全新姿态登陆海外市场,并分析其成功的关键…...

Qt的QTableWidget样式设置

在 Qt 中,可以通过样式表(QSS)为 QTableWidget 设置各种样式。以下是一些常见的样式设置示例: 1. 基本样式设置 tableWidget->setStyleSheet(// 表格整体样式"QTableWidget {"" background-color: #F0F0F0;…...

计算机毕业设计Python旅游评论情感分析 NLP情感分析 LDA主题分析 bayes分类 旅游爬虫 旅游景点评论爬虫 机器学习 深度学习

温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...

渗透测试扫描器全解析:分类与介绍

目录 前言 操作系统扫描器:系统漏洞的 “侦察兵” 特点 代表性工具 网站 Web 漏洞扫描器:Web 应用的 “安全卫士” 特点 代表性工具 手机漏洞扫描器:移动设备的 “安全护盾” 特点 代表性工具 前言 在数字技术飞速发展的当下&…...

day9手机创意软件

趣味类 in:记录趣味生活(通用) 魔漫相机:真人变漫画(通用) 活照片:让照片活过来(通用) 画中画相机:与众不同的艺术 年龄检测仪:比一比谁更年轻&#xf…...

A001基于SpringBoot实现的小区物业管理系统

系统介绍 基于SpringBoot实现的小区物业管理系统是为物业管理打造的一款在线管理平台,它可以实时完成信息处理,对小区信息、住户等进行在线管理,使其系统化和规范化。 系统功能说明 1、系统共有物业、业主角色,物业拥有系统最高…...

【Uniapp】关于实现下拉刷新的三种方式

在小程序、h5等地方中,常常会用到下拉刷新这个功能,今天来讲解实现这个功能的三种方式:全局下拉刷新,组件局部下拉刷新,嵌套组件下拉刷新。 全局下拉刷新 这个方式简单,性能佳,最推荐&#xf…...

常见的 Web 攻击方式有哪些,如何防御?

一、XSS攻击(跨站脚本攻击) 攻击原理:恶意脚本通过用户输入注入页面,分为存储型(数据库持久化)、反射型(URL参数注入)、DOM型(客户端脚本修改) 防御方案&am…...

深入理解DeepSeek与企业实践(二):32B多卡推理的原理、硬件散热与性能实测

前言 在《深入理解 DeepSeek 与企业实践(一):蒸馏、部署与评测》文章中,我们详细介绍了深度模型的蒸馏、量化技术,以及 7B 模型的部署基础,通常单张 GPU 显存即可满足7B模型完整参数的运行需求。然而&…...

数据结构-链式二叉树

文章目录 一、链式二叉树1.1 链式二叉树的创建1.2 根、左子树、右子树1.3 二叉树的前中后序遍历1.3.1前(先)序遍历1.3.2中序遍历1.3.3后序遍历 1.4 二叉树的节点个数1.5 二叉树的叶子结点个数1.6 第K层节点个数1.7 二叉树的高度1.8 查找指定的值(val)1.9 二叉树的销毁 二、层序…...

【云安全】云原生- K8S etcd 未授权访问

什么是etcd? etcd 是一个开源的分布式键值存储系统,主要用于存储和管理配置信息、状态数据以及服务发现信息。它采用 Raft 共识算法,确保数据的一致性和高可用性,能够在多个节点上运行,保证在部分节点故障时仍能继续提…...

AI时代的前端开发:对抗压力的利器

在飞速发展的AI时代,前端开发工程师们面临着前所未有的挑战。项目周期不断缩短,需求变化日新月异,交付压力更是与日俱增,这使得开发人员承受着巨大的压力。如何提升对抗压能力,成为摆在每一位前端工程师面前的重要课题…...

在npm上传属于自己的包

前言 最近在整理代码,上传到npm方便使用,所以学习了如何在npm发布一个包,整理写成一篇文章和大家一起交流。 修改记录 更新内容更新时间文章第一版25.2.10新增“使用包”,“删除包的测试”25.2.12 1、注册npm账号 npm | Home 2…...

[Spring] Spring常见面试题

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...

适配器模式详解(Java)

一、引言 1.1 定义与类型 适配器模式是一种结构型设计模式,主要目的是将一个类的接口转换为客户期望的另一个接口。这种模式使得原本因为接口不匹配而不能一起工作的类可以一起工作,从而提高了类的复用性。适配器模式分为类适配器和对象适配器两种类型。类适配器使用继承关…...

22.4、Web应用漏洞分析与防护

目录 Web应用安全概述DWASP Top 10Web应用漏洞防护 - 跨站脚本攻击XSSWeb应用漏洞防护 - SQL注入Web应用漏洞防护 - 文件上传漏洞Web应用漏洞防护 - 跨站脚本攻击XSS Web应用安全概述 技术安全漏洞,主要是因为技术处理不当而产生的安全隐患,比如SQL注入…...

【Pandas】pandas Series drop

Pandas2.2 Series Computations descriptive stats 方法描述Series.align(other[, join, axis, level, …])用于将两个 Series 对齐,使其具有相同的索引Series.case_when(caselist)用于根据条件列表对 Series 中的元素进行条件判断并返回相应的值Series.drop([lab…...

SpringBoot实战:高效获取视频资源

文章目录 前言技术实现SpringBoot项目构建产品选取配置数据采集 号外号外 前言 在短视频行业高速发展的背景下,海量内容数据日益增长,每天都有新的视频、评论、点赞、分享等数据涌现。如何高效、精准地获取并处理这些庞大的数据,已成为各大平…...

mysql大数据量分页查询

一、什么是‌MySQL大数据量分页查? MySQL大数据量分页查‌是指在使用MySQL数据库时,将大量数据分成多个较小的部分进行显示,以提高查询效率和用户体验。分页查询通常用于网页或应用程序中,以便用户能够逐步浏览结果集。 二、为什…...

迅为RK3568开发板篇OpenHarmony实操HDF驱动配置LED-LED测试

将编译好的镜像全部进行烧写,镜像在源码根目录 out/rk3568/packages/phone/images/目录下。 烧写完成之后,在调试串口查看打印日志,如下图所示: 然后打开 hdc 工具,运行测试程序,输入“led_test 1”&…...

OpenGL ES -> 投影变换矩阵完美解决绘制GLSurfaceView绘制图形拉伸问题

GLSurfaceView绘制图形拉伸问题 假如在XML文件中声明GLSurfaceView的宽高为 android:layout_width"match_parent"android:layout_height"match_parent GLSurfaceView绘制的图形在Open GL ES坐标系中,而Open GL ES坐标系会根据GLSurfaceView的宽高将…...

玩转大语言模型——使用Kiln AI可视化环境进行大语言模型微调数据合成

系列文章目录 玩转大语言模型——使用langchain和Ollama本地部署大语言模型 玩转大语言模型——三分钟教你用langchain提示词工程获得猫娘女友 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 玩转大语言模型—…...

图书管理项目(spring boot + Vue)

想要该项目的话,就 jia 我,并在评论区给我说一下,只需要1元,我把整个项目发给你 jia微:18439421203(名字叫:Bingo) 运行图片:...

【机器学习】简单线性回归算法及代码实现

线性回归算法 一、摘要二、线性回归算法概述三、损失函数的定义和衡量标准四、简单线性回归的求解和应用五、机器学习算法一般求解思路 一、摘要 本文讲解了线性回归算法的基础知识和应用,强调线性回归主要用于解决回归问题。通过分析房产价格与房屋面积的关系&…...

AI-大模型(3)-MoE模型

1.什么是MOE模型 多个领域专家共同工作,并行计算。 2.MOE如何工作 gate层:根据输入Token选择专家 基于Token来选择专家 Gate层选择专家 除专家外,其他层共享一个token可以选择多个专家 一个token 可以选择一个专家或者多个专…...

PySide(PyQT)使用场景(QGraphicsScene)进行动态标注的一个demo

用以标注图像的一个基本框架demo import sys from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QMainWindow, QLabel, QGraphicsPixmapItem from PySide6.QtGui import QPixmap, QPainter, QTransform from PySide6.QtCore import Qt, QPointF, S…...