Java中String详解(从原理理解经典面试题)

news2024/11/19 17:30:09

本篇文章我先通过经典面试题,筛选需要观看本篇文章的朋友,然后咱们介绍String的基本特性,通过基本特性就可以找到面试题的答案。最后咱们再深入每个面试题,通过字节码、编译原理、基本特性深入剖析所有的面试题,让大家最终能通过原理理解所有的面试题,而不是死记硬背。

1、经典面试题

如果你可以全部答对并且是从原理上答对的,那您是真的完全理解String了,如果全部答对但是都是死记硬背或者未全部答对,则通过本篇文章相信您完全可以不用死记硬背就能答对,所有题目。

import org.junit.Test;

/**
 * @author liuchao
 * @date 2023/3/2
 */
public class StringTest {

    @Test
    public void test1() {
        String str1 = "abc";
        String str2 = "abc";
        System.out.println(str1 == str2);
    }

    @Test
    public void test2() {
        String str1 = "abc";
        String str2 = "abc";
        str2 = "efg";
        System.out.println(str1 == str2);
    }


    @Test
    public void test3() {
        String str1 = "abc";
        String str2 = "abc";
        str2 += "efg";
        System.out.println(str1 == str2);
    }


    @Test
    public void test4() {
        String str1 = "abc";
        String str2 = str1.replace('a', 'e');
        System.out.println(str1 == str2);
    }


    @Test
    public void test5() {
        String str1 = "a" + "b" + "c";
        String str2 = "abc";
        System.out.println(str1 == str2);

    }


    @Test
    public void test6() {
        String str1 = "Hello";
        String str2 = "String";

        String str3 = "HelloString";
        String str4 = "Hello" + "String";
        String str5 = str1 + "String";
        String str6 = "Hello" + str2;
        String str7 = str1 + str2;

        System.out.println(str3 == str4);
        System.out.println(str3 == str5);
        System.out.println(str3 == str6);
        System.out.println(str3 == str7);
        System.out.println(str5 == str6);
        System.out.println(str5 == str7);
        System.out.println(str6 == str7);

        String str8 = str6.intern();
        System.out.println(str3 == str8);
    }

}


/**
 * @author liuchao
 * @date 2023/3/2
 */
public class StringExer {

    String str = "hello";
    char[] ch = {'a', 'b'};

    public void change(String str, char[] ch) {
        str = "string";
        ch[0] = 'e';
    }

    public static void main(String[] args) {
        StringExer exer = new StringExer();
        exer.change(exer.str, exer.ch);
        //请问 输出值 分别是什么
        System.out.println(exer.str);
        System.out.println(exer.ch);

    }

}


/**
 * @author liuchao
 * @date 2023/3/3
 */
public class Test3 {

    @Test
    public void test1() {
        String str = new String("ab");
        //问 new String("ab") 会创建几个对象呢?
    }

    @Test
    public void test2(){
        String str = new String("a") + new String("b");
        //问 new String("a") + new String("b"); 会创建几个对象呢?
    }
}

2、String基本特性

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {

①、String:字符串,使用一对""引起来

②、String类被声明为final的,不可继承

③、String实现了Serializable接口:表示字符串是支持序列化的;实现Comparable接口:表示String可以比较大小

④、String在jdk8及以前内部定义了final char[] value用于存储字符串数据。jdk9时改为byte[] 存储字符串数据。

这里说下为什么jdk9要改为byte[]数组

官网解释:https://openjdk.org/jeps/254

官网解释翻译:

  • 动机(为什么要更改)

目前String类的实现将字符存储在一个char数组中,每个字符使用两个字节(16位)。从许多不同的应用中收集到的数据表明,字符串是堆使用的主要组成部分,此外,大多数字符串对象只包含Latin-1(拼音之类的字符,一个拼音等于一个byte,用char存储就有点浪费了)字符。这些字符只需要一个字节的存储空间,因此这些字符串对象的内部字符数组中有一半的空间没有被使用。

  • 说明

我们建议将String类的内部表示方法从UTF-16字符数组改为字节数组加编码标志域。新的String类将根据字符串的内容,以ISO-8859-1/Latin-1(每个字符一个字节)或UTF-16(每个字符两个字节)的方式存储字符编码。编码标志将表明使用的是哪种编码。

⑤、String:代表不可变的字符序列。简称不可变性。

  • 当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。

  • 当对现有字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

  • 当调用String的replace()方法修改指定字符或者字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。

⑥、通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值什么在字符串常量池中。

⑦、字符串常量池是不会存储相同内容的字符串的。

String的String Pool是一个固定大小的Hashtable,默认值大小长度是1009。如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降。

使用-XX:StringTableSize可设置StringTable的长度

  • 在jdk6中StringTable是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。StringTablesize设置没有要求

  • 在jdk7中,StringTable的长度默认值是60013,StringTablesize设置没有要求

  • 在jdk8中,设置StringTable长度的话,1009是可以设置的最小值

3、String内存分配

在Java语言中有8种基本数据类型和一种比较特殊的类型String。这些类型为了使它们在运行过程中速度更快、更节省内存,都提供了一种常量池的概念。

常量池就类似一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种。

  • 直接使用双引号声明出来的String对象会直接存储在常量池中。 eg: String test = "test";

  • 如果不是用双引号声明的String对象,可以使用String提供的intern()方法。动态创建字符串放入常量池

Java 6及以前,字符串常量池存放在永久代

Java 7中 Oracle的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到Java堆内

  • 所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时仅需要调整堆大小就可以了。

  • 字符串常量池概念原本使用得比较多,但是这个改动使得我们有足够的理由让我们重新考虑在Java 7中使用String.intern()。

Java8元空间,字符串常量在堆

4、字符串拼接操作

①、常量与常量的拼接结果在常量池,原理是编译期优化

②、常量池中不会存在相同内容的变量

③、只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder

④、如果拼接的结果调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回此对象地址

5、通过字节码分析每种题目

5.1、常量池不允许相同字符串重复存在

代码:

import org.junit.Test;

/**
 * @author liuchao
 * @date 2023/3/2
 */
public class StringTempTest {

    @Test
    public void test() {
        String str1 = "abc";
        String str2 = "abc";
        System.out.println(str1 == str2);

    }
}

通过字节码指令可以看出,加载的是同一个常量在地址

注:使用插件为jclasslib,可以访问https://blog.csdn.net/u011837804/article/details/129064876 查看使用方式

5.2、常量与常量的拼接结果在常量池,原理是编译期优化佐证

代码:

import org.junit.Test;

/**
 * @author liuchao
 * @date 2023/3/2
 */
public class StringTempTest {

    @Test
    public void test() {
        String str1 = "a" + "b" + "c";
        String str2 = "abc";
        System.out.println(str1 == str2);

    }
}

javac编译后的.class文件反编译看下,证明常量与常量的拼接结果在常量池,原理是编译期优化。

5.3、只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder

代码:

import org.junit.Test;

/**
 * @author liuchao
 * @date 2023/3/2
 */
public class StringTempTest {

    @Test
    public void test() {
        String str1 = "Hello";

        String str3 = "HelloString";
        String str5 = str1 + "String";

        System.out.println(str5 == str3);
    }
}

代码和字节码对比图片

咱们对字节码逐行分析

# 对应代码11行
 0 ldc #2 <Hello>
 2 astore_1
# 对应代码12行
 3 ldc #3 <HelloString>
# 对应代码14行开始
# 咱们从字节码不难看出,这里先是new StringBuilder() 然后又调用append()方法,
# 最后调用了toString()方法
 5 astore_2
 6 new #4 <java/lang/StringBuilder>
 9 dup
10 invokespecial #5 <java/lang/StringBuilder.<init> : ()V>
13 aload_1
14 invokevirtual #6 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
17 ldc #7 <String>
19 invokevirtual #6 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
22 invokevirtual #8 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
25 astore_3
# 对应代码14行结束
26 getstatic #9 <java/lang/System.out : Ljava/io/PrintStream;>
29 aload_3
30 aload_2
31 if_acmpne 38 (+7)
34 iconst_1
35 goto 39 (+4)
38 iconst_0
39 invokevirtual #10 <java/io/PrintStream.println : (Z)V>
42 return

咱们在看看StringBuilder方法的toString()方法具体信息

咱们发现toString()方法实际就是new String()对象,咱们知道通过关键字 new创建的对象,对象实例都是存储在堆上的(非字符串常量池的一块区域),是不是就佐证了,上述代码结果为false

5.4、String intern()方法

通过翻译方法注释,佐证此方法结果是将对象值,在字符串常量池创建一份

a string that has the same contents as this string, but is
guaranteed to be from a pool of unique strings.

翻译:与此字符串具有相同内容,但保证来自唯一字符串池的字符串

5.5、会创建几个对象

通过字节码查看创建了几个对象

6、面试题答案公布

相信大家看完上述的理论+佐证,可以很简单的理解面试题,并且很快解答出来,咱们来看看所有题目的答案。

import org.junit.Test;

/**
 * @author liuchao
 * @date 2023/3/2
 */
public class StringTest {

    @Test
    public void test1() {
        String str1 = "abc";
        String str2 = "abc";
        System.out.println(str1 == str2);//true
    }

    @Test
    public void test2() {
        String str1 = "abc";
        String str2 = "abc";
        str2 = "efg";
        System.out.println(str1 == str2);//不变性,所以为 false
    }

    @Test
    public void test3() {
        String str1 = "abc";
        String str2 = "abc";
        str2 += "efg";
        System.out.println(str1 == str2);//不变性,所以  false
    }

    @Test
    public void test4() {
        String str1 = "abc";
        String str2 = str1.replace('a', 'e');
        System.out.println(str1 == str2);//不变性,所以 false
    }

    @Test
    public void test5() {
        String str1 = "a" + "b" + "c";
        String str2 = "abc";
        System.out.println(str1 == str2);//编译优化 所以 true

    }

    @Test
    public void test6() {
        String str1 = "Hello";
        String str2 = "String";

        String str3 = "HelloString";
        String str4 = "Hello" + "String";
        String str5 = str1 + "String";
        String str6 = "Hello" + str2;
        String str7 = str1 + str2;

        System.out.println(str3 == str4);//编译优化 所以 true
        System.out.println(str3 == str5);//两个变量存储的位置不一致,所以 false
        System.out.println(str3 == str6);//两个变量存储的位置不一致,所以 false
        System.out.println(str3 == str7);//两个变量存储的位置不一致,所以 false
        System.out.println(str5 == str6);//都未存储在字符串常量池中,所以地址不一致  false
        System.out.println(str5 == str7);//都未存储在字符串常量池中,所以地址不一致  false
        System.out.println(str6 == str7);//都未存储在字符串常量池中,所以地址不一致  false

        String str8 = str6.intern();
        System.out.println(str3 == str8);//intern 将str6内容重新在常量池中创建一份,但是常量池中不允许重复值,所以true
    }

}
/**
 * @author liuchao
 * @date 2023/3/2
 */
public class StringExer {

    String str = "hello";
    char[] ch = {'a', 'b'};

    public void change(String str, char[] ch) {
        str = "string";
        ch[0] = 'e';
    }

    public static void main(String[] args) {
        StringExer exer = new StringExer();
        exer.change(exer.str, exer.ch);
        System.out.println(exer.str);//hello
        System.out.println(exer.ch);//eb

    }


}
/**
 * @author liuchao
 * @date 2023/3/3
 */
public class Test3 {

    @Test
    public void test1() {
        String str = new String("ab");
        //问 new String("ab") 会创建几个对象呢?
        /**
         * 答案:2个对象
         * 对象1:new String()
         * 对象2: 常量池中的"ab"
         */
    }

    @Test
    public void test2(){
        String str = new String("a") + new String("b");
        //问 new String("a") + new String("b"); 会创建几个对象呢?
        /**
         * 答案: 6个对象
         * 对象1:new StringBuilder()
         * 对象2:new String("a")
         * 对象3:常量池中的"a"
         * 对象4:new String("b)
         * 对象5:常量池中的"b"
         * 对象6:StringBuilder.toString()底层 又 new String() 了一个对象
         * 这里注意:StringBuilder.toString() 虽然 new 了一个Stirng,
         *  但是 字符串常量池中是没有生成”ab“
         */
    }
}

最后:希望对大家的面试有帮助,如果有写错的地方,欢迎大家留言指正,谢谢。

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

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

相关文章

jsp试卷分析管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP试卷分析管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

三、JavaScript

目录 一、JavaScript和html代码的结合方式 二、javascript和java的区别 1、变量 2、运算 3、数组&#xff08;重点&#xff09; 4、函数 5、重载 6、隐形参数arguments 7、js中的自定义对象 三、js中的事件 四、DOM模型 五、正则表达式 一、JavaScript和html代码的结合方…

代码执行漏洞 | iwebsec

文章目录00-代码执行漏洞原理环境01-eval函数示例命令执行写入webshellbash反弹shell02-assert函数示例webshell03-call_user_func函数示例04-call_user_func_array函数示例总结05-create_function函数示例06-array_map函数示例总结08-preg_replace漏洞函数示例07-preg_replace…

Centos 部署Oracle 11g

Centos 部署Oracle 11g部署Oracle 11g准备工作服务器信息oracle安装包服务器准备oracle环境安装Oracle静默方式配置监听以静默方式建立新库及实例部署Oracle 11g 在SpringMVC模式下开发web项目&#xff0c;必然会使用到关系型数据库来存储数据&#xff0c;目前使用比较多的关系…

18、多维图形绘制

目录 一、三维图形绘制 &#xff08;一&#xff09;曲线图绘制plot3() &#xff08;二&#xff09;网格图绘制 mesh() &#xff08;三&#xff09;曲面图绘制 surf() &#xff08;四&#xff09;光照模型 surfl() &#xff08;五&#xff09;等值线图(等高线图)绘制 cont…

电力系统系统潮流分析【IEEE 57 节点】(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…

C语言函数:字符串函数及模拟实现strcmp()

C语言函数&#xff1a;字符串函数及模拟实现strcmp() strcmp()函数&#xff1a; 作用&#xff1a;进行字符串的比较大小。 引入&#xff1a;如下代码&#xff0c; #define _CRT_SECURE_NO_WARNINGS#include <stdio.h>int main() {char* p "wan";char* q &qu…

Spring MVC源码解析——HandlerMapping(处理器映射器)

Sping MVC 源码解析——HandlerMapping处理器映射器1. 什么是HandlerMapping2. HandlerMapping2.1 HandlerMapping初始化2.2 getHandler解析3. getHandlerInternal()子类实现3.1 AbstractUrlHandlerMapping与AbstractHandlerMethodMapping的区别3.2 AbstractUrlHandlerMapping3…

MySQL实战解析底层---全局锁和表锁:给表加个字段怎么有这么多阻碍

目录 前言 全局锁 表级锁 前言 数据库锁设计的初衷是处理并发问题作为多用户共享的资源&#xff0c;当出现并发访问的时候&#xff0c;数据库需要合理地控制资源的访问规则而锁就是用来实现这些访问规则的重要数据结构根据加锁的范围&#xff0c;MySQL 里面的锁大致可以分成…

js正则表达式以及元字符

0、常用的正则表达式规则 手机号 const reg /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/;密码 const reg /^[a-zA-Z0-9]{6,20}$/;验证码 const reg /^\d{6}$/;1、正则表达式的介绍与使用 正则表达式(Regular Expression)是用于匹配字符串中字符组合…

RTOS中信号量的实现与应用

RTOS中的信号量是一种用来协调多个任务间共享资源访问的同步机制。它可以保证多个任务之间访问共享资源的正确性和一致性&#xff0c;避免了因多任务并发访问造成的不可预期的问题。 信号量的实现 信号量的实现原理比较简单&#xff0c;主要包括两个部分&#xff1a;计数器和…

12 readdir 函数

前言 在之前 ls 命令 中我们可以看到, ls 命令的执行也是依赖于 opendir, readdir, stat, lstat 等相关操作系统提供的相关系统调用来处理业务 因此 我们这里来进一步看一下 更细节的这些 系统调用 我们这里关注的是 readdir 这个函数, 入口系统调用是 getdents 如下调试…

HDMI协议介绍(六)--EDID

目录 什么是EDID EDID结构 1)Header Information 头信息(厂商信息、EDID 版本等) (2)Basic Display Parameters and Features 基本显示参数(数字/模拟接口、屏幕尺寸、格式支持等) (3)色度信息 (4)Established Timings(VESA 定义的电脑使用 Timings) (5)Standard Timing…

并发编程——synchronized优化原理

如果有兴趣了解更多相关内容&#xff0c;欢迎来我的个人网站看看&#xff1a;耶瞳空间 一&#xff1a;基本概念 使用synchronized实现线程同步&#xff0c;即加锁&#xff0c;实现的是悲观锁。加锁可以使一段代码在同一时间只有一个线程可以访问&#xff0c;在增加安全性的同…

Python基础知识——字符串、字典

字符串 在Python中&#xff0c;字符和字符串没有区别。可能有些同学学过其他的语言&#xff0c;例如Java&#xff0c;在Java中&#xff0c;单引号’a’表示字符’a’&#xff0c;双引号"abc"表示字符串"abc"&#xff0c;但在Python当中&#xff0c;它们没…

【百日百题-C语言-1】KY15、45、59、72、101、132

本节目录1、KY15 abc2、KY45 skew数3、KY59 神奇的口袋4、KY72 Digital Roots5、KY115 后缀子串排序6、KY132 xxx定律 3n1思想7、KY168 字符串内排序1、KY15 abc #include<stdio.h> int main() {int a,b,c;for(a1;a<9;a)for(b1;b<9;b)for(c0;c<9;c){int xa*100 …

【macOS软件】iThoughtsX 9.3 思维导图软件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。应用介绍iThoughtsX可以帮助您直观组织想法、主意和信息。亮点使用大部分常用桌面应用程序格式来进行导入导出MindManageriMindmapFreemind/FreeplaneNovamindXMindMindviewConceptDrawOPML (OmniOutliner, Scrivener etc.)…

CornerNet介绍

CornerNet: Detecting Objects as Paired Keypoints ECCV 2018 Paper&#xff1a;https://arxiv.org/pdf/1808.01244v2.pdf Code&#xff1a;GitHub - princeton-vl/CornerNet 摘要&#xff1a; 提出了一种single-stage的目标检测算法CornerNet&#xff0c;它把每个目标检…

Vector - CAPL - 获取相对时间函数

在自动化开发中&#xff0c;无论是CAN通信测试&#xff0c;还是网络管理测试&#xff0c;亦或是休眠唤醒等等存在时间相关的&#xff0c;都可能会使用相关的时间函数&#xff1b;今天主要介绍的就是获取当前时间&#xff0c;我们知道vector工具的最大优势就是稳定和精确度高&am…

Windows使用QEMU搭建arm64 ubuntu 环境

1. 下载 QEMU&#xff1a; https://qemu.weilnetz.de/w64/ QEMU UEFI固件文件&#xff1a; https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/QEMU_EFI.fd arm64 Ubuntu镜像&#xff1a; http://cdimage.ubuntu.com/releases/20.04.3/rel…