精华内容
下载资源
问答
  • Java中的字符串常量
    千次阅读
    2021-03-07 00:44:58

    ava中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid");,这两种方式我们在代码编写时都经常使用,尤其是字面量的方式。然而这两种实现其实存在着一些性能和内存占用的差别。这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。

    工作原理

    当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。

    举例说明

    字面量创建形式

    String str1 = "droid";

    JVM检测这个字面量,这里我们认为没有内容为droid的对象存在。JVM通过字符串常量池查找不到内容为droid的字符串对象存在,那么会创建这个字符串对象,然后将刚创建的对象的引用放入到字符串常量池中,并且将引用返回给变量str1。

    如果接下来有这样一段代码

    String str2 = "droid";

    同样JVM还是要检测这个字面量,JVM通过查找字符串常量池,发现内容为”droid”字符串对象存在,于是将已经存在的字符串对象的引用返回给变量str2。注意这里不会重新创建新的字符串对象。

    验证是否为str1和str2是否指向同一对象,我们可以通过这段代码

    System.out.println(str1 == str2);

    结果为true。

    使用new创建

    String str3 = new String("droid");

    当我们使用了new来构造字符串对象的时候,不管字符串常量池中有没有相同内容的对象的引用,新的字符串对象都会创建。因此我们使用下面代码测试一下,

    String str3 = new String("droid");

    System.out.println(str1 == str3);

    结果如我们所想,为false,表明这两个变量指向的为不同的对象。

    intern

    对于上面使用new创建的字符串对象,如果想将这个对象的引用加入到字符串常量池,可以使用intern方法。

    调用intern后,首先检查字符串常量池中是否有该对象的引用,如果存在,则将这个引用返回给变量,否则将引用加入并返回给变量。

    String str4 = str3.intern();

    System.out.println(str4 == str1);

    输出的结果为true。

    疑难问题

    前提条件?

    字符串常量池实现的前提条件就是Java中String对象是不可变的,这样可以安全保证多个变量共享同一个对象。如果Java中的String对象可变的话,一个引用操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。

    引用 or 对象

    字符串常量池中存放的时引用还是对象,这个问题是最常见的。字符串常量池存放的是对象引用,不是对象。在Java中,对象都创建在堆内存中。

    更新验证,收到的很多评论也在讨论这个问题,我简单的进行了验证。 验证环境

    22:18:54-androidyue~/Videos$ cat /etc/os-release

    NAME=Fedora

    VERSION="17 (Beefy Miracle)"

    ID=fedora

    VERSION_ID=17

    PRETTY_NAME="Fedora 17 (Beefy Miracle)"

    ANSI_COLOR="0;34"

    CPE_NAME="cpe:/o:fedoraproject:fedora:17"

    22:19:04-androidyue~/Videos$ java -version

    java version "1.7.0_25"

    OpenJDK Runtime Environment (fedora-2.3.12.1.fc17-x86_64)

    OpenJDK 64-Bit Server VM (build 23.7-b01, mixed mode)

    验证思路:以下的Java程序读取一个大小为82M的视频文件,以字符串形式进行intern操作。

    22:01:17-androidyue~/Videos$ ll -lh | grep why_to_learn.mp4

    -rw-rw-r--. 1 androidyue androidyue 82M Oct 20 2013 why_to_learn.mp4

    验证代码

    import java.io.BufferedReader;

    import java.io.FileNotFoundException;

    import java.io.FileReader;

    import java.io.IOException;

    public class TestMain {

    private static String fileContent;

    public static void main(String[] args) {

    fileContent = readFileToString(args[0]);

    if (null != fileContent) {

    fileContent = fileContent.intern();

    System.out.println("Not Null");

    }

    }

    private static String readFileToString(String file) {

    BufferedReader reader = null;

    try {

    reader = new BufferedReader(new FileReader(file));

    StringBuffer buff = new StringBuffer();

    String line;

    while ((line = reader.readLine()) != null) {

    buff.append(line);

    }

    return buff.toString();

    } catch (FileNotFoundException e) {

    e.printStackTrace();

    } catch (IOException e) {

    e.printStackTrace();

    } finally {

    if (null != reader) {

    try {

    reader.close();

    } catch (IOException e) {

    e.printStackTrace();

    }

    }

    }

    return null;

    }

    }

    由于字符串常量池存在于堆内存中的永久代,适用于Java8之前。我们通过设置永久代一个很小的值来进行验证。如果字符串对象存在字符串常量池中,那么必然抛出java.lang.OutOfMemoryError permgen space错误。

    java -XX:PermSize=6m TestMain ~/Videos/why_to_learn.mp4

    运行证明程序没有抛出OOM,其实这个不能很好的证明存储的是对象还是引用。

    但是这个至少证明了字符串的实际内容对象char[]不存放在字符串常量池中。既然这样的话,其实字符串常量池存储字符串对象还是字符串对象的引用反而不是那么重要。但个人还是倾向于存储的为引用。

    优缺点

    字符串常量池的好处就是减少相同内容字符串的创建,节省内存空间。

    如果硬要说弊端的话,就是牺牲了CPU计算时间来换空间。CPU计算时间主要用于在字符串常量池中查找是否有内容相同对象的引用。不过其内部实现为HashTable,所以计算成本较低。

    GC回收?

    因为字符串常量池中持有了共享的字符串对象的引用,这就是说是不是会导致这些对象无法回收?

    首先问题中共享的对象一般情况下都比较小。据我查证了解,在早期的版本中确实存在这样的问题,但是随着弱引用的引入,目前这个问题应该没有了。

    intern使用?

    关于使用intern的前提就是你清楚自己确实需要使用。比如,我们这里有一份上百万的记录,其中记录的某个值多次为美国加利福尼亚州,我们不想创建上百万条这样的字符串对象,我们可以使用intern只在内存中保留一份即可。关于intern更深入的了解请参考深入解析String#intern。

    总有例外?

    你知道下面的代码,会创建几个字符串对象,在字符串常量池中保存几个引用么?

    String test = "a" + "b" + "c";

    答案是只创建了一个对象,在常量池中也只保存一个引用。我们使用javap反编译看一下即可得知。

    17:02 $ javap -c TestInternedPoolGC

    Compiled from "TestInternedPoolGC.java"

    public class TestInternedPoolGC extends java.lang.Object{

    public TestInternedPoolGC();

    Code:

    0: aload_0

    1: invokespecial #1; //Method java/lang/Object."":()V

    4: return

    public static void main(java.lang.String[]) throws java.lang.Exception;

    Code:

    0: ldc #2; //String abc

    2: astore_1

    3: return

    看到了么,实际上在编译期间,已经将这三个字面量合成了一个。这样做实际上是一种优化,避免了创建多余的字符串对象,也没有发生字符串拼接问题。关于字符串拼接,可以查看Java细节:字符串的拼接。

    更多相关内容
  • C中字符串常量&字符数组&字符常量

    万次阅读 多人点赞 2019-12-17 11:21:42
    在 C 语言中,字符串有两种存储方式,一种是通过字符数组存储,另一种是通过字符指针存储。...C语言字符常量与字符串常量 备注:字符常量和字符变量: 字符常量字符常量是用单引号括起来的一个字符,...

    在 C 语言中,字符串有两种存储方式,一种是通过字符数组存储,另一种是通过字符指针存储。

    一、字符常量:

     一个用单引号括起来的单个字符(或字符转义序列或三字母词)

    实质(含义):是一个整形值。属于四大基本数据类型(分别是整型,浮点型,指针,聚合类型)中的整型(integers)。

    C语言字符常量与字符串常量

    备注:字符常量和字符变量:

    • 字符常量
      字符常量是用单引号括起来的一个字符,如'a'、'A'、'1'、'='。因此字符常量有以下特点:
      I. 字符常量只能用单引号括起来,不能用双引号或其他符号。
      II. 字符常量只能是单个字符,即单引号内只能有一个字符。
      转义字符是一种特殊的字符常量,有以下特点:
      I. 转义字符以反斜线"\"开头,后面跟一个或几个字符。
      II. 转义字符具有特定的含义,不同于字符原有的意义。
    • 字符变量
      I. 字符变量的类型说明符为char,字符变量定义格式为:char a;、char a,b;
      II. 字符变量用来存储字符常量,字符值是以ASCII码的二进制形式存放在变量的内存单元之中的,如字符'x'的ASCII码是120,定义字符变量char a = 'x';,实际上字符变量a内存放120的二进制代码。若将整型值直接赋值给字符变量,如char a = 100;,实际上是将ASCII码为100的字符'd'赋值给了字符变量a。
      III. 字符型可看做一种特别短的整型
      类型说明符:[signed] char,表示的数值范围:-128 ~ 127,存储大小:1字节
      类型说明符:unsigned char,表示的数值范围:0 ~ 255,存储大小:1字节

    字符型参与算术运算

    由于字符型可看做一种特别短的整型,因此可以将字符型与整型进行算术运算。

    #include <stdio.h>
    
    int main(void)
    {
        printf("%d\n", 'A'+2);
        printf("%c\n", 'A'+2);
    }
    

    以上程序输出:

    67
    C
    

    字符型参与算术运算,实际上是将字符对应的ASCII码进行算术运算。字符'A'对应的ASCII码为65,应此'A'+2以十进制形式输出带符号整数的结果为67;而ASCII码值为67的字符为'C',应此'A'+2以单个字符输出的结果为C。

    \n——换行,

    \t——tab,

    \r——回车,

    \f——换页,

    \b——退格,

    \ddd——1到3位8进制的数代表的字符

    二、字符串常量:

     两种定义方式:

    (字符数组)数组方式定义字符串:当数组名用于表达式中时,它们的值也是个指针常量。我们可以对它们进行下标引用、间接访问以及指针运算。

    (字符串指针)指针常量定义字符串:当一个字符串常量出现在表达式中时,它的值是个指针常量。编译器把这些指定字符的一份拷贝存储在内存的某个位置,并存储一个指向第一个字符的指针。(一个是指针的地址,一个是字符串的地址)

    1、

    char str[] = "hello, world";
    str[1] = 'a';
    
    

    2、

    char *str = "hello, world";
    str[1] = 'a';
    
    

    两个程序都可以编译,但第二个运行会出现段错误。

    两个程序的区别在:

    第一个程序的 str 属于初始化变量,str 如果是局部变量则指向栈上的内存区域,如果是 static 或全局变量则指向进程的 data 段内存区域。data 段权限是可读可写

    第二个程序中 "hello, world" 是一个字符串面量,str 的确指向其地址,但该地址存在于在进程的 text 段,text 段除了保存常量还保存可执行代码,因此是不允许可写权限的,而是只允许可读可执行权限

     实例:

    char p[] = "hello"; // 方式1
    
    char *p = "hello"; // 方式2
    
    char *p;
    p = (char *)malloc(sizeof(char)*6);
    strcpy(p, "hello"); // 方式3
    
    

    这三种情况下:
    方式1:所有6个char字符都连续的存放在栈区。内容可更改!
    方式2:"Hello"存在程序内存的常量区中,是编译时就固定下来的(不可更改),然后p是一个指向常量区"hello"的指针,p本身存在栈区
    方式3: malloc向堆申请了空间,p存放在栈区,指向malloc申请出来的地址,最后"hello"就被copy到了p所指向的地址。内容可更改!
    从速度来看1中栈的数据都是直接读的,另外两种都需要通过指针间接读取,所以1显然是最快的。
    我觉得首先如果字符串很小且确定,可以用1的写法,在栈区速度快。
    如果字符串很大或者不确定,要知道栈区大小是有限的,所以采用3的动态分配比较好。
    如果字符串被大量复用,其实可以采用2中写法,这样只要引用了常量区的同一字符串,他们将会共用同一块地址。(当然这种共用是合理的,因为那里的字符串是不可修改的,且到程序结束才会被释放)。

    C语言字符串常量

    示例代码:

    #include <stdio.h>
    #include <string.h>
    int main() {
    	printf("\t方式1……\n");
    	char p0[] = "hello"; // 方式1
    	printf("输出数组定义字符串:%s\n",p0);
    	printf("输出数组定义字符串地址:%p\n",p0);
    	//更改数组P0中的某一个元素
    	p0[0] = 'H';
    	printf("输出数组定义字符串:%s\n", p0);
    	/*
    	printf("\t方式2……\n");
    	char *p1 = "hello"; // 方式2
    	printf("输出指针定义字符串:%s\n", p1);
    	printf("输出指针定义字符串地址:%p\n", &p1);
    	//更改数组P1中的某一个元素
    	p1[0] = 'H';
    	printf("输出指针定义字符串:%s\n", p1);
    	*/
    	printf("\t方式3……\n");
    	char *p2;// 方式3
    	p2 = (char *)malloc(sizeof(char) * 6);
    	strcpy(p2, "hello");
    	printf("输出指针定义字符串:%s\n", p2);
    	printf("输出指针定义字符串地址:%p\n", &p2);
    	//更改数组P2中的某一个元素
    	p2[0] = 'H';
    	printf("输出指针定义字符串:%s\n", p2);
    	return 0;
    }
    

    运行结果:

    示例代码:

    #include <stdio.h>
    #include <string.h>
    int main() {
    	printf("\t方式1……\n");
    	char p0[] = "hello"; // 方式1
    	printf("输出数组定义字符串:%s\n",p0);
    	printf("输出数组定义字符串地址:%p\n",p0);
    	//更改数组P0中的某一个元素
    	p0[0] = 'H';
    	printf("输出数组定义字符串:%s\n", p0);
    	
    	printf("\t方式2……\n");
    	char *p1 = "hello"; // 方式2
    	printf("输出指针定义字符串:%s\n", p1);
    	printf("输出指针定义字符串地址:%p\n", &p1);
    	//更改数组P1中的某一个元素
    	p1[0] = 'H';
    	printf("输出指针定义字符串:%s\n", p1);
    	
    	printf("\t方式3……\n");
    	char *p2;// 方式3
    	p2 = (char *)malloc(sizeof(char) * 6);
    	strcpy(p2, "hello");
    	printf("输出指针定义字符串:%s\n", p2);
    	printf("输出指针定义字符串地址:%p\n", &p2);
    	//更改数组P2中的某一个元素
    	p2[0] = 'H';
    	printf("输出指针定义字符串:%s\n", p2);
    	return 0;
    }

     方式2可以编译,但运行会出现错误

    运行结果:

     C语言中字符串常量的问题

    如下示例,表明不同的字符串所在的内存位置不一样,故不相等

    C/C++对字符串常量的处理策略

    指针常量示例:

    【 C 】彻底学会字符串常量

    示例代码:

    /*
    #include <stdio.h>
    int main() {
    	char *p_char = "Hello World";
    	printf("输出字符串:%s\n",p_char);
    	printf("输出指针指向字符串的地址:%p\n",&p_char);
    	printf("输出字符串:%c\n", p_char[1]);
    	p_char[1] = 'H';
    	printf("输出字符串:%s\n", p_char);
    	//printf("输出指针指向字符串的地址:%p\n", &(p_char+1));
    	char str[] = "I Love Beijing";
    	printf("输出数组定义字符串:%s\n",str);
    	printf("输出数组定义字符串地址:%p\n",str);
    
    	return 0;
    }
    */
    
    /*
    #include <stdio.h>
    #include <string.h>
    int main() {
    	printf("\t方式1……\n");
    	char p0[] = "hello"; // 方式1
    	printf("输出数组定义字符串:%s\n",p0);
    	printf("输出数组定义字符串地址:%p\n",p0);
    	//更改数组P0中的某一个元素
    	p0[0] = 'H';
    	printf("输出数组定义字符串:%s\n", p0);
    	
    	printf("\t方式2……\n");
    	char *p1 = "hello"; // 方式2
    	printf("输出指针定义字符串:%s\n", p1);
    	printf("输出指针定义字符串地址:%p\n", &p1);
    	//技巧:
    	printf("输出指针定义字符串:%c\n", *"hello");
    	printf("输出指针定义字符串:%c\n", *("hello"+1));
    	printf("输出指针定义字符串:%c\n", *(p1 + 1));
    	//更改数组P1中的某一个元素
    	p1[0] = 'H';
    	printf("输出指针定义字符串:%s\n", p1);
    	
    	printf("\t方式3……\n");
    	char *p2;// 方式3
    	p2 = (char *)malloc(sizeof(char) * 6);
    	strcpy(p2, "hello");
    	printf("输出指针定义字符串:%s\n", p2);
    	printf("输出指针定义字符串地址:%p\n", &p2);
    	//更改数组P2中的某一个元素
    	p2[0] = 'H';
    	printf("输出指针定义字符串:%s\n", p2);
    	return 0;
    }
    */
    
    #include <stdio.h>
    #include <string.h>
    int main() {
    	printf("\t方式1……\n");
    	char p0[] = "hello"; // 方式1
    	printf("输出数组定义字符串:%s\n", p0);
    	printf("输出数组定义字符串地址:%p\n", p0);
    	//更改数组P0中的某一个元素
    	p0[0] = 'H';
    	printf("输出数组定义字符串:%s\n", p0);
    
    	printf("\t方式2……\n");
    	char *p1 = "hello"; // 方式2
    	printf("输出指针定义字符串:%s\n", p1);
    	printf("输出指针定义字符串地址:%p\n", &p1);
    	//技巧:
    	printf("输出指针定义字符串:%c\n", *"hello");
    	printf("输出指针定义字符串:%c\n", *("hello" + 1));
    	printf("输出指针定义字符串:%c\n", *(p1 + 1));
    	//更改数组P1中的某一个元素
    	p1[0] = 'H';
    	printf("输出指针定义字符串:%s\n", p1);
    
    
    	return 0;
    }
    
    

    运行结果:

     

     C语言字符常量与字符串常量

    来自知乎的提问:

    为什么char *a="xxxxx", *b="xxx"; strcpy(a, b);的用法不行?

    展开全文
  • 【JavaSE】字符串常量池、String详解

    千次阅读 2022-02-11 15:48:38
    1.String Pool 1.1字符串常量池产生时间 1.2字符串常量池的位置 1.3字符串常量池的优点 2.String 2.1不可变性 2.2定义方式 2.2.1 字面量的方式 2.2.2 new的方式 2.2.3 intern()的方式

    PS:本人学生,才疏学浅,如有错误,恳请指出。

    1.String Pool

    String 的字符串常量池(String Pool)是一个固定大小的HashTable(数组+链表的数据结构),故不存在两个相同的字符串。也叫StringTable
    StringTable是放在本地内存的,是C++写的,里面放的是字符串对象的引用,真实的字符串对象是在里。

    1.1字符串常量池产生时间

    在这里插入图片描述

    像这些静态的、未加载的.class文件的数据被称为静态常量池,但经过jvm把.class文件装入内存、加载到方法区后,常量池就会变为运行时常量池。
    在这里插入图片描述
    当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本。
    不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用。

    简单来说,HotSpot VM里StringTable是个哈希表,里面存的是驻留字符串的引用(而不是驻留字符串实例自身)。也就是说某些普通的字符串实例被这个StringTable引用之后就等同被赋予了“驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例里只有一份,被所有的类共享。类的运行时常量池里的CONSTANT_String类型的常量,经过解析(resolve)之后,同样存的是字符串的引用;解析的过程会去查询StringTable,以保证运行时常量池所引用的字符串与StringTable所引用的是一致的。

    字符串常量池
    本质就是一个哈希表
    存储的是字符串实例的引用
    在被整个JVM共享
    在解析运行时常量池中的符号引用时,会去查询字符串常量池,确保运行时常量池中解析后的直接引用跟字符串常量池中的引用是一致的

    1.2字符串常量池的位置

    以下图表示的是运行时数据区

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    1.3字符串常量池的优点

    为了避免频繁的创建和销毁对象而影响系统性能,实现了对象的共享。
    例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
    节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
    节省运行时间:比较字符串时,==equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

    2.String

    2.1不可变性

    为什么String类被设计成不可变? 传送门(点我)

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    }
    

    2.2定义方式

    2.2.1 字面量的方式

    字面量,常量和变量之间的区别?传送门(点我)

    String s1 = "hello";
    

    此时会有如下过程。
    会去解析的符号引用,ldc指令,会先到字符串常量池中查找是否存在对应字符串实例的引用,如果有的话,那么直接返回这个字符串实例的引用,如果没有的话,会创建一个字符串实例,那么将其添加到字符串常量池中(实际上是将其引用放入到一个哈希表中),之后再返回这个字符串实例对象的引用。
    ldc:这个指令的作用是将对应的常量的引用压入操作数栈,在执行ldc指令时会触发对它的符号引用进行解析。

    在这里插入图片描述

    2.2.2 new的方式

    问题:以下方式会创建几个对象?怎么证明?

    String str = new String("hello");
    

    2个对象或者1个对象
    ①如果字符串常量池中已经有“hello”,则创建了一个对象。如下图。

    public class Main {
        public static void main(String[] args) {
            String s1 = "hello";
            String s2 = new String("hello");
            System.out.println(s1==s2);
        }
    }
    //输出:false,由图容易看出
    

    在这里插入图片描述

    ②如果字符串常量池中不存在“hello”,则创建了两个对象。
    一个对象是:new关键字在堆空间创建的
    另一个对象是:另外一个是在解析常量池的时候JVM自动创建的,如下图。

    在这里插入图片描述

    2.2.3 intern()的方式

    如果不是用字面量的方式定义的String对象,可以使用String提供的intern方法:intern方法会从字符串常量池中查询当前字符串是否存在,若存在则返回其引用;若不存在就会将当前字符串放入常量池中,并返回其引用。我们只需牢记返回的是字符串常量池的引用(即哈希表中的值)即可。

    public class Main {
        public static void main(String[] args) {
            String s1 = new String("1");
            String s2=s1.intern();
            String s3 = "1";
            System.out.println(s2 == s3);
        }
    }
    

    分析:true
    第一行,创建了两个对象实例,其引用一个在字符串常量池中,一个返回给s,如上图。
    第二行,intern()方法会会从字符串常量池中查询当前字符串是否存在,发现存在,返回的是字符串常量池的引用(地址)。
    第三行,s3是赋值为字符串常量池的引用。
    故 s2和s3地址一样。

    2.3字符串拼接

    String s =new String("1")+new String("1");
    

    分析:true
    执行完成后,堆区多了两个匿名对象,另外堆区还多了一个字面量为11的字符串实例,并且栈中存在一个引用指向这个实例。当我们在进行字符串拼接时,编译器默认会创建一个StringBuilder对象并调用其append方法来进行拼接,最后再调用其toString方法来转换成一个字符串,StringBuildertoString方法其实就是new一个字符串。

    //StringBuilder的toString方法
    @Override
        public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
    
    
    展开全文
  • 常量池概述1.1 常量池、运行时常量池与字符串常量池的区别2. 字符串常量池的设计初衷3. 字符串的几种创建方式及原理①:直接赋值②:new String()③:intern()方法3. 面试题:字符串比较     &...

            

    1. 常量池概述

            java代码经过编译后会生成.class字节码文件,随便打开一个target目录下的.class文件,可以看到cafe babe魔数等其他的二进制数据!
    在这里插入图片描述
    在这里插入图片描述
            可以看到.class文件中除了魔数、主次版本等,其他的都属于常量池。常量池可以看做存放java代码的,主要存放两大类常量:字面量和符号引用。

    • 字面量:字面量就是指由字母、数字等构成的字符串或者数值常量。字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面量。
    int a = 1;
    int b = 2;
    int c = "abcdefg";
    int d = "abcdefg";
    
    • 符号引用:上面的a、b就是字段名称,就是一种符号引用,符号引用可以是:
      • 类和接口的全限定名 com.xx.User
      • 字段的名称和描述符 name
      • 方法的名称和描述符 set()

    1.1 静态常量池、运行时常量池与字符串常量池的区别

            像这些静态的、未加载的.class文件的数据被称为静态常量池,但经过jvm.class文件装入内存、加载到方法区后,常量池就会变为运行时常量池!对应的符号引用在程序加载或运行时会被转变为被加载到方法区的代码的直接引用,在jvm调用这个方法时,就可以根据这个直接引用找到这个方法在方法区的位置,然后去执行。

            而字符串常量池又是运行时常量池中的一小部分,字符串常量池的位置在jdk不同版本下,有一定区别!

    • Jdk1.6及之前: 有永久代, 运行时常量池包含字符串常量池
      在这里插入图片描述
    • Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里
      在这里插入图片描述
    • Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里
      在这里插入图片描述

    验证:不同jdk版本中,往ArrayList<String>中添加字符串

    /**
     * jdk6:-Xms6M -Xmx6M -XX:PermSize=6M -XX:MaxPermSize=6M  
     * jdk8:-Xms6M -Xmx6M -XX:MetaspaceSize=6M -XX:MaxMetaspaceSize=6M
     */
    public class RuntimeConstantPoolOOM{
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<String>();
            for (int i = 0; i < 10000000; i++) {
                String str = String.valueOf(i).intern();
                list.add(str);
            }
        }
    }
    

    运行结果:可以看到jdk7jdk6抛出的异常是在不同的区域

    • jdk7及以上抛的异常:Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
    • jdk6抛的异常:Exception in thread “main” java.lang.OutOfMemoryError: PermGen space

            

    2. 字符串常量池的设计初衷

            字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。

            JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:为字符串开辟一个字符串常量池,类似于缓存区。在创建字符串常量时:

    • 首先查询字符串常量池是否存在该字符串
    • 如果存在该字符串,返回引用实例
    • 如果不存在,先实例化该字符串并放入池中(),然后再把这个字符串返回。
    • 下次再调用时,直接从字符串常量池中取值!

            字符串常量池底层是hotspot的C++实现的,底层类似一个哈希表k-v结构的, 保存的本质上是字符串对象的引用。

            

    3. 字符串的几种创建方式及原理

    字符串的创建分为以下三种:

    • 直接赋值
    • new String()
    • intern方法
              

    ①:直接赋值

    这种方式创建的字符串对象,只会在常量池中。返回的也只是字符串常量池中的对象引用!

    String s = "aaa";  // s指向常量池中的引用
    

    步骤如下:

    • 因为有aaa这个字面量,在创建对象s的时候,JVM会先去常量池中通过 equals(key) 方法,判断是否有相同的对象
    • 如果有,则直接返回该对象在常量池中的引用
    • 如果没有,则会在常量池中创建一个新对象,再返回常量池中aaa的对象引用。
              

    ②:new String()

    这种方式会保证字符串常量池和堆中都有这个对象,最后返回堆内存中的对象引用!

    String s1 = new String("aaa");  // s1指向内存中的对象引用
    

    步骤如下:

    • 同上,看到有"aaa"这个字面量,就会先去字符串常量池中检查是否存在字符串"aaa"
    • 如果不存在,先在字符串常量池里创建一个字符串对象"aaa";再去堆内存中创建一个字符串对象"aaa"
    • 如果存在,就直接去堆内存中创建一个字符串对象"aaa"
    • 无论存不存在,都只返回堆内存中的字符串对象"aaa"的引用
              

    ③:intern()方法

    String s1 = new String("aaa");   
    String s2 = s1.intern();
    
    System.out.println(s1 == s2);  //false
    

    intern方法是一个 native 的方法,当调用 intern方法时:

    • 如果字符串常量池中已经包含一个等于"aaa"的字符串(用equals(oject)方法确定),则返回字符串常量池中的字符串"aaa"
    • 如果字符串常量池中没有"aaa"这样一个字符串,则会将intern返回的引用指向当前字符串 s1,也就说会返回堆中的"aaa"

    注意:在jdk1.6版本及以前,如果字符串常量池中没有"aaa" 这样一个字符串 ,还需要将"aaa" 复制到字符串常量池里,然后返回字符串常量池中的这个新创建的字符串"aaa"

                    
            

    3. 面试题:字符串比较

            

    ①:下面的代码创建了多少个 String 对象(intern方法的理解)?

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

    答案:

    • 在 JDK 1.6 下输出是 false,创建了 6 个对象
    • 在 JDK 1.7 及以上的版本输出是 true,创建了 5 个对象

    原理如下:

    • String s1 = new String("he") + new String("llo")这段代码首先会先根据字面量在字符串常量池中创建hello这两个对象
    • 然后根据new String又会在堆中创建hello这两个对象,此时一共四个对象
    • 然后再根据+号,合成一个新的对象hello,这个对象是存在堆中的,一共五个对象

    上述过程无论是jdk哪个版本都是一致的,但在调用intern()方法时,需要区分jdk版本:
            
    在 jdk1.6 中调用 intern() 方法时:

    • 首先会在字符串常量池中寻找与hello(s1的值)equal()方法 相等的字符串
    • 假如字符串存在,就返回该字符串在字符串常量池中的引用
    • 假如字符串不存在,jvm会重新在永久代上创建一个实例,并返回这个字符串常量池中新建的实例hello
    • 这道题显然是不存在的,所以s2的引用是字符串常量池中新建的实例hello,而s1的引用则是堆中的实例hellos1 == s2又比较的是引用地址,所以在jdk1.6创建了 6 个对象,输出是 false
      在这里插入图片描述

    在 jdk1.8 中调用 intern() 方法时:

    • 在 JDK 1.7 (及以上版本)中,由于字符串常量池不在永久代了,放在了堆中,刚好字符串对象s1也是存在于堆中的,所以intern() 做了一些修改,为了更方便地利用堆中的对象,省去了字符串常量池的复制操作!可以直接指向堆上的实例hello
    • 所以在 JDK 1.7 (及以上版本)中,只需要创建5个对象,且intern()返回的对象s2也是堆中的对象hellos1s2同为堆中对象hello的引用,所以s1 == s2,返回true
      在这里插入图片描述

            
    ②:字符串比较

    示例1:

    String s0="ab";
    String s1="ab";
    String s2="a" + "b";
    System.out.println( s0==s1 ); //true
    System.out.println( s0==s2 ); //truea
    

    解析:

    • 例子中的 s0和s1中的ab都是字符串常量,它们在编译期就被确定了,所以s0 == s1为 true;
    • String s2="a" + "b"字面量a和字面量b直接相加,在编译期就被优化为一个字符串常量"ab",所以s0 、s1 、s2都可以看作是sx = "ab",返回的都是字符串常量池中的ab,无论怎么比较都是相等的!

            
    示例2:

    String s0="ab";
    String s1=new String("ab");
    String s2="a" + new String("b");
    System.out.println( s0==s1 );  // false
    System.out.println( s0==s2 );  // false
    System.out.println( s1==s2 );  // false
    

    解析:

    • s0 指向字符串常量池中的ab,s1指向堆中的ab,两者不相等
    • s2因为有后半部分 new String(”b”),所以jvm无法在编译期确定,所以也是一个新创建对象”ab”的引用,s2也相当于new String("ab"),与其他的都不想等!

            
    示例3:

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

    分析:

    • JVM对于字符串常量的"+"号连接,将在程序编译期,JVM就将常量字符串"+"连接优化为连接后的值,拿"a" + 1来说,经编译器优化后在class中就已经是a1
    • 在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为true

            
    示例4:

    String a = "ab";
    String bb = "b";
    String b = "a" + bb;
    
    System.out.println(a == b); // false
    

    分析:

    • JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false

            
    示例5:

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

    分析:

    • 和示例4中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb"a" + "b"效果是一样的。故上面程序的结果为true

            
    示例6:

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

    分析:

    • JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面程序的结果为false

            
    示例7:

    //字符串常量池:"计算机"和"技术"     
    //堆内存:str1引用的对象"计算机技术"  
    //堆内存中还有个StringBuilder的对象,但是会被gc回收
    //StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
    String str2 = new StringBuilder("计算机").append("技术").toString();   //字面量没有出现"计算机技术"字面量,所以不会在常量池里生成"计算机技术"对象
    //"计算机技术" 在池中没有,但是在堆中存在,则intern时,会直接返回该堆中的引用
    System.out.println(str2 == str2.intern());  //true
    
    
    //字符串常量池:"ja"和"va"    
    //堆内存:str1引用的对象"java"  
    //堆内存中还有个StringBuilder的对象,但是会被gc回收
    //StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
    String str1 = new StringBuilder("ja").append("va").toString();    //没有出现"java"字面量,所以不会在常量池里生成"java"对象
    //java是关键字,在JVM初始化的相关类里肯定早就放进字符串常量池了
    System.out.println(str1 == str1.intern());  //false
    
    
    
    //"test"作为字面量,放入了池中
    //而new时s1指向的是heap中新生成的string对象
    //s1.intern()指向的是"test"字面量之前在池中生成的字符串对象
    String s1=new String("test");  
    System.out.println(s1==s1.intern());   //false
    
    
    String s2=new StringBuilder("abc").toString();
    System.out.println(s2==s2.intern());  //false
    //同上
    

            

    4. 从字节码层面分析字符串相加"+"的原理

            
    先看如下代码:分析为什么s0s1不相等!

    public class Stringtest {
        public static void main(String[] args) {
    
            String  s0  =  "a" + "b" + "c";  //就等价于String s = "abc";
           
            String  a  =  "a";
            String  b  =  "b";
            String  c  =  "c";
            String  s1  =   a  +  b  +  c;
            
        }
    }
    

    运行程序后,在IDEA里会生成Stringtest.class,打开后发现:

    //编译后的 Stringtest.class 文件
    public class Stringtest {
        public Stringtest() {
        }
    
        public static void main(String[] args) {
       	  	//s0编译后已经被优化成abc
            String s0 = "abc";	
            
            String a = "a";
            String b = "b";
            String c = "c";
            //s1则不会直接被编译器优化,而是使用StringBuilder.append().toString()生成
            (new StringBuilder()).append(a).append(b).append(c).toString();
        }
    }
    
    

    可以看到:

    • 对于字符串常量的相加操作:s0 = "a" + "b" + "c"经过编译后已经被优化成s0 = "abc",这种返回的是字符串常量池中的对象引用
    • 对于字符串引用的相加操作:String s1 = a + b + c;编译器无法对其进行优化,在底层会调用StringBuilder.append().toString()生成对象引用

    那么两者不相等的原因只能出现在StringBuilder.append().toString()中!我们进一步查看StringBuilder的源码发现其toString方法居然是new String()

        @Override
        public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
    

    new String返回的是堆中的对象引用,与s0 = "abc"返回常量池的对象引用是肯定的不相等的!

    注意:Stringtest.class是字节码文件的易读版本,如果不信,可以使用javap -c Stringtest.class来直接查看字节码文件!

     public static void main(java.lang.String[]);
        Code:
           0: ldc           #2                  // String abc s0直接被优化成abc
           2: astore_1
           3: ldc           #3                  // String a
           5: astore_2
           6: ldc           #4                  // String b
           8: astore_3
           9: ldc           #5                  // String c
          11: astore        4
          13: new           #6                  // class java/lang/StringBuilder
          16: dup
          17: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
          20: aload_2
          21: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          24: aload_3
          25: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          28: aload         4
          30: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          33: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          36: astore        5
          38: return
    

            

    5. 其他的常量池

            java中基本类型的包装类的大部分都实现了常量池技术(严格来说应该叫对象池,在堆上),这些类是ByteShortIntegerLongCharacterBoolean。另外两种浮点数类型的包装类FloatDouble则没有实现。

            ByteShortIntegerLongCharacter5种整型的包装类也只是在值为【-128 ,127】之间才可使用对象池, 因为一般这种比较小的数用到的概率相对较大。

    public class IntegerTest {
        public static void main(String[] args) {
            //5种整形的包装类Byte,Short,Integer,Long,Character的对象,
            //在值小于127时可以使用对象池
            Integer i1 = 127;  //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
            Integer i2 = 127;
            System.out.println(i1 == i2);//输出true
    
            //值大于127时,不会从对象池中取对象
            Integer i3 = 128;
            Integer i4 = 128;
            System.out.println(i3 == i4);//输出false
    
            //用new关键词新生成对象不会使用对象池
            Integer i5 = new Integer(127);
            Integer i6 = new Integer(127);
            System.out.println(i5 == i6);//输出false
    
            //Boolean类也实现了对象池技术
            Boolean bool1 = true;
            Boolean bool2 = true;
            System.out.println(bool1 == bool2);//输出true
    
            //浮点类型的包装类没有实现对象池技术
            Double d1 = 1.0;
            Double d2 = 1.0;
            System.out.println(d1 == d2);//输出false
        }
    }
    
    

    上述代码,使用javap -c IntegerTest.class查看字节码文件如下:

     public static void main(java.lang.String[]);
        Code:
           0: bipush        127
           2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
           5: astore_1
           6: bipush        127
           8: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
          11: astore_2
          12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
          15: aload_1
          16: aload_2
          17: if_acmpne     24
          20: iconst_1
          21: goto          25
          24: iconst_0
          25: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
          28: sipush        128
          31: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
          34: astore_3
          35: sipush        128
          38: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
          41: astore        4
          43: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
          46: aload_3
          47: aload         4
          49: if_acmpne     56
          52: iconst_1
          53: goto          57
          56: iconst_0
          57: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
          60: new           #5                  // class java/lang/Integer
          63: dup
          64: bipush        127
          66: invokespecial #6                  // Method java/lang/Integer."<init>":(I)V
          69: astore        5
          71: new           #5                  // class java/lang/Integer
          74: dup
          75: bipush        127
          77: invokespecial #6                  // Method java/lang/Integer."<init>":(I)V
          80: astore        6
          82: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
          85: aload         5
          87: aload         6
          89: if_acmpne     96
          92: iconst_1
          93: goto          97
          96: iconst_0
          97: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
         100: iconst_1
         101: invokestatic  #7                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
         104: astore        7
         106: iconst_1
         107: invokestatic  #7                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
         110: astore        8
         112: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         115: aload         7
         117: aload         8
         119: if_acmpne     126
         122: iconst_1
         123: goto          127
         126: iconst_0
         127: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
         130: dconst_1
         131: invokestatic  #8                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
         134: astore        9
         136: dconst_1
         137: invokestatic  #8                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
         140: astore        10
         142: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         145: aload         9
         147: aload         10
         149: if_acmpne     156
         152: iconst_1
         153: goto          157
         156: iconst_0
         157: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
         160: return
    }
    
    

    可以看到,当比较【-128,127】以内的数时,会调用ByteShortIntegerLongCharacterBoolean这五种包装类的valueOf()方法,以Integer为例:

        public static Integer valueOf(int i) {
        	//如果在【low,hign】区间内,则统一使用IntegerCache缓存池中的对象
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            //如果不在【low,hign】区间内,则 new 一个对象,生成一个新的地址引用
            return new Integer(i);
        }
    

    其中【low,hign】区间如下:

          static final int low = -128;
          static final int high;
          ...
          int h = 127;
          high = h;
    
    展开全文
  • C语言中的字符串常量

    2021-05-20 02:22:07
    C语言中的字符串常量字符串常量是由一对双引号括起的字符序列。例如: "CHINA" ,"C program: , "$12.5" 等都是合法的字符串常量字符串常量和字符常量是不同的量。它们之间主要有以下区别:1.字符常量由单引号括...
  • 4)字符串常量池和运行时常量池究竟去了哪里?4)元空间是什么?5)关于为什么移除永久代?5)补充         我们知道在JDK1.8中取消了永久代,区而代之使用了元空间来实现...
  • 深入探究JVM之内存结构及字符串常量

    千次阅读 热门讨论 2020-07-20 23:36:53
    字符串常量池网上争议比较多,我个人理解它也是运行时常量池的一部分,专门用于存储字符串常量,这里先简单提一下,稍后会详细分析字符串常量池。 5. 堆 这个区域是垃圾回收的重点区域,对象都存在于堆中(但随着...
  • 【番外篇】本篇核心:JDK各个版本中JDK的运行时常量池、字符串常量池、静态常量池的功能及存储位置。 在写本系列文章时,发现一旦追究起底层实现都会涉及到一些内存结构的问题。其中涉及比较多的便是常量池,本篇...
  • 关于字符串的比较在前面文章中已经详解过,本篇文章基于字符串常量池的存储及在使用intern方法时所引起的内存变化进行一步深层次的讲解。 重点内容:当字符串调用intern方法后,再进行字符串的比较,会发生什么变化...
  • c++ 字符串常量

    千次阅读 2020-08-04 19:58:53
    字符串常量。之所以称之为常量,是因为它可看作是一个没有命名的字符串且为常量,存放在静态数据区。这里说的静态数据区,是相对于堆、栈等动态数据区而言的。 静态数据区存放的是全局变量和静态变量。从这一点上来...
  • 字符串常量与宏定义

    2021-04-20 11:43:46
    文章目录字符串常量常量和字符常量的区别:宏定义注意以下几点宏定义表示数据类型和用typedef定义数据说明符的区别:带参宏定义注意 字符串常量 字符串常量是由一对双引号括起来的字符序列 例如:“CHINA”,"$12.5...
  • 4. 类常量池、运行时常量池、字符串常量池的关系?5. 符号引用和直接引用的区别?6. 回归开头问题7. 趁热打铁解析相关面试题 1. 问题引入 问题引入:字符串常量归常量池管理,那String str = "abc"; 这段代码中的abc...
  • 字符串常量是由一对双引号括起的字符序列例如 "CHINA" "C program: $12.5" 等都是合法的字符串常量字符串常量和字符常量是不同的量它们之间主要有以下区别 1.字符常量由单引号括起来字符串常量由双引号括起来 2.字符...
  • java中的字符串和字符串常量

    千次阅读 2019-03-24 13:31:23
    String作为一种被Final修饰并按照不可变性设计的类,应当说某种程度上语言本身是希望开发者把它当做基本数据类型去使用的,然而...java本身又提供了一些构建和操作字符串的快捷或隐秘的方式,使用不当往往产生很多不...
  • C#--字符串常量

    千次阅读 2019-02-27 22:42:13
    在介绍字符串常量池之前,先看一段简单的代码: using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { string a = "123"; string b = "...
  • Java面试之字符串常量

    千次阅读 2018-09-26 22:51:06
    作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池: 字符串常量池的设计意图是什么? 字符串常量...
  • 字符型数据是计算机中用来表示英文字母...字符串常量是由一对双引号括起来的字符序列。例如,“CHINA”,“C program”,“$12.5”等都是合法的字符串常量字符串常量和字符常量是不同的量。它们之间主要有以下 ...
  • 关于常量池,字符串常量池的整理

    千次阅读 2018-07-04 14:43:54
    全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象...
  • 这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对
  • 作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池: 1、字符串常量池的设计意图是什么? 2、字符...
  • 字符串常量到底存放在哪个存储区

    千次阅读 2021-03-02 22:59:03
    **字符串常量,放在哪个存储区呢?是“自动存储区”还是“静态存储区”中? 比如: char *pstr=“hello world!”; 这里,"hello world!"是一个字符串常量, pstr是在栈中的变量。 我想问,字符串常量,在哪个内存...
  • ava中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid");,这两种方式我们在代码编写时...
  • C++中指向字符串常量的指针(const string *)不能指向字符串常量,而得用const char*来指向,但指向字符串常量的指针却能指向字符串数组的原因
  • 文章目录8_字符串常量池 StringTable8.1_String 的基本特性8.2_String 的内存分配8.3_String 的拼接操作拼接操作与 append 的效率对比8.4_intern() 的使用new String("ab")会创建几个对象?new String("a") + new ...
  • 浅谈JAVA中字符串常量的储存位置

    千次阅读 2021-02-12 18:46:26
    从该角度看的话Java内存结构包含以下部分:1、栈区:由编译器自动分配释放,具体方法执行结束后,系统自动释放JVM内存资源。其作用有保存局部变量的值,包括:1.用来保存基本数据类型的值;2.保存类的实例,即堆区...
  • Python格式化字符串常量f-string总结 本文主要总结在Python中如何使用格式化字符串常量f-string(Formatted string literals)。在 Python 程序中,大部分时间都是使用 %s 或 format 来格式化字符串,在 Python 3.6 ...
  • JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。 JDK1.8开始,取消了Java方法区,取而代之的是位于直接内存的元空间(metaSpace)。 已知:...
  • Java字符串常量

    千次阅读 多人点赞 2016-03-28 10:55:48
    开篇同学们面试的时候总会被问到字符串常量池的问题吧?如果你是死记硬背的答案,那么我想看了我这篇文章,你应该以后能胸有成竹了跟着Alan,走起!1. 享元模式其实常量池这个问题涉及到一个设计模式,叫“享元模式...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 215,758
精华内容 86,303
关键字:

以下属于字符串常量的是