精华内容
下载资源
问答
  • 前文介绍了一些基本的数据常量,这里我们主要来接触字符常量字符常量。 首先,是最小单位的常量boolean常以及最简单的字符常量。 布尔类型常量只有true和false两个值,常用的赋值方式如 bool test=false; ...

    前文介绍了一些基本的数据常量,这里我们主要来接触字符常量和字符串常量。

    首先,是最小单位的常量boolean常以及最简单的字符常量。

    布尔类型常量只有true和false两个值,常用的赋值方式如 bool test=false;

    字符常量分为可显示的字符常量和不可显示的字符常量:
     1. 可显示的字符常量写在一对单引号之间,如'a'、 '2'、 ','、 ' '等。这些字符常量是默认为char的,如果要定义wide-charater,我们要在定义的字符前加上L,如 L'a'。

    2. 不可显示的字符常量,主要是一些Escape Sequences(转义序列)。不可显示的字符常量及不会显示在程序的运行结果中,如空格、换行等。我们在编写这些操作的时候,通常用到转义序列,由一个反斜杠(\)开始,下面介绍一些在C++中常用的专业序列:

      newline  \n  alter(bell)  \a
     horizontal tab  \t  backslash  \\
     vertical tab  \v  question mark  \?
     backspace  \b  single quote  \'
     carriage return  \r  double quote  \"
     formeed  \f  


     

    当然,我们可以用这个格式来书写其他的转义序列,如 \000,这里的000代表三个八进制数字,八进制数字的值代表这个字符的十进制的值。

    下面列举的是一些用ASCⅡ字符来表示的字面常量:

      \7  (bell)  \12  (newline)  \40  (blank)
     \0  (null)  \62  ('2')  \115  ('M')


     

    同样,我们也可以利用十六进制的转义字符来表示一些字面常量,如 \xddd。

    —————————————————————————————————————————————————

     前面介绍的字面常量都是由基本的内建型数据类型构成的,下面是关于字符串字面常量的一些介绍,这要更加复杂。字符串常量是由字符常量组成的阵列,他是在双引号中的字符常量,可以有零个或者更多个字符组成,也可以由转义序列组成。并且,正如wide charater literal,字符串也可以通过加上“L”前缀来定义。

     为了与C兼容,C++的程序中会附加一个字符以配合那些C程序员输入的字符。每一个字符串的结尾都会由编译器附加一个null字符,及“\0”。

    —————————————————————————————————————————————————

    有字符串联,那么也就有字符串串联了。下面我们介绍串联字符串的表示方法。

    串联字符串常量,就是由几个相邻的仅被空格、标记符(tab)或者换行符隔开的string literals(or wide string literals)组成。这种用法便于将很长的字符串分解成不同的行以方便定义及解读。如下:

    std::cout<<"a multi-line"
    
               "string literal"
    
               "using concatenation"
    
              <<std::endl;
    

    当这段程序被执行的时候,就会显示出:
     a multi-line string literal using concatenatiion

    很简单的表达,但是,如果把一个string literal和一个wide string literal串联,该怎么办呢?

    std::cout<<"multi-line" L"literal"<< std::endl; //Concatenating plain and wide character string is undefined

    如果我们像上面这样把一个无格式的string literal和一个wide string literal串联在一起,那么结果就如注释所说,是不合法的。其实在部分编译器中,这个写法是可以的,但是很多的编译器并不能识别这种写法,因此,我们在进行程序的编写是,不要将两种不同类型的字符串串联在一起。

    ——————————

     当然,还有一种更简单(或者说没有多大用途)的方法来处理这些比较长的字符串,就是通过在字符串的行末加上“\”。这样,下一行的内容我们会认为跟这一行一在同一个字符串内的。如下:

    //A"\"before a new line ignores the line break
    
    std::cou\
    
    t<< "Hi"<<st\
    
    d::endl;
    

    上面的语句和下面语句的作用是一样的

    
    
    std::cout<< "Hi"<<std::endl;
    


    那么,我们就可以把一个很长的字符串常量通过“\”来隔开:

    std::cout<< "a multi-line \
    
    string litreral \
    
    using a backslash"<<std::endl;
    

     需要注意的是,利用反斜杠来分行的时候一定要把反斜杠写在每一行的最后(在反斜杠后面不能有注释或者空格),同样,下一行的文字不能有缩进,因为下一行的所有内容都是包括在这个字符串内的。

    这几篇讲述了基本的数据类型和一些常量的表示方法,后面,我们会接触到变量的一些表示方法。

    展开全文
  • C语言字符常量与变量

    千次阅读 2020-05-24 14:47:10
      在这一节,我们来讨论字符字符串。 1. 字符常量   如果我想在屏幕上打印"HelloWorld"。应该怎样做呢?大家应该很熟悉这个代码了。 #include <stdio.h> int main() { printf("HelloWorld\n"); ...

    字符常量与变量

      在这一节中,我们来讨论字符与字符串。

    1. 字符常量

      如果我想在屏幕上打印"HelloWorld"。应该怎样做呢?大家应该很熟悉这个代码了。

    #include <stdio.h>
    int main()
    {
    	printf("HelloWorld\n");
    	return 0;
    }
    

      我们使用printf函数,第一个参数是需要输出的字符串。字符串用双引号包括。

      不知道大家有没有考虑过,字符串是由什么组成的呢?没错,就是我们这一节讨论的字符了。

      那如果我单独用字符来打印HelloWorld,将代码改为如下这样。

    #include <stdio.h>
    int main()
    {
    	printf("H");
    	printf("e");
    	printf("l");
    	printf("l");
    	printf("o");
    	printf("W");
    	printf("o");
    	printf("r");
    	printf("l");
    	printf("d");
    	printf("\n");
    	return 0;
    }
    

      这样看上去是在单个单个的输出字符。但是,我们要注意,字符串是用双引号包括的。也就是说,这上面输出的仍然是字符串,只不过是只有一个可打印字符的字符串罢了。

      那我们怎样,用单个字符的形式来输出呢?

      定义:字符常量由单引号包括

      类似于’a’  ,  ‘b’ ,  ‘c’ ,  ‘1’ ,  ‘2’ ,  ‘\n’。这样的都是字符常量。

      'ab’这样的写法是错误的,字符常量只允许有一个字符。如果需要多个字符,请使用字符串"ab"。

      那么,’\n’不是两个字符吗?是的,\n是两个字符,但是它代表的是换行符。我们C语言中,换行符没法直接打在代码当中。难道要像下面这样打出来?

    '
    '
    

      而且,会和代码格式的换行弄混淆。所以,我们用\来表示转义,斜杠加字母的形式,表示为另一个意思。这样的字符有很多,例如,换行’\n’,退格’\b’,制表’\t’等等。

      好的,那我们知道了,单引号包括的是字符,那我们这样打印行不行?

    #include <stdio.h>
    int main()
    {
    	printf('H');
    	printf('e');
    	printf('l');
    	printf('l');
    	printf('o');
    	printf('W');
    	printf('o');
    	printf('r');
    	printf('l');
    	printf('d');
    	printf('\n');
    	return 0;
    }
    

      答案是不行的,这样会编译报错。为什么呢?printf的第一个参数必须是字符串。那如果是这样的话,我们考虑能不能用printf函数的占位符来给字符占位。然后,在printf的后续参数里面传入字符串呢。之前我们已经了解到了,整数用%d,浮点数用%f。那么字符用什么呢?

      字符请使用转换方式%c。

    #include <stdio.h>
    int main()
    {
        printf("%c%c%c%c%c%c%c%c%c%c%c", 'h', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd', '\n');
        return 0;
    }
    

      正如你看到的,%c可以用于给字符占位。

    2. 字符类型与字符变量

      有了字符常量,那我们肯定需要一种类型,来定义字符变量。

      关键词char,是字符类型。

      定义一个字符变量

    char c1 = 'a';
    char c2 = '1';
    char c3 = '\n';
    

      打印这三个变量

    #include <stdio.h>
    int main()
    {
        char c1 = 'a';
        char c2 = '\n';
        char c3 = '1';
        printf("c1=%c c2=%c c3=%c", c1, c2, c3);
        return 0;
    }
    

    printf_char

      第一个%c被替换成了a,第二个被替换成了换行,第三个被替换成了1。

      那么我们来看看,char这种类型的数据大小吧,用关键词sizeof。

    sizeof_char

      好的,我们证实了,char都是1个字节的。

      接下来,我们思考一个问题。事实上,我们在整型那一节中,已经见过char了,char也属于一种整型变量。而在printf那一节中,我们知道了对于变参函数的可变参数部分。比int小的整型类型,都会转换为int。所以,我们可以用%d来打印short类型。很显然,char是一个字节,肯定是比int小的。那么,我们也可以用%d来打印char。来看看,char里面所存储的数值是多少。

    ascii a b c d e

      观察结果,a,b,c,d,e,居然是连续的。那我们有理由推测,字符和数值,存在某种联系。

      是的,这种猜测是正确的。字符在计算机里面就是以数值形式存储的。关键是,你把它看做是字符,还是数值。

    %d %c char

      97,98,99,100,101如果你以%d打印,那么它们输出的是数值。如果你以%c打印,那么它们输出的是字符。那么,我们为什么还要char,字符类型char呢?int也可以呀。因为在早期,计算机只考虑了拉丁字符集,小写的a到z,大写的A到Z加上数字0到9,标点符号。全部加起来就127个。char的数值范围是-128到127,那么完全足够了。所以,不需要使用其它更大的类型来存储。

      那我们来看一下这0-127数值与字符的对应关系吧。这个对应关系被称之为ASCII(American Standard Code for Information Interchange),美国信息交换标准代码。

    ascii
    图片来自百度百科

      在表中,我们可以明确看到a对应97,b对应98,c对应99,d对应100,e对应101。和我们刚刚代码运行的结果一致。

    3. 字符串

      那么字符串,自然可想而知,就是由一个个字符组成的了。为了区分,字符串由双引号包括。例如:“HelloWorld”,“a”。

      我们也来测试一下字符串的占用的空间大小。

    sizeof helloworld

      HelloWorld一共10个字符,为什么sizeof测试出来的结果是11呢?

      回答这个问题之前,我们先看下面的代码。

    #include <stdio.h>
    int main()
    {
        printf("%s", "HelloWorld");
        return 0;
    }
    

      我们用%s来作为字符串的占位。

      我们思考一个问题,为什么%s能打印HelloWorld完就结束了呢?它是怎么知道,我们这次输送的一共10个字符呢?我们来看一下字符串的内存组成。

    helloworld\n

      这下是11个了吧。没错,C语言自动帮我们添加了结束的字符’\0’。我们尝试一下,将\0添加在字符串中间。会是一个什么效果。

    hello0world

      没错,%s在解析的时候发现了’\0’就认为字符串结束了,所以world就没有被打印出来了。

      那我们来看看’\0’和’\n’的数值是多少。

    -0-n

      ‘\0’对应的数值是0,’\n’,对应的数值是10。在ASCII表中,位置如下。

    ascii-0-n

      注意,千万不要将数值与字符搞混了。字符’0’的ascii码是48,而’\0’才是ascii的0。

    \0与0的区别

      '\0’被认为是结束符,而’0’被认为是普通的字符。

      '\0’的数值为0,而’0’的数值为48。

    \0与0的区别

      最后,我们知道%号和\都被用作特殊用途了。一个被用于占位符的开始,一个被用于字符转义。那如果我们确实需要打印%号和\怎么办呢?

    %%\

    4. getchar、putchar函数

      getchar函数用于从键盘读取一个字符。

      putchar函数用于输出一个字符。

      使用getchar的时候,输入完字符,请按回车键。

    getchar_putchar

    5. 中文字符串

    你好

      C语言支持中文字符串,但是你会发现ASCII中没有中文。

      早期在制定标准的时候,并且考虑到非拉丁字符。目前,计算机已经在全世界范围内使用。为了兼容早期的ASCII,高于127的数值,将会被用于其他语言字符。但是,这还是不够用于所有的非英文字符的。所以,中文字符会是由多个char组成。

      在下面的例子中我们看到"你好"的空间大小为5。除去’\n’,那么就是每个中文汉字占用2个字节。

    sizeof你好

    在这里插入图片描述

    展开全文
  • 作为最基础用的最多的引用数据类型,Java 设计者为String 提供了各种优化,其中就有为 String 提供了字符常量池以提高其性能,主要就是为了降低内存开销,那么字符常量池的具体原理是什么,我们带着以下三个问题...

    字符串常量池

    作为最基础用的最多的引用数据类型,Java 设计者为String 提供了各种优化,其中就有为 String 提供了字符串常量池以提高其性能,主要就是为了降低内存开销,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池:

    1、字符串常量池的设计意图是什么?

    2、字符串常量池在哪里?

    3、如何操作字符串常量池?

    从上一节String基础 到这里,我们一直在说字符串常量池或者串池,但是没有解释为什么叫字符串常量池,首先和所有的池子一样,例如线程池、数据库连接池都是为了性能,因为是存储字符串的所以叫字符串池,因为存储的字符串是不可变的,也就是常量,所以叫字符串常量池

    一道Java 面试题的锅

    我想所有 Java 程序员都曾被这个 new String 的问题困扰过,这是一道高频的 Java 面试题,但可惜的是网上众说纷纭,竟然找不到标准的答案。有人说创建了 1 个对象,也有人说创建了 2 个对象,还有人说可能创建了 1 个或 2 个对象,但谁都没有拿出干掉对方的证据,这就让我们这帮吃瓜群众们陷入了两难之中,不知道到底该信谁得。

    以目前的情况来看,关于 new String("xxx") 创建对象个数的答案有 3 种:

    1. 有人说创建了 1 个对象;
    2. 有人说创建了 2 个对象;
    3. 有人说创建了 1 个或 2 个对象。

    而出现多个答案的关键争议点在「字符串常量池」上,有的说 new 字符串的方式会在常量池创建一个字符串对象,有人说 new 字符串的时候并不会去字符串常量池创建对象,而是在调用 intern() 方法时,才会去字符串常量池检测并创建字符串。

    那我们就先来说说这个「字符串常量池」。

    字符串常量池初识

    前面我们说到,你要是认为字面量的这种方式就是创建String 对象的目的话,那你就错了,Java 提供的这种方式不单单是为了简化String 的创建,更主要的目的是为了和通过构造方法创建的这种方式进行区分,那区分出来的目的是什么呢?就是我们后面说的串池,因为它将这两种方式去分出来之后,让通过字面量的这种方式会走串池的这个设计,因为通过new 创建出来的String 会存储在堆里,并且有自己的空间,通过字面量的这种方式创建的字符串会被区别对待吗?会被放到公共的串池中这也就说,如果两个字面量有相同的内容(字符串),那么其实它们返回个字符串对象的是同一个地址,这就是节约内存的原理——没有创建新的,返回了已有对象的地址

    和其对象的创建和分配一样,通过new 创建出来的字符串对象是在堆上分配的,需要耗费高昂的时间和空间为代价,作为最基础的数据类型,如果大量频繁的创建字符串,会极大程度地影响程序的性能,因此 JVM 为了提高性能和减少内存开销引入了字符串常量池(Constant Pool Table)的概念,字符串常量池相当于给字符串开辟一个常量池空间类似于缓存区,对于直接赋值的字符串(String s=“xxx”)来说,在每次创建字符串时优先使用已经存在字符串常量池的字符串,如果字符串常量池没有相关的字符串,会先在字符串常量池中创建该字符串,然后将引用地址返回变量,如下图所示:

    image-20201217215632399

    实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享,运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收,这个时候你要记住这一点,因为缓存虽好,但是可能引起内存泄漏的问题,除了这个问题还有一个问题那就是,如果 Pool 中对象过多,可能导致 YGC 变长,因为 YGC 的时候,需要扫描 String Pool,更多细节请看String.intern()导致的YGC不断变长

    前面我们学习包装类的时候也说过包装类也是有缓存的,因为创建包装类的对象也是一个代价比较高昂的操作,需要注意的是基础类型包装类的缓存池使用一个数组进行缓存,而 String 类型,JVM 内部使用 HashTable 进行缓存,我们知道,HashTable 的结构是一个数组,数组中每个元素是一个链表。和我们平时使用的 HashTable 不同,JVM 内部的这个 HashTable 是不可以动态扩容的。关于HashTable 你可以看深度剖析HashTable,当然最终的形态也是和HashTable 一致的,如下所示

    image-20201219102806212

    以上说法可以通过如下代码进行证明:

    public class StringExample {
        public static void main(String[] args) {
            String s1 = "Java";
            String s2 = "Java";
            System.out.println(s1 == s2);
        }
    }
    

    以上程序的执行结果为:true,说明变量 s1 和变量 s2 指向的是同一个地址。在这里我们顺便说一下字符串常量池的再不同 JDK 版本的变化。

    常量池的内存布局

    JDK 1.7 之后把永久代换成的元空间,把字符串常量池从方法区移到了 Java 堆上。需要注意的是不论是永久代还是元空间都是对方法区的实现,在JVM 规范中并没有规定方法区的实现

    在 Java 6 中,String Pool 置于 PermGen Space 中,PermGen 有一个问题,那就是它是一个固定大小的区域,虽然我们可以通过 -XX:MaxPermSize=N 来设置永久代的空间大小,但是不管我们设置成多少,它终归是固定的。

    所以,在 Java 6 中,我们应该尽量小心使用 String.intern() 方法,否则容易导致 OutOfMemoryError。到了 Java 7,大佬们已经着手去掉 PermGen Space 了,首先,就是将 String Pool 移到了堆中。把 String Pool 放到堆中,即使堆的大小也是固定的,但是这个时候,对于应用调优工作,只需要调整堆大小就行了。

    前面我们说了 String Pool 使用一个 HashTable 来实现,这个 HashTable 不可以扩容,也就意味着极有可能出现单个 bucket 中的链表很长,导致性能降低。在 Java 6 中,这个 HashTable 固定的 bucket 数量是 1009,后来添加了选项(-XX:StringTableSize=N)可以配置这个值。到 Java 7(7u40),大佬们提高了这个默认值到 60013,Java 8 依然也是使用这个值,对于绝大部分应用来说,这个值是足够用的。当然,如果你会在代码中大量使用 String#intern(),那么有必要手动设置一下这个值。

    JDK 1.8 与 JDK 1.7 最大的区别是 JDK 1.8 将永久代取消,并设立了元空间。官方给的说明是由于永久代内存经常不够用或发生内存泄露,会爆出 java.lang.OutOfMemoryError: PermGen 的异常,所以把将永久区废弃而改用元空间了,改为了使用本地内存空间,官网解释详情:http://openjdk.java.net/jeps/122

    字符串常量池的控制

    可以通过-XX:StringTableSize参数进行控制大小,可以使用-XX:+PrintStringTableStatistics参数,让JVM退出时打印出常量池使用情况。

    这里首先我们什么都不干,就先看一下JVM 退出的时候常量池的情况

    public class StringPool {
        public static void main(String[] args) {
    
        }
    }
    
    SymbolTable statistics:
    Number of buckets       :     20011 =    160088 bytes, avg   8.000
    Number of entries       :     11852 =    284448 bytes, avg  24.000
    Number of literals      :     11852 =    459800 bytes, avg  38.795
    Total footprint         :           =    904336 bytes
    Average bucket size     :     0.592
    Variance of bucket size :     0.592
    Std. dev. of bucket size:     0.770
    Maximum bucket size     :         6
    // StringTable
    StringTable statistics:
    Number of buckets       :     60013 =    480104 bytes, avg   8.000
    Number of entries       :       849 =     20376 bytes, avg  24.000
    Number of literals      :       849 =     57464 bytes, avg  67.684
    Total footprint         :           =    557944 bytes
    Average bucket size     :     0.014
    Variance of bucket size :     0.014
    Std. dev. of bucket size:     0.119
    Maximum bucket size     :         2
    

    可以这里的字面量的个数是849,但是这个值不太固定多次运行发现结果不一样,本来我想证明的是我添加一个字符串,Number of literals 也会加1的效果,但是因为初始值每次不固定,所以我就测不了,知识有限就说到这里了,如果你们有什么好的办法请一定要告知我哦。

    下面我们测试一下,常量池大小对程序性能的影响,首先我们看一下代码

    int size=0;
    @Before
    public void setUp(){
        size = 4000000;
    }
    
    @Test
    public   void test() {
        final List<String> lst = new ArrayList<String>(size);
        long start = System.currentTimeMillis();
        for (int i = 0; i < size; ++i) {
            final String str = "Very very very very very very very very very very very very very very very long string: " + i;
            lst.add(str.intern());
    
            if (i % 200000 == 0) {
                System.out.println(i + 200000 + "; time = " + (System.currentTimeMillis() - start) / 1000.0 + " sec");
                start = System.currentTimeMillis();
            }
        }
        System.out.println("Total length = " + lst.size());
    }
    

    下面我们看一下输出结果

    默认大小60013修改后大小400031
    200000; time = 0.0 sec
    400000; time = 0.125 sec
    600000; time = 0.107 sec
    800000; time = 0.098 sec
    1000000; time = 0.103 sec
    1200000; time = 0.125 sec
    1400000; time = 0.145 sec
    1600000; time = 0.172 sec
    1800000; time = 0.344 sec
    2000000; time = 0.183 sec
    2200000; time = 0.198 sec
    2400000; time = 0.22 sec
    2600000; time = 0.238 sec
    2800000; time = 0.264 sec
    3000000; time = 0.286 sec
    3200000; time = 0.309 sec
    3400000; time = 0.346 sec
    3600000; time = 0.367 sec
    3800000; time = 0.39 sec
    4000000; time = 0.417 sec
    Total length = 4000000


    SymbolTable statistics:
    Number of buckets : 20011 = 160088 bytes, avg 8.000
    Number of entries : 20751 = 498024 bytes, avg 24.000
    Number of literals : 20751 = 779440 bytes, avg 37.562
    Total footprint : = 1437552 bytes
    Average bucket size : 1.037
    Variance of bucket size : 1.030
    Std. dev. of bucket size: 1.015
    Maximum bucket size : 7
    StringTable statistics:
    Number of buckets : 60013 = 480104 bytes, avg 8.000
    Number of entries : 4003339 = 96080136 bytes, avg 24.000
    Number of literals : 4003339 = 928145992 bytes, avg 231.843
    Total footprint : = 1024706232 bytes
    Average bucket size : 66.708
    Variance of bucket size : 51.320
    Std. dev. of bucket size: 7.164
    Maximum bucket size : 89
    200000; time = 0.0 sec
    400000; time = 0.128 sec
    600000; time = 0.097 sec
    800000; time = 0.079 sec
    1000000; time = 0.076 sec
    1200000; time = 0.082 sec
    1400000; time = 0.083 sec
    1600000; time = 0.085 sec
    1800000; time = 0.085 sec
    2000000; time = 0.235 sec
    2200000; time = 0.071 sec
    2400000; time = 0.075 sec
    2600000; time = 0.07 sec
    2800000; time = 0.07 sec
    3000000; time = 0.069 sec
    3200000; time = 0.076 sec
    3400000; time = 0.073 sec
    3600000; time = 0.076 sec
    3800000; time = 0.079 sec
    4000000; time = 0.076 sec
    Total length = 4000000


    SymbolTable statistics:
    Number of buckets : 20011 = 160088 bytes, avg 8.000
    Number of entries : 20751 = 498024 bytes, avg 24.000
    Number of literals : 20751 = 779440 bytes, avg 37.562
    Total footprint : = 1437552 bytes
    Average bucket size : 1.037
    Variance of bucket size : 1.030
    Std. dev. of bucket size: 1.015
    Maximum bucket size : 7
    StringTable statistics:
    Number of buckets : 400031 = 3200248 bytes, avg 8.000
    Number of entries : 4003341 = 96080184 bytes, avg 24.000
    Number of literals : 4003341 = 928146128 bytes, avg 231.843
    Total footprint : = 1027426560 bytes
    Average bucket size : 10.008
    Variance of bucket size : 3.495
    Std. dev. of bucket size: 1.870
    Maximum bucket size : 20

    可以看到修改后的的程序每次插入200000条需要的时间更短,性能更好,所以我们可以认为合适地修改串池大小可以提高我们程序的性能

    答案揭秘(理论认证)

    认为 new 方式创建了 1 个对象的人认为,new String 只是在堆上创建了一个对象,只有在使用 intern() 时才去常量池中查找并创建字符串。

    认为 new 方式创建了 2 个对象的人认为,new String 会在堆上创建一个对象,并且在字符串常量池中也创建一个字符串。

    认为 new 方式有可能创建 1 个或 2 个对象的人认为,new String 会先去常量池中判断有没有此字符串,如果有则只在堆上创建一个字符串并且指向常量池中的字符串,如果常量池中没有此字符串,则会创建 2 个对象,先在常量池中新建此字符串,然后把此引用返回给堆上的对象,如下图所示:
    image-20201219122603897

    正确的答案是什么呢?还记得我们前面关于使用字符串字面量的方式创建字符串对象吗?其实到这里我们不用论证都可以猜出来,Java 特意提供了使用字面量的方式来创建字符串对象,那肯定是不希望破坏new 关键字的一致性——在堆上分配,所以我们认为答案是一种,而且针对字符串对象提供了intern() 方法,可以看做是对new 这种方式的一种扩展,也就是说非常量池中的字符串对象,也可以通过intern() 方法放入常量池,既然如此new 关键字肯定不会放入常量池了,否则就不用提供intern()方法了,也不用提供使用字面量创建字符创变量的方法了。

    那么想法对吗,我们下面来从实现方面进行分析一下

    技术论证

    每个 java 文件编译为 class 文件后,都将产生当前类独有的常量池,我们称之为静态常量池。class 文件中的常量池包含两部分:字面值(literal)和符号引用(Symbolic Reference)。其中字面值可以理解为 java 中定义的字符串常量、final 常量等;符号引用指的是一些字符串,这些字符串表示当前类引用的外部类、方法、变量等的引用地址的抽象表示形式,在类被jvm装载并第一次使用这些符号引用时,这些符号引用将会解析为直接引用。符号常量包含:

    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符

    jvm在进行类装载时,将class文件中常量池部分的常量加载到方法区中,此时方法区中的保存常量的逻辑区域称之为运行时常量区。

    方法的调用、成员变量的访问最终都是通过运行时常量池来查找具体地址的

    解铃还须系铃人,回到问题的那个争议点上,new String 到底会不会在常量池中创建字符呢?我们通过反编译下面这段代码就可以得出正确的结论,代码如下:

    public class StringTableExample {
        public static void main(String[] args) {
            String s1 = new String("Hello World");
        }
    }
    

    首先我们使用 javac StringTableExample.java 编译代码,然后我们再使用 javap -verbose StringTableExampl.class 查看反编译的结果,相关信息如下:

    Classfile /datastructure/str/StringTableExample.class
      Last modified 2020-12-19; size 590 bytes
      MD5 checksum 4e93cb3151c78dc81b015f7637c6c166
      Compiled from "StringTableExample.java"
    public class datastructure.str.StringTableExample
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    // 常量池
    Constant pool:
       #1 = Methodref          #6.#22         // java/lang/Object."<init>":()V
       #2 = Class              #23            // java/lang/String
       #3 = String             #24            // Hello World
       #4 = Methodref          #2.#25         // java/lang/String."<init>":(Ljava/lang/String;)V
       #5 = Class              #26            // datastructure/str/StringTableExample
       #6 = Class              #27            // java/lang/Object
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               LocalVariableTable
      #12 = Utf8               this
      #13 = Utf8               Ldatastructure/str/StringTableExample;
      #14 = Utf8               main
      #15 = Utf8               ([Ljava/lang/String;)V
      #16 = Utf8               args
      #17 = Utf8               [Ljava/lang/String;
      #18 = Utf8               s1
      #19 = Utf8               Ljava/lang/String;
      #20 = Utf8               SourceFile
      #21 = Utf8               StringTableExample.java
      #22 = NameAndType        #7:#8          // "<init>":()V
      #23 = Utf8               java/lang/String
      #24 = Utf8               Hello World
      #25 = NameAndType        #7:#28         // "<init>":(Ljava/lang/String;)V
      #26 = Utf8               datastructure/str/StringTableExample
      #27 = Utf8               java/lang/Object
      #28 = Utf8               (Ljava/lang/String;)V
    {
      public datastructure.str.StringTableExample();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 3: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Ldatastructure/str/StringTableExample;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=2, args_size=1
             0: new           #2                  // class java/lang/String
             3: dup
             4: ldc           #3                  // String Hello World
             6: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
             9: astore_1
            10: return
          LineNumberTable:
            line 5: 0
            line 6: 10
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      11     0  args   [Ljava/lang/String;
               10       1     1    s1   Ljava/lang/String;
    }
    SourceFile: "StringTableExample.java"
    
    

    其中 Constant pool 表示字符串常量池,我们在字符串编译期的字符串常量池中找到了我们创建变量 String s1 = new String("Hello World"); 时定义的"Hello World"字符,#24 = Utf8 Hello World可以看出,也就是在编译期 new 方式创建的字符串就会被放入到编译期的字符串常量池中,也就是说 new String的方式会首先去判断字符串常量池,如果没有就会新建字符串那么就会创建 2 个对象,如果已经存在就只会在堆中创建一个对象指向字符串常量池中的字符串。

    所以看出,new 出来的字符串还是会在常量池中创建字符串,也就是说答案可能是一个也可能是两个

    那么问题来了,以下这段代码的执行结果为 true 还是 false?

    String s1 = new String("Hello World"");
    String s2 = new String("Hello World"");
    System.out.println(s1 == s2);
    

    既然 new String 会在常量池中创建字符串,那么执行的结果就应该是 true 了。其实并不是,这里对比的变量 s1 和 s2 堆上地址,因为堆上的地址是不同的,所以结果一定是 false,如下图所示:

    字符串引用.png
    从图中可以看出 s1 和 s2 的引用一定是相同的,而 s3 和 s4 的引用是不同的,对应的程序代码如下:

    public static void main(String[] args) {
        String s1 = "Java";
        String s2 = "Java";
        String s3 = new String("Java");
        String s4 = new String("Java");
        System.out.println(s1 == s2);
        System.out.println(s3 == s4);
    }
    // 输出结果
    true
    false
    

    扩展知识

    我们知道 String 是 final 修饰的,也就是说一定被赋值就不能被修改了。但编译器除了有字符串常量池的优化之外,还会对编译期可以确认的字符串进行优化,例如以下代码:

    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "ab" + "c";
        String s3 = "a" + "b" + "c";
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);
    }
    

    按照 String 不能被修改的思想来看,s2 应该会在字符串常量池创建两个字符串“ab”和“c”,s3 会创建三个字符串,他们的引用对比结果也一定是 false,但其实不是,他们的结果都是 true,这是编译器优化的功劳。

    首先我们使用 javac StringTableExample.java 编译代码,然后我们再使用 javap -c StringTableExampl.class 查看反编译的结果

    Compiled from "StringTableExample.java"
    public class datastructure.str.StringTableExample {
      public datastructure.str.StringTableExample();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: ldc           #2                  // String abc
           2: astore_1
           3: ldc           #2                  // String abc
           5: astore_2
           6: ldc           #2                  // String abc
           8: astore_3
           9: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
          12: aload_1
          13: aload_2
          14: if_acmpne     21
          17: iconst_1
          18: goto          22
          21: iconst_0
          22: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
          25: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
          28: aload_1
          29: aload_3
          30: if_acmpne     37
          33: iconst_1
          34: goto          38
          37: iconst_0
          38: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
          41: return
    }
    
    

    从 Code 3、6 可以看出字符串都被编译器优化成了字符串“abc”了,也就是提前拼接了

    总结

    本文我们通过 javap -v XXX 的方式查看编译的代码发现 new String 首次会在字符串常量池中创建此字符串,那也就是说,通过 new 创建字符串的方式可能会创建 1 个或 2 个对象,如果常量池中已经存在此字符串只会在堆上创建一个变量,并指向字符串常量池中的值,如果字符串常量池中没有相关的字符,会先创建字符串在返回此字符串的引用给堆空间的变量。我们还介绍了字符串常量池在 JDK 1.7 和 JDK 1.8 的变化以及编译器对确定字符串的优化,下面是具体的知识点

    JVM所使用的内存中,字符串作为一种特殊的基础数据类型,占据了大量的内存,且字符串有着大量的重复。由于字符串具体不可变性,因此使用String Pool对于同样的字符串做一个缓存,防止多次分配内存,从而提高内存利用率。

    String Pool在JDK当中是一个类似HashTable的结构,其特点线程安全,不可扩容,但是可以rehash

    可以通过-XX:StringTableSize参数进行控制大小如果你对程序有大量的String.intern()调用,可以使用-XX:+PrintStringTableStatistics参数,让JVM退出时打印出常量池使用情况。

    StringTableSize,在 Java 6 中,是 1009;在 Java 7 和 Java 8 中,默认都是 60013,如果有必要请自行扩大这个值(因为字符串常量池是左右缓存用的,所以叫String Pool,因为是线上使用HashTable 实现的,所以参数叫StringTableSize)

    展开全文
  • C语言的 “字符型数据

    千次阅读 2020-02-16 21:34:21
    字符型数据 字符型(Character)数据是不具计算能力的文字数据类型,用字母C表示。它包括中文字符、英文字符、数字字符和其他ASCⅡ字符,其长度(即字符个数)范围是0-255个字符,即0x00至0xFF。 字符常量 字符...

    字符型数据

    字符型(Character)数据是不具计算能力的文字数据类型,用字母C表示。它包括中文字符、英文字符、数字字符和其他ASCⅡ字符,其长度(即字符个数)范围是0-255个字符,即0x00至0xFF。


    字符常量

    字符常量是指用一对单引号括起来的一个字符。如‘a’,‘9’,‘!’。字符常量中的单引号只起定界作用并不表示字符本身。

    字符常量的分类
    • 普通的字符常量

    单引号括起来的一个字符 就是字符常量。如‘a’,‘#’,‘%’,‘D’是合法的字符常量,在内存中占一个字节。

    • 转义字符常量

    除了字符常量外,C还允许用一种特殊形式的字符常量,就是以“\”开头的字符序列。例如,’\n’代表一个“换行”符,叫做转义字符。转义字符虽然包含两个或多个字符,但它只代表一个字符。编译系统在见到字符“\”时,会接着找它后面的字符,把它处理成一个字符,在内存中只占一个字节。


    字符变量

    与字符常量相对应的便是字符变量。如:

    char c='a';
    

    但是字符串常量不可以赋给字符变量,如:

    char c="hello";
    

    这样的表达是不合法的。


    字符串常量

    定义:用双引号(“”)括起来的0个或者多个字符组成的序列
    存储:每个字符串尾自动加一个 ‘\0’ 作为字符串结束标志

    注意:"" 字符串中内容为空,也是合法的字符串常量。

    在C语言中没有专门的字符串变量,如果想将一个字符串存放在变量中以便保存,必须使用字符数组,即用一个字符型数组来存放一个字符串,数组中每一个元素存放一个字符。例如:

    char a[10]="love";

    符号常量

    C语言允许将程序中的常量定义为一个标识符,称为符号常量。符号常量在使用前必须先定义,定义的形式为:

    #define N 10

    N 就是符号常量。


    在练习编程题时,遇到了这样的一个题目,记在下面,对字符数据类型的理解能更加深刻。

    #include<stdio.h>
    #pragma warning  (disable : 4996)
    #define  N 16
    int fun (char s[])
    {
       int i,n=0;
       for(i=0;s[i]!='\0'&&i<N;++i)
       n=n*2+(s[i]-48);
       return n;
    }
    main()
    {
       char num[]="10011";
       int n;
       n=fun(num);
       printf("%s-->%d\n",num,n);
    }
    
    
    
    

    在这里插入图片描述

    这是个简单的将二进制数转换为十进制数的程序,其中 s[i]-48 的48 还可以换成‘0’ 结果都是一样的。
    若写成 0则会出现编译上的错误。

    展开全文
  • 1、局部变量存储在栈2、全局变量、静态变量(全局和局部静态变量)存储在静态存储区3、new申请的内存是在堆4、字符常量也是存储在静态存储区 补充说明:1、栈的变量内存会随着定义所在区间的...
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    文章目录Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台性?原理是什么Java语言有哪些...Java和C++的区别Oracle JDK 和 OpenJDK 的对比基础语法数据类型Java有哪些数据类型switc...
  • 字符常量字符数组

    千次阅读 2018-05-04 10:03:22
    据C语言内存模型,字符常量应该被放在代码区(也有书上称作数据区),是只读不可写的(所以说字符常量不可修改)。下面两个函数分别返回hello和world两个字符串,但却只有一个能达到预期效果: 输出结果: ...
  • C语言中字符常量存储

    千次阅读 2016-12-13 10:20:19
    局部变量、静态局部变量、全局变量、全局静态变量、字符常量以及动态申请的内存区 1、局部变量存储在栈 2、全局变量、静态变量(全局和局部静态变量)存储在静态存储区 3、new申请的内存是在堆 4、字符串...
  • 在上一节已知道:整型数据可分为int, short int,long int以及unsigned int, unsigned short, unsigned long等类别。整型常量也分为以上类别。为什么将数值常量区分为不同的类别呢?因为在进行赋值或函数的参数...
  • C语言数组——字符数组

    万次阅读 多人点赞 2020-02-17 20:59:51
    C语言目录 C/C++学习资源(百度云盘链接) 计算机二级资料(过级专用) C语言学习路线(从入门到实战) ...C语言基础-数据类型 C语言的输入输出函数 C语言流程控制语句 C语言数组——一维数组 C语言数组——二维数...
  • C++中字符型变量与整型的算术运算

    千次阅读 2019-10-14 19:24:54
    C++中字符型变量与整型的算术运算字符型变量与整型的算术运算C++算术运算的精度下面的代码展示了上面表达式类型的转换数据类型的自动转换 字符型变量与整型的算术运算 最近在学习C++,经常碰到字符与整数的加...
  • 字符常量到底存放在哪个存储区

    千次阅读 2019-09-03 22:19:38
    字符常量到底存放在哪个存储区 2013年02月23日 16:57:57 若水三千你是一千 阅读数 40499更多 分类专栏: c语言 字符常量,放在哪个存储区呢?是“自动存储区”还是“静态存储区”? 比如: char *pstr=“hello...
  • 是一个字符串字面量 该指针被放置在全局的只读存储区(const),一旦通过该指针对内存写就会出错。 在声明字符串字面量时,应该加上const. const char *p = "test";一下是转载内容:我相信,使用C...
  • c++ 字符常量

    千次阅读 2015-08-20 11:45:29
    字符常量,之所以称之为常量,因为它可一看作是一个没有命名的字符串且为常量,存放在静态数据区。 这里说的静态数据区,是相对于堆、栈等动态数据区而言的。 静态数据区存放的是全局变量和静态变量,从这一点上...
  • 字符常量到底存在哪了?

    千次阅读 多人点赞 2017-09-29 11:34:15
    局部变量、静态局部变量、全局变量、全局静态变量、字符常量以及动态申请的内存区 1、局部变量存储在栈 2、全局变量、静态变量(全局和局部静态变量)存储在静态存储区 3、new申请的内存是在堆 4、字符...
  • 关于C语言数值常量字符常量

    千次阅读 多人点赞 2019-08-12 16:22:36
    在C语言,字符常量有以下特点:  ...但数字被定义为字符型之后就不能参与数值运算。 下面四个选项,均是正确的数值常量或字符常量的选项是()。 A.0.0 0f 8.9e ‘&’ B.“a” 3.9e-2.5 lel ‘\”’ C.’3...
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据字符流按 16 位传输以字符为单位输入输出数据。 16. BIO、NIO、AIO 有什么区别? BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,...
  • javaScript学习笔记(一)js基础

    万次阅读 多人点赞 2018-09-21 10:07:18
    JavaScript是目前web开发不可缺少的脚本语言,js不需要编译即可运行,运行在客户端,需要通过浏览器来解析执行JavaScript代码。 诞生于1995年,当时的主要目的是验证表单的数据是否合法。 Java...
  • C语言

    万次阅读 多人点赞 2019-12-18 23:01:50
    (不能是整形数据,如0)(常量的类型可以从字面上区分)(1为整型常量)(1.0为实型常量)(a为字符型常量) 14.\0为八进制数,所以\09是错误的。 15.字符常量在内存占1个字节,字符常量可以进行关系运算。不能...
  •  在VS测试发现,字符常量区(只读数据区)在编译时候与静态数据区放在了一起。因此在静态数据区的几种类型没有独立的地址区域,都是按照定义的时间顺序进行分配。而动态内存区不同,堆和栈有独立的地址区域,具体...
  • 常量 常量是指在程序的整个运行过程中值保持不变的量。...常量值又称为字面常量,它是通过数据直接表示的,因此有很多种数据类型,像整型和字符等。下面一一介绍这些常量值。 1.整型常量值 Java 的整
  • 字符常量的存储区

    千次阅读 2014-05-26 23:24:23
    字符常量,放在哪个存储区呢?是“自动存储区”还是“静态存储区”? 比如: char *pstr="hello world!"; 这里,"hello world!"是一个字符常量, pstr是在栈的变量。 我想问,字符常量,在...
  • 字符常量及其存放位置

    千次阅读 2014-11-14 16:43:18
    局部变量、静态局部变量、全局变量、全局静态变量、字符常量以及动态申请的内存区 1、局部变量存储在栈 2、全局变量、静态变量(全局和局部静态变量)存储在静态存储区 3、new申请的内存是在堆 4、字符串...
  • 为什么字符型指针(char*)指向字符串不能修改其内容??? 字符串的演变: char* p="123456"; “123456”(常量字符串)----》char []------》static const char[]----->char*p 如果不懂,请往下看: 先...
  • C语言中字符常量到底存在哪了?

    千次阅读 2014-03-20 22:03:47
    局部变量、静态局部变量、全局变量、全局静态变量、字符常量以及动态申请的内存区 1、局部变量存储在栈 2、全局变量、静态变量(全局和局部静态变量)存储在静态存储区 3、new申请的内存是在堆 4、字符串...
  • 最大的区别就是C风格的...C风格字符串和char数组是不一样的,看下面两种定义: char carr1 = {'a', 'b', 'c'}; char carr2 = {'a', 'b', 'c', '\0'}; 看上面,carr2可以说成是C风格字符串,carr1就不是C风格字符
  • JAVA常量

    2021-01-07 09:32:47
    常量值又称为字面常量,它是通过数据直接表示的,因此有很多种数据类型,像整型和字符等。下面一一介绍这些常量值。 (二)分类 《1》整型常量值 Java 的整型常量值主要有如下 3 种形式。 十进制数形式【……】 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 95,889
精华内容 38,355
关键字:

下面数据中属于字符型常量