简单聊聊java中各种常量池

news2024/12/24 2:59:51

引子

小试身手

首先我们来看一道题

 Integer i1 = 127;  
 Integer i2 = 127;
 System.out.println(i1 == i2); 
 //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
 
//值大于127时,不会从对象池中取对象  
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);

这道题是笔试题中常见的一道题。答案相信大部分人都知道,第3行输出true,第9行输出false。

本着知其然,知其所以然的精神我们进一步探究得知:第1,2,7,8行实际执行的是Integer.valueOf()。这个方法出于减少对象创建次数和节省内存的考虑,会对数值为-128~127之间的Integer对象进行缓存,如果valueOf()方法传入的参数在这个范围之内,就直接返回缓存中的对象

乘胜追击

上面的理论大家可能都知道,但是这个理论是不是和“鬼”一样,大家只听过,没见过呢?下面我来和大家一起,揭开它的神秘面纱。

首先可能大家会问,第1,2,7,8行实际执行的是Integer.valueOf(),这个你是怎么知道的呢?是不是也是从书上看来的。能不能证明给我们看看?

javap -v 命令

当然可以,我们都知道java文件在运行的时候会被编译为字节码文件,要想解答上面疑惑我们只需要查看字节码就可以了。找到class文件,一个简单的 javap -v 命令就可以搞定。

在这里插入图片描述

字节码我们看过了,但是还是没有看到缓存。下一步我们在来看下源码。

在这里插入图片描述

在来看下以下片段。这个一个static代码块,在项目启动的时候就从low到high依次加入到Integer数组中也就是所谓的缓存中。

在这里插入图片描述

二 包装类的常量池(对象池)

Java中有6种基本类型的包装类型实现了常量池技术,分别是:Byte,Short,Integer,Long,Character,Boolean,其中Byte,Short,Integer,Long,Character这5种整型的包装类只是在对应值小于等于127时才可使用对象池。 另外两种浮点数类型的包装类型没有实现。之所以这样设计是基于使用频率的考虑。

Byte a1 = 1;
Byte a2 = 1;
System.out.println(a1 == a2);
 
Short b1 = 2;
Short b2 = 2;
System.out.println(b1 == b2);
 
Character c1 = 'a';
Character c2 = 'a';
System.out.println(c1 == c2);
 
Long d1 = 11L;
Long d2 = 11L;
System.out.println(d1 == d2);
 
Boolean e1 = false;
Boolean e2 = false;
System.out.println(e1 == e2);
 
Float f1 = 1.0f;
Float f2 = 1.0f;
System.out.println(f1 == f2);//false
 
Double g1 = 1.0;
Double g2 = 1.0;
System.out.println(g1 == g2);//false

上面这段代码,除了最后两个为false,其余均为true。需要注意使用new关键词生成的对象不会使用常量池。这里就不需要做过多解释了。下面进入今天的重点内容。

Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
 
System.out.println(i1 == i2);    //true
System.out.println(i1 == i4);   //false
System.out.println(i4 == i5);   //false
//自动拆箱:
// 在进行“+”运算时,Integer类型自动拆箱为int,
// “==”运算时Integer无法与int,所以也拆箱为int。
//所以最终变成了int 与int 比较
System.out.println(i1 == i2 + i3);  //true
System.out.println(i4 == i5 + i6);  //true
System.out.println(40 == i5 + i6);  //true

分析:自动拆箱。在进行“+”运算时,Integer类型自动拆箱为int, “==”运算时Integer无法与int,所以也拆箱为int。所以最终变成了int 与int 比较。在字节码中我们可以看到Integer.intValue。

在这里插入图片描述

三 常量池的概念以及分类

1 常量池的概念

很多同学都知道常量池的概念,但是你是否对class文件常量池,运行时常量池,字符串常量池傻傻分不清楚呢?我们通过下面这张图来直观感受一下。

在这里插入图片描述

2 Class文件常量池

首先我们来看下class文件常量池。Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外, 还有一项信息是常量池表(Constant Pool Table) , 用于存放编译期生成的各种字面量符号引用, 这部分内容将在类加载后存放到方法区的运行时常量池中。

这里的常量池表就是Class文件常量池。

常量池表(Constant Pool Table)

我们使用javap命令查看Test1的class文件。

Classfile /D:/PPT/constant-pool/target/classes/com/pool/Test1.class
  Last modified 2021-12-26; size 384 bytes
  MD5 checksum 5a9a4b4f205a38c85fa7634109358b46
  Compiled from "Test1.java"
public class com.pool.Test1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#17         // java/lang/Object."<init>":()V
   #2 = Methodref          #18.#19        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Fieldref           #4.#20         // com/pool/Test1.i1:Ljava/lang/Integer;
   #4 = Class              #21            // com/pool/Test1
   #5 = Class              #22            // java/lang/Object
   #6 = Utf8               i1
   #7 = Utf8               Ljava/lang/Integer;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Lcom/pool/Test1;
  #15 = Utf8               SourceFile
  #16 = Utf8               Test1.java
  #17 = NameAndType        #8:#9          // "<init>":()V
  #18 = Class              #23            // java/lang/Integer
  #19 = NameAndType        #24:#25        // valueOf:(I)Ljava/lang/Integer;
  #20 = NameAndType        #6:#7          // i1:Ljava/lang/Integer;
  #21 = Utf8               com/pool/Test1
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/Integer
  #24 = Utf8               valueOf
  #25 = Utf8               (I)Ljava/lang/Integer;
{
  java.lang.Integer i1;
    descriptor: Ljava/lang/Integer;
    flags:
 
  public com.pool.Test1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: bipush        127
         7: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        10: putfield      #3                  // Field i1:Ljava/lang/Integer;
        13: return
      LineNumberTable:
        line 11: 0
        line 12: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0  this   Lcom/pool/Test1;
}

我们再来理解下这句话:Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外, 还有一项信息是常量池表(Constant Pool Table)。

字节码中第6,7行即为类的版本。 第35至58行为字段、方法、接口等描述。第9行Constant pool至34行,即为常量池表。

我们也可以换一种"非人类视角"来查看class文件。使用文本编辑器打开class文件,调整为16进制查看。如果文本编辑器默认不支持,需要自己安装一下插件。(此部分仅作了解,可以跳过)

在这里插入图片描述

第一眼看上去是一堆乱码,但是如果你知道了规律就会发现有章可循。通过对照下图我们就可以阅读class文件了。

  • 魔数:cafe babe。每个Class文件的头4个字节被称为魔数(Magic Number) , 它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。
  • 次版本号(MinorVersion):0 。 第5和第6个字节是次版本号(MinorVersion)。
  • 主版本号(Major Version):0034 转为10进制为52,即为JDK8 。 第7和第8个字节是主版本号(Major Version)
  • 常量池容量计数值(constant_pool_count)
  • 常量池(constant_pool)

在这里插入图片描述

要想阅读常量池的内容,就需要使用常量池项目类型表,感兴趣的同学推荐阅读下《深入理解Java虚拟机》“6.3 Class类文件的结构”,这里就不在深入解析了。

在这里插入图片描述

通过上面的常量池项目类型我们可以知道,class文件常量池中主要存放两大类常量: 字面量(Literal) 和符号引用(Symbolic References) 。

字面量(Literal)

字面量比较接近于Java语言层面的常量概念, 如文本字符串、 被声明为final的常量值等。

字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面量。

符号引用(Symbolic References)

符号引用则属于编译原理方面的概念, 主要包括下面几类常量:

  • 被模块导出或者开放的包(Package)·
  • 类和接口的全限定名(Fully Qualified Name)·
  • 字段的名称和描述符(Descriptor)·
  • 方法的名称和描述符·
  • 方法句柄和方法类型(Method Handle、 Method Type、 Invoke Dynamic)·
  • 动态调用点和动态常量(Dynamically-Computed Call Site、 Dynamically-Computed Constant

下面我们通过一个实例来帮助大家理解字面量和符号引用。

package com.pool;
public class Test2 {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;
        System.out.println(i1 == i2);//输出true
        //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
 
        //值大于127时,不会从对象池中aaa取对象
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);//输出false
 
    }
}

字面量: 等号右边的 127,128即为字面量。

符号引用:

  • 类的全限定名 com.pool.Test2

  • 字段的名称 i1,i2,i3,i4

  • 方法的名称 main()

3 运行时常量池(Runtime Constant Pool)

运行时常量池(Runtime Constant Pool) 是方法区的一部分。 Class文件常量池里的内容将在类加载后存放到方法区的运行时常量池中,并将符号引用解析为直接引用。

我们看到类文件的信息存储在class静态文件中,在程序运行时会由类加载器加载到运行时常量池中,并在解析阶段,将符号引用转换为直接引用,指向真正的内存地址。

这里可以理解为 class文件信息为一份超市的购物清单,上面写着苹果,橘子等。 等你到了超市,超市的导购在你的购物清单上加上了货物的地址信息。 苹果 -> A货架5排3列,橘子->B货架3排5列。

4 字符串常量池(String Table)

字符串常量池里的内容是在类加载、验证、准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。

在这里插入图片描述

四 字符串常量池深度解析

1 字符串常量池的设计思想

字符串常量池的设计思想和基本类型的包装类型常量池设计思想是一样的。

1.在内存中为字符串开辟一块空间,作为字符串常量池,类似于缓存。

2.创建字符串时,首先查询字符串常量池是否存在该字符串。

3.如果存在返回引用实例,不存在则实例化该字符串并放入常量池中。

字符串常量池位置

JDK1.7及以后

在这里插入图片描述

JDK1.6及以前

在这里插入图片描述

在JDK 6或更早之前的HotSpot虚拟机中, 常量池都是分配在永久代中, 我们可以通过-XX: PermSize和-XX: MaxPermSize限制永久代的大小, 即可间接限制其中常量池的容量。

/**
 * VM Args: -XX:PermSize=6M -XX:MaxPermSize=6M
 * @author zzm
 */
public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
// 使用Set保持着常量池引用, 避免Full GC回收常量池行为
        Set<String> set = new HashSet<String>();
// 在short范围内足以让6MB的PermSize产生OOM了
        short i = 0;
        while (true) {
            set.add(String.valueOf(i++).intern());
        }
    }
}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

自JDK 7起,原本存放在永久代的字符串常量池被移至Java堆之中。使用-Xmx参数限制最大堆到6MB就能够看到以下异常:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

当年使用永久代来实现方法区的决定并不是一个好主意,这种设计导致了Java应用更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小,而J9和JRockit只要没有触碰到进程可用内存的上限,例如32位系统中的4GB限制,就不会出问题),而且有极少数方法(例如String::intern())会因永久代的原因而导致不同虚拟机下有不同的表现。当Oracle收购BEA获得了JRockit的所有权后,准备把JRockit中的优秀功能,譬如Java Mission Control管理工具,移植到HotSpot虚拟机时,但因为两者对方法区实现的差异而面临诸多困难。考虑到HotSpot未来的发展,在JDK 6的时候HotSpot开发团队就有放弃永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划了[1],到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出,而到了JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Metaspace)来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。 --《深入理解Java虚拟机》

2 字符串常量池的创建

字符串的创建有3种方式:

使用字面量直接赋值

String s="hello";

使用字面量赋值,只会直接分配到字符串常量池中。如果字符串常量池中存在则直接返回,如果不存在则先创建,然后返回。

这里需要注意字符串常量池中存储的是引用值而不是实例对象,具体的实例对象存储在堆中。

在这里插入图片描述

example-1:

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

example-2:

String s1="hello";
String s2="he" + "llo";
System.out.println( s1==s2 ); //true

分析:s2=字符串常量(字面量)+字符串常量(字面量),所以s2一定也是字面量。

编译期优化:如果在编译期就能确定为字面量,则编译器在编译时会进行优化。

如下代码编译成字节码是一样的

在这里插入图片描述

example-3:

String a = "a1";
String b = "a" + 1;
System.out.println(a == b); // true 
 
String c = "atrue";
String d = "a" + "true";
System.out.println(a == b); // true 
 
String e = "a3.4";
String f = "a" + 3.4;
System.out.println(a == b); // true

字节码如下:

在这里插入图片描述

example-4:

String a = "hello";
final String bb = "llo";
String b = "he" + bb;
System.out.println(a == b); // true

分析:被声明为final的常量值为字面量。所以可以在编译期优化。

在这里插入图片描述

example-5:

public static final String A; // 常量A
public static final String B;    // 常量B
 
static {
    A = "he";
    B = "llo";
}
 
private static void example11() {
    String s1 = A + B;
    String s2 = "hello";
    System.out.println(s1 == s2); //false
}

分析:先来先看下字面量的定义,并对比example-4。这里明明也是声明为final的常量为什么结果不一样。原因很简单,因为A,B没有在编译期赋值,只要到了运行时才会正在被创建。编译期无法进行优化

使用new String()创建

String s1 = new String("hello");

这种方式创建字符串可以理解成两步:

1.“hello"为字面量,所以首先按照字面量的方式在常量池中创建"hello”。

2.使用new指令,会在堆中开辟一块空间,然后再将堆的地址返回到栈上保存。

在这里插入图片描述

example-6:

String s0="hello";
String s1=new String("hello");
String s2="he" + new String("llo");
System.out.println( s0==s1 );  // false
System.out.println( s0==s2 );  // false
System.out.println( s1==s2 );  // false

分析:编译期无法优化:用new String() 创建的字符串不是常量,不能在编译期就确定,不会放入常量池中,会在堆上分配空间。

example-7:

String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println(a == b); // false
 
private static String getBB() 
{  
    return "b";  
 }

分析:编译期无法优化

example-8:

String s = "he" + "llo";  //编译时优化为: "hello";
String a = "he";
String b = "llo";
String s1 = a + b;
System.out.println(s == s1);//false

分析:注意这里 s1 = a+b ,a和b都不是字面量,而是符号引用,在编译期无法优化。

对于上面三个例子,我们来观察下字节码

在这里插入图片描述

可以看出来JVM指令码中实际是通过StringBuilder.append来实现的“+”操作,然后调用toString()。再来看下StringBuilder类的toString源码,底层还是一个new String操作。

在这里插入图片描述

所以对于String s1 = a + b;(符号引用相加)可以理解为如下代码,等同于new String()的效果。

StringBuilder temp = new StringBuilder();
temp.append(a).append(b);
String s1 = temp.toString();

使用intern方法创建

String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。(jdk1.6版本需要将 s1 复制到字符串常量池里)

注意
比如:String s2 = s1.intern()
拆分为两块来看:

  • s1.intern()
    如果常量池中不包含s1对象的字符串,则把该字符串添加到常量池;如果包含,则不做操作。
  • String s2 =
    如果该方法的返回值有变量接收,如上面的s2,它始终会得到代表池中这个字符串的引用

1.字符串常量池中已经包含一个等于此String对象的字符串

example-9:

//s0指向常量池
String s0 = "hello";
//s1指向堆中
String s1 = new String("hello");
//s2指向常量池
String s2 = s1.intern();
 
System.out.println(s0 == s2);   //ture
System.out.println(s1 == s2);  //false

在这里插入图片描述

2.字符串常量池中没有包含一个等于此String对象的字符串

example-10:

String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
System.out.println(s1 == s2);

1.JDK1.6中,调用 intern() 方法,当字符串常量池中不存在时,会在永久代上创建一个实例,并存入常量池中。所以在JDK1.6中答案为false,创建了6个对象

解析

String s1 = new String(“he”) + new String(“llo”);
String s2 = s1.intern();

  1. 在常量池创建字面量“he”
  2. 在堆中创建new String(“he”)
  3. 同1,2创建“llo”
  4. 在堆上创建“hello”,“+”实际是通过StringBuilder.append().toString()实现,源码里最终使用了new String()。但是这里需要注意此处没有字面量出现,所以不会在常量池中创建对象
  5. 将“hello”的地址返回给s1
  6. s1.intern()在jdk1.6拷贝字符串的实例到永久代了

在这里插入图片描述

2.从JDK7开始常量池移入了堆中,因此JDK 7(以及部分其他虚拟机,例如JRockit)的intern()方法实现就不需要再拷贝字符串的实例到永久代了,既然字符串常量池已经移到Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可。所以在JDK1.7中答案为true,创建了5个对象

String s1 = new String(“he”) + new String(“llo”);
String s2 = s1.intern();

  1. 在常量池创建字面量“he”
  2. 在堆中创建new String(“he”)
  3. 同1,2创建“llo”
  4. 在堆上创建“hello”,“+”实际是通过StringBuilder.append().toString()实现,源码里最终使用了new String()。但是这里需要注意此处没有字面量出现,所以不会在常量池中创建对象
  5. 将“hello”的地址返回给s1
  6. s1.intern()和之前的版本不同jdk1.7修改为直接指向堆上的实例,然后返回给S2

在这里插入图片描述

example-11:

String s1 = new String("he") + new String("llo");
String s2 = "hello";
System.out.println(s1 == s2); //false

example-12:

String s1 = new String("he") + new String("llo");
s1.intern();
String s2 = "hello";
System.out.println(s1 == s2);//true

分析:因为常量池中没有“hello”,S1.intern会将堆中的对象放入到常量池

在这里插入图片描述

我们在第2行加一句String s3 = new String(“hello”);

example-13:

String s1 = new String("he") + new String("llo");
String s3 = new String("hello");
s1.intern();
String s2 = "hello";
System.out.println(s1 == s2); //false
System.out.println(s1 == s3); //false
System.out.println(s2 == s3); //false

分析:因为第2行 new String()的原因,所以在常量池中创建了对象。 s1.intern检查到常量池中已有“hello”不再创建,相当于s1.intern()没有任何影响。而s2拿到的是常量池中的对象。所以都不相等。

在这里插入图片描述

把上面的第2行,第3行交换下位置,再看看

example-14:

String s1 = new String("he") + new String("llo");
s1.intern();
String s3 = new String("hello");
String s2 = "hello";
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s2 == s3);//false

在这里插入图片描述

特例:

example-15:

String str1 = new StringBuilder("he").append("llo").toString();
System.out.println(str1 == str1.intern());  //true
 
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2 == str2.intern());  //false
 
String str3 = new StringBuilder("in").append("t").toString();
System.out.println(str3 == str3.intern());  //false
 
String str4 = new StringBuilder("dou").append("ble").toString();
System.out.println(str4 == str4.intern());  //false

分析:发现规律了吗? 部分java关键字在JVM初始化的时候已经放入了常量池。

五 总结

  1. 基本类型的包装类的常量池:

    • Java中有6种基本类型的包装类型实现了常量池技术,分别是Byte,Short,Integer,Long,Character,Boolean,其中Byte,Short,Integer,Long,Character这5种整型的包装类只是在对应值小于等于127时才可使用对象池。另外两种浮点数类型的包装类型没有实现。

    • 包装类型在进行运算时可能会自动拆箱

  2. 三种常量池:

    • Class文件常量池(字面量,符号引用),其中字面量的概念需要重点掌握。

    • 运行时常量池

    • 字符串常量池

  3. 字符串常量池

    • 字符串常量池位置,jdk不同版本中的变化。

    • 字符串常量池的创建

      • 使用字面量直接赋值
      • 使用new String()创建
      • 使用intern方法创建

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1191753.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4S店汽车行业万能通用小程序源码系统 在线预约试驾+购车计算器 源码完全开源可二次开发

随着互联网技术的发展和普及&#xff0c;越来越多的消费者开始依赖于互联网进行消费。传统的汽车销售模式也正在经历着数字化转型&#xff0c;以适应消费者需求的变化。这款小程序源码系统就是为帮助汽车4S店等销售商实现数字化转型而开发的。 以下是部分核心功能的代码模块&a…

合成数据在医疗保健行业的案例研究

从机器人辅助手术到医学成像技术&#xff0c;人工智能在医疗保健领域的应用正在迅速改变医疗保健行业&#xff0c;并改善服务成本和服务质量。例如&#xff0c;埃森哲表示&#xff0c;到 150 年&#xff0c;人工智能临床健康应用每年可以为美国医疗保健行业节省 2026 亿美元。 …

C++中的函数重载:多功能而强大的特性

引言 函数重载是C编程语言中的一项强大特性&#xff0c;它允许在同一个作用域内定义多个同名函数&#xff0c;但这些函数在参数类型、个数或顺序上有所不同。本文将深入探讨函数重载的用法&#xff0c;以及它的优势和应用场景。 正文 在C中&#xff0c;函数重载是一项非常有…

Spring Security使用总结八,Security的第二个功能授权,不同的角色访问不同的资源

前面五章基本都是给认证做铺垫的,这一章是security的另一个硬菜:授权,你在我这里注册,成为唯爱痞,我给你个令牌,你可以访问我资源,但是不能所有资源都给你,于是就有了授权,你只能访问我让你访问的资源,我不让你访问的资源,你一点都别想看。这里就出现了角色,不同的…

一个不用充钱也能让你变强的 VSCode 插件!!!

今天给大家推荐一款不用充钱也能让你变强的 vscode 插件 通义灵码&#xff08;TONGYI Lingma&#xff09;&#xff0c;可以称之为 copilot 的替代甜品 &#x1f4aa; 前言 之前一直使用的 GitHub Copilot&#xff0c;虽然功能强大&#xff0c;但是收费相对来说有点贵&#xf…

C 语言 while 和 do...while 循环

在本教程中&#xff0c;您将在示例的帮助下学习在C语言编程中创建while和do ... while循环。 在编程中&#xff0c;循环用于重复代码块&#xff0c;直到满足指定条件为止。 C语言编程具有三种类型的循环。 for循环 while循环 do... while循环 在上一教程中&#xff0c;我…

解决:AttributeError: ‘WebDriver‘ object has no attribute ‘find_element_by_xpath‘

解决&#xff1a;AttributeError: ‘WebDriver’ object has no attribute ‘find_element_by_xpath’ 背景 在使用之前的代码通过selenium定位元素时&#xff0c;报错&#xff1a;selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to l…

vioovi的ECRS工时分析软件:食品加工行业的生产效率提升利器

在食品加工行业&#xff0c;提高生产效率、降低成本、优化资源配置是至关重要的。随着科技的不断发展&#xff0c;越来越多的企业开始借助先进的软件工具来助力生产管理。本文将介绍一款备受食品加工企业青睐的工业工程软件——vioovi的ECRS工时分析软件&#xff0c;并探讨其如…

dapp技术开发

随着区块链技术的普及和应用&#xff0c;DApp&#xff08;去中心化应用&#xff09;逐渐成为了区块链领域中备受关注的核心部分。DApp是一种运行在去中心化网络上的应用程序&#xff0c;其开发、部署和运行都不依赖于任何中心化的实体或中介机构。这种应用程序的兴起和发展&…

基于springboot 手工艺品在线展示系统-计算机毕设 附源码 42553

springboot 手工艺品在线展示系统 目 录 摘要 1 绪论 1.1本课题研究意义 1.2系统开发目的 2 1.3系统开发技术的特色 3 1.4 springboot框架介绍 3 1.5论文结构与章节安排 4 2 手工艺品在线展示系统系统分析 5 2.1 可行性分析 5 2.2 系统流程分析 5 2.2.1数据增加流程 5 2.2.…

【正点原子STM32连载】 第四十八章 内存管理实验 摘自【正点原子】APM32F407最小系统板使用指南

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html## 第四…

如何用Java高效地存入一万条数据?这可能是你面试成功的关键!

大家好&#xff0c;我是你们的小米&#xff0c;一个热爱技术、喜欢分享的29岁程序猿。今天我要和大家聊一聊一个常见的面试题&#xff1a;在Java中&#xff0c;当我们需要将一万条数据存储到数据库时&#xff0c;如何能够提高存储效率呢&#xff1f; 在面试过程中&#xff0c;…

生活污水处理一体化处理设备有哪些

生活污水处理一体化处理设备有多种类型&#xff0c;包括但不限于以下几种&#xff1a; 鼓风机&#xff1a;提供曝气系统所需的气流。潜水污水提升泵&#xff1a;将污水从低处提升到高处。旋转式滚筒筛分机&#xff1a;对污水中的悬浮物进行分离和筛选。回旋式格栅&#xff1a;…

以技术创新引领行业发展,飞凌嵌入式获双项省级荣誉

近日&#xff0c;飞凌嵌入式荣获「2023年河北省专精特新示范企业」以及「第五批省级制造业单项冠军企业」两项殊荣。这两项荣誉的获得&#xff0c;是对飞凌嵌入式在专业技术领域与创新能力的高度认可&#xff0c;荣誉的背后&#xff0c;凝聚着飞凌嵌入式无数次的研发探索与对创…

Vite项目的初体验 - 非Vite脚手架版本

开箱即用 &#xff08;out of box&#xff09;: 无需做任何的配置&#xff0c;就可以用vite来帮助我们处理构建工作。 前提 &#xff1a;node 版本 > 12.0.0&#xff0c;使用 npm 进行依赖管理。 本文的案例&#xff0c;从0到1的&#xff0c;一步一步的体会vite的作用。 本文…

QQ恢复聊天记录,就用这3个方法!

无论是因为误操作、手机丢失、系统崩溃&#xff0c;还是因为更换了新手机&#xff0c;恢复重要的QQ聊天记录都是一件必做的事情。通过聊天记录&#xff0c;用户可以随时查看之前的信息&#xff0c;以便了解事情的经过。 那么&#xff0c;如何恢复丢失的QQ聊天记录呢&#xff1…

【算法】道路与航线(保姆级题解)

题目 农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。 他想把牛奶送到 T 个城镇&#xff0c;编号为 1∼T。 &#xff08;存在T个点&#xff09; 这些城镇之间通过 R 条道路 (编号为 1 到 R) 和 P 条航线 (编号为 1 到 P) 连接。 &#xff08;存在R条道路&#…

【Proteus仿真】【Arduino单片机】LCD1602-IIC液晶显示

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用PCF8574、LCD1602液晶等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602液晶显示各种效果。 二、软件设计 /* 作者&#xff1a;嗨小…

分享一个使用get_hash_value比对数据脚本

使用get_hash_value获取每个字段的值&#xff0c;再sum起来比对&#xff0c;如果表有lob字段&#xff0c;则会先排除掉lob字段再比对其它字段 这个脚本有两个问题&#xff1a; 1.如果字段所有的值长度加起来超过4000会报错&#xff0c;比对不了&#xff0c;这种情况一般比较少…

【亚马逊云科技产品测评】活动征文|AWS EC2 部署Echarts大屏展示项目

前言 Echarts简介 ECharts是一个使用JavaScript开发的&#xff0c;开源的可视化库。它可以让数据变得生动起来&#xff0c;提供直观&#xff0c;交互性强&#xff0c;可高度个性化定制的数据可视化图表。ECharts支持大部分的浏览器&#xff0c;如IE6、Chrome、Firefox、Safari等…