【Java基础专题】8. String 五连击按照提问逐一回答

本问题对应的项目链接:高频题回答模版训练

1、String 为什么要设计为不可变类?(指导:可以从安全性,性能等方面来考虑)

2、String a = new String(“aa”) + “bb” “这句话创建了多少个对象?为什么?(指导:没啥好说,这种笔试考的多)

3、String 对象最多可以存放多少个字符(长度)?(指导:可以从源码角度分析勒)

4、字符串常量池是放在堆中吗?

5、String中 “+” 和 StringBuffer 中的 append 会有性能上的差别吗?

请在下面逐一打卡回答

共以下 11 个回答

  • 八点上线 永久会员 2024年12月13日 上午1:59

    1.从安全性方面来看,不可变类避免了其它指针修改String,在多线程情况下依旧安全,从性能来看,不可变类的string在第一次调用hashcode的时候保存hashcode,作为hashmap的键时性能很高,另外,由于string不可变,相同的string只有一个就能节省空间,在创建string的时候就会找有没相同的指向它
    2.4个
    3.编译器对String字面量的限制是2^16-2,运行时是看char数据的下标,也就是int的最大值2^31-1
    4.1.7以前是放在方法区中,1.7以后是在堆中,方便垃圾回收
    5.有,+是将string转为stringbuffer后append转为string,比直接apped性能差

    0 赞同 0 条回复
  • Silence 永久会员 2023年10月23日 下午10:01

    1 首先来说一下string它是如何实现不可变的,它在他的这个value属性上加上了private final来修饰。并且这个类它本身也是用final来修饰的。这样就会导致这个string一旦被创建并且他的value被负了初始值。那么以后就没有办法再对他进行任何修改。也不可能说通过使用继承这个类的方式来添加一些方法对它进行修改。因为这个类它是用final修饰的。

    之所以要这样设计,我认为有三个好处。第一个好处就是他不可变了之后就可以给他加上一个缓存机制。在jvm的方法区中有一个字符串常量池。专门用于存放已经创建好的字符串。当后续再有string想创建相同的字符串时,他就会直接引用这个常量池中的字符串,而不用去创建新的字符串。可以节省内存空间。其次就是出于安全性的考虑。String通常可以用来保存密码,或者说是一些敏感的。信息把它设置成不可变类之后就不可能存在某一些误操作导致错误的修改某些敏感信息。最后一个好处就是它设置成了不可变类之后,它就是线程安全的。在多线程的条件下,访问这个string就可以不加任何的同步机制,就不会导致阻塞降低性能。

    2 感觉创建了6个对象,一个堆中的string对象,“aa”,“bb”,“aabb”,底层走的stringbuilder,最后a指向的string在堆中对象

    3 最长是2的,31次方-1。int能表示的最大值0x7ffffff。

    4 字符串常量池是放在方法区中的

    5 这个需要分情况而定。

    如果说是用的加号左右两边都是引号引起来的并且不是在循环中使用的。用加号的效率不会弱于用stream builder。因为JAVA的编译器它在底层有优化。这种加号的话,他直接会先做运算再来创建对象。

    在其他情况下,加号的效率会弱于string builder。因为加号它本身在底层也是走的builder的逻辑。会额外创建一些新的对象。

    如果说是在循环中大量的拼接字符串,那么string builder的效率远远高于用加号。因为string是不可变类,他每次都需要创建新的对象。而string builder它是可以动态伸缩长度的他只需要在扩容的时候重新创建新的value组即可,并且通常就会预留很多的空间来拼接字符串。所以效率远远高于加号。

    0 赞同 0 条回复
  • LilyBoy 永久会员 2023年9月6日 上午11:34

    打卡
    1、final、private修饰
    2、字符串常量池、堆空间
    3、
    4、不是,永久代、堆
    5、+ = for循环创建StringBuffer对象

    0 赞同 0 条回复
  • 中国用户 永久会员 2023年6月26日 下午7:40

    1.从安全方面来看,String被很多java类库当做参数,比如url、path,所以为了保证安全,固定不变;从性能方面来看,字符串常量池的需要,如果创建一个String对象时,假如常量池中已经存在,则可以直接引用。允许 String 对象缓存 HashCode,字符串不变性保证了 hash 码的唯一性;
    2:4个,“aa”是一个字符串常量,“bb”同理,new String(“aa”) 一个字符串对象,new String(“aa”) + “bb”创建一个新的字符串对象来连接。
    3.String对象存放的最大字符数由int类型的value.length字段最大值来判断,String对象内部是一个长度不可变的char数组,最大值为2的31次方减1;
    4.java8.0是放在方法区;
    5.String中+,每次连接都会创建一个新的对象,性能会下降;StringBuffer中的append只是改变状态。

    0 赞同 0 条回复
  • 。 永久会员 2023年6月19日 上午12:01

    1.String底层是一个由final修饰的char数组,new出来的String会存在字符串常量池中,相同的字符串对象会指向同一地址,节省空间。String常常用于map的key,不变可以加快获取hashCode,而不是每次重新计算。Java很多类需要使用String,可变的String会导致一些重要信息,比如url,文件地址被修改;
    2.5个对象,两个值为“aa”的对象,值为“bb”的对象,值为“aabb”的对象,还有一个StringBuilder;
    3.底层为char数组,存储于堆内存,最长可为Integer.MAX_VALUE;
    4.在方法区;
    5.有的,”+”实际上是使用StringBuilder来实现字符串拼接,StringBuffer是线程安全的,需要对资源进行保护,性能会差一些;

    0 赞同 0 条回复
  • Gary 永久会员 2023年6月2日 下午2:42

    1.String的底层实现,是一个被final修饰的char数组,意味着这个数组一旦创建之后,是不能被修改的。
    从安全的角度看,String在创建之后就不能变,多线程可以安全的共用它们,保证线程安全。
    从性能的角度看,JVM在方法区中,开辟了一块区域作为常量池,来存储String常量,不同的String对象引用相同字符串的时候,都是指向常量池的同一个内存地址,这样可以节省重复的创建字符串的工作。创建一个字符串时,JVM会先在字符串常量池查找这个字符串是否存在,如果存在,就直接指向这个字符串的内存地址,不存在的话,就在常量池中新建一个。如果String可变,就起不到缓存的作用了。
    另外,String经常作为HashMap的key,hashMap的每个对象,都有一个属于自己的hashCode,缓存起来,被hashMap用来快速地查询,String不可变可以保证每个对象的hashCode是不会变的,不用每次使用都重新计算。
    2.这句话在编译的时候,会自动转换成String a = new StringBuilder(new String(“aa”)).append(“bb”).toString()
    如果常量池中没有”aa” “bb”,那么会在常量池新建这两个字符串,在堆里面,会新建一个”aa”,以及”aabb”,如果算上StringBuilder的话,一共创建了五个对象,但是StringBuilder的生命周期很短,一旦执行完toString方法之后,就会被垃圾回收。
    3.编译时,String长度最多可以是2的16次方减2,运行时长度最多可以是2的31次方减1,意思是可以在编译时定义一些短的字符串,运行时可以进行拼接,长一点也可以。
    4.放在方法区。
    5.使用String的+,编译器会自动优化成StringBuilder的append操作,StringBuffer要保证线程安全,所以它的方法里面,都有加synchronized来加锁,多线程环境下,StringBuffer比StringBuilder慢,但是如果在单线程环境下使用字符串拼接,两者性能差别不大。

    0 赞同 0 条回复
  • upward ᯤ⁶⁰ᴳ 永久会员 2023年6月2日 上午8:58

    1 不可变意味着:底层的数组final修饰,Stirng类final修饰,多线程不可修改,生成String时就生成了hashcode并且不变,因此在作为map的键时有天然优势
    2 new String(“aa”)两个, “bb”一个,变量和常量拼接,所以是Stringbuilder 然后append两个String 然后toString一个,一共5个
    3 理论最大Integer.MAX_VALUE-1个
    4 1.6及之前在方法区,1.7及之后在堆
    5 差别很大,由于StringBuffer是可变对象,String不可变,String每次拼接都会创建Stringbuilder对象,然后append,然后toString,分配空间多(除非是字面量拼接)

    0 赞同 0 条回复
  • Everglow 永久会员 2023年5月31日 上午12:34

    1.String 为什么要设计为不可变类?

    这个问题可以从两个角度来分析
    从安全性来看,String被final修饰就意味着该类不可被继承,并且底层的char数组被private修饰且没有对外访问的接口,这样在多线程的环境下,我们就不需要担心String类型的线程安全问题,因为每次对String的修改都指的是创建一个新对象,或是由字符串常量池中返回一个对象。

    从性能的角度分析,首先java为了使字符串可以复用,在jvm中有专门的字符串常量池存放String对象,如果String提供给外界可以修改字符串的接口,那么这个字符串常量池也失去了它的意义,并且因为String类型经常作为HashMap中的key所以String中就专门缓存了用private修饰的hash值,这个hash值在第一次调用String重写的hashcode方法后就会缓存到对象中,提高了效率,而且由于String是final修饰的,我们也就不用担心由于它的哈希值改变在通过key查询value时,查询不到值。

    2.String a = new String(“aa”) + “bb” “这句话创建了多少个对象

    如果字面量“aa”和“bb”在之前都没有出现过,那么首先会在字符串常量池中创建两个String对象,之后会创建一个StringBuilder对象调用append方法拼接“aa”和“bb”,最后调用toString在堆内存中创建了一个String对象存放“aabb”,假如这两个垃圾对象StringBuilder和new String(“aa”)都被gc那么,在最后的状态在堆内存中存在3个String对象,但是在这个过程中创建了5个对象

    3.String 对象最多可以存放多少个字符(长度)?

    理论上是Integer.MAX_VALUE大小个字符,也就是21亿多,因为String底层使用char数组存放字符串,而通过String的length()方法可以发现,char[]的长度是int类型,所以理论上的最大值是21亿,但是由于很多虚拟机会在数组中加入一些元数据,所以精确来讲可以存放Integer.MAX_VALUE – 8个字符,这一点可以在ArrayList源码中定义的数组最大长度可以发现。

    4.字符串常量池是放在堆中吗?

    在java6及以前常量池放在永久代中,java7开始字符串常量池从永久代移动到堆内存中,这样设计可能字符串常量池就可以存放更多的数据,并且java8开始永久代就被废除,方法区的实现变成了元数据空间。

    5.String中 “+” 和 StringBuffer 中的 append 会有性能上的差别吗?

    会有。如果使用+就可能会创建新的String对象,而使用StringBuffer的append方法只是对原有的char[]数组修改,具体的实现逻辑就是判断char[]数组是否需要扩容,之后调用System.arraycopy方法将String中的char[]拷贝到StringBuffer中的char[]中,不需要创建一个新的对象。

    1 赞同 0 条回复
  • 小鱼 永久会员 2023年5月30日 下午8:19

    1,为了性能,在常量池有一个值,如果下次还要用就不用在new了。直接从常量池里拿就行了。然后为了安全,多个线程共享一个string,不用同步处理。
    2,一共5个?创建了4个string对象,1个stringbuilder,先调用stringbuilder构造器,第一个。然后常量池一个aa,一个bb,然后在堆空间2个new出来的值分别指向常量池的aa,bb.最后StringBuilder完成字符串变量拼接完成后返回的 toString()方法中还创建一个新的对象aabb。
    3.字符串的最大长度2^31-1。(网上查的:编译期的限制:字符串的UTF8编码值的字节数不能超过65535,字符串的长度不能超过65534;
    运行时限制:字符串的长度不能超过2^31-1,占用的内存数不能超过虚拟机能够提供的最大值。)
    4. 1.8之后放在常量池(元数据),之前在堆吧(永久代)5
    5.有的,string每次加都要创建新对象,但是stringbuilder可以在后面跟着加,所以后面的性能会更好。

    1 赞同 0 条回复
  • mpweixin用户 永久会员 2023年5月28日 下午5:48

    1、在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String池的概念,在堆中开辟一块存储空间字符串池,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。另外就是安全性,多个线程共享一个string时是线程安全的,因为不会有修改string造成的线程不安全问题。
    2、4个,首先常量池找aa,没有就创建后放入,此时常量池一个对象,new的时候产生一个对象放放在堆内存中,引用地址指向常量池中aa对象地址,bb也在常量池中有一个对象,然后就是常量池中有一个aabb的对象。(总之new一个对象就会在堆内存中单独生成一个对象)
    3、string中的字符是用char[int]来存储的,int的长度为2^31-1,这是理论长度,由于常量池限制,在常量池中string最大长度为2^16-2。堆栈中是我们的虚拟机堆内存限制
    4、不是的
    5、有的,前者的内存和CPU开销会大于后者,毕竟安全高和开销小不可兼得嘛。

    1 赞同 0 条回复
  • 秊 永久会员 2023年5月27日 下午9:43

    1、这个主要是出于线程安全和性能方面的考虑。线程安全体现在,由于 String 是不可变的,多个线程共享一个 String 时不用担心它的同步问题;性能体现在缓存哈希值和设计常量池上。String 在被创建时就缓存了自己的哈希值,使用时直接拿出来就行,不用重新计算,这使得 String 适合用来作为 Map 的 Key,可以快速获得 Key 的哈希值,提高查找和比较的效率;除此之外,基于 String 的不可变,Java 使用常量池来尽可能的共享相同的字符串,来节约 String 的存储空间。具体的,当我们使用字面值创建 String 时,会先去查它是否已经存在于常量池中,如果是则直接返回这个已存在字符串的引用,而不会创建新的对象。

    2、共创建了 4 个 String 对象,第一个是常量池中的对象 “aa”,如果常量池中有 “aa” 就直接返回,没有就创建并添加进常量池中。第二个是 new 出来的以 “aa” 为初始值创建的 String 对象,第三个 “bb” 同 “aa”,第四个是通过 “+” 拼接前两个对象,创建出的新 String 对象。

    3、String 在源码中使用 char[] 来维护字符序列的,而 char[] 的长度是 int 类型,所以理论上 String 的长度最大为 $2^{31}-1 $ ,占用空间大约为 4 GB,不过根据实际 JVM 的堆内存限制,我自己电脑上测试是 $2^{31}-3 $。

    4、不是,Java 8 以前被放在永久代中,Java 8 及以后被放在方法区的元数据 metadata 中。

    5、String 的 “+” 效率低于 StringBuffer 的 append( )。原因在于 String 是不可变类,任何对 String 的操作都会创建新的 String 对象。而 “+” 的执行过程实际上是先创建了一个 StringBuffer,然后调用 append( ),最后在 toString( )。效率上肯定是不如 StringBuffer 直接 append( ) 高的。

    4 赞同 3 条回复
    • 小鱼 永久会员 @ 秊 2023年5月30日 下午7:57

      太猛了哥们

    • 秊 永久会员 @ 秊 2023年8月14日 上午10:24

      第 3 题参考 @小鱼 补充修改为:String 的长度在编译期和运行期都有相应的限制。在编译期,如果使用 UTF-8 编码的话,JVM 常量池中限制每个字符串的长度类型为 u2,也就是 $2^{16} – 1$ 。同时 Javac 编译器也会检查每个 String 的长度,大于等于 $2^{16} – 1$ 就会报错,所以总体在编译期 String 的最大长度为 $2^{16} – 2$;在运行期,String 在源码中使用 char[] 来维护字符序列的,而 char[] 的长度是 int 类型,所以理论上 String 的长度最大为 $2^{31}-1 $ ,占用空间大约为 4 GB,不过根据实际 JVM 的堆内存限制,我自己电脑上测试是 $2^{31}-3 $。

    • 秊 永久会员 @ 秊 2023年9月19日 下午4:42

      第 4 题参考 @L 修改为:这个需要看版本,JDK 1.7 之前,常量池包括字符串常量池都在方法区中,不在堆中。JDK 1.7 及以后,常量池才被放在了堆。期间,运行时常量池一直在方法区中,不同的是在 1.8 时废除了永久代的概念,方法区的实现从永久代转变成了元空间,位于本地内存中。

# 回答此问题

后才能回答