金九银十,收下这份 Java String 面试题

2024-01-08 17:59

本文主要是介绍金九银十,收下这份 Java String 面试题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

请点赞关注,你的支持对我意义重大。

前言

过去两年,我们在掘金平台上发布 JetPack 专栏文章,小彭也受到了大家的意见和鼓励。最近,小彭会陆续搬运到公众号上。

在每种编程语言里,字符串都是一个躲不开的话题,也是面试常常出现的问题。在这篇文章里,我将总结 Java 字符串中重要的知识点 & 面试题 ,如果能帮上忙,请务必点赞加关注,这真的对我非常重要。

学习路线图:

1. C 和 Java 中字符串和字符数组的对比

1.1 内存表示不同

  • 在 C 语言中,字符串和字符数组相同。字符串本质上是以 \0 为结束符的字符数组字符数组,因此字符串和字符数组在本质上相同,都是一块连续的内存空间,以需要转义 \0 为结束符。C 语言是不关心 char[] 里存储字符的编码方式的,只有通过程序的上下文确定;
  • 在 Java 中,字符串和字符数组不同。字符串是 String 对象,而字符数组是数组对象,均不需要结束符。如果是数组对象,对象内存区域中有一个字段表示数组的长度,而 String 相当于字符数组的包装类。内部包装了一个基于 UTF-16 BE 编码的字符数组(从 Java 9 开始变为字节数组)。其他字符编码输入的字节流在进入 String 时都会被转换为 UTF-16 BE 编码。

java.lang.String

public final class String {private final char value[];private int hash;...
}

1.2 char 类型的数据长度

  • 在 C 语言中,char 类型占 1 字节,分为有符号与无符号两种;
  • 在 Java 中,char 类型占 2 字节,只有无符号类型。
语言类型存储空间(字节)最小值最大值
Javachar2065535
Cchar(相当于signed char)1-128127
Csigned char1-128127
Cunsigned char10255

2. 为什么 Java 9 String 内部将 char 数组改为 byte 数组?

Java String 的内存表示本质上是基于 UTF-16 BE 编码的字符数组。UTF-16 是 2 个字节或 4 个字节的变长编码,这意味着即使是 UniCode 字符集的拉丁字母,使用 ASCII 编码只需要一个字节,但是在 String 中需要两个字节的存储空间。

为了优化存储空间,从 Java 9 开始,String 内部将 char 数组改为 byte 数组,String 会判断字符串中是否只包含拉丁字母。如果是的话则采用单字节编码(Latin-1),否则使用 UTF-16 编码。

String.java (since Java 9)

private final byte coder;
static final boolean COMPACT_STRINGS;
static {COMPACT_STRINGS = true;
}
byte coder() {return COMPACT_STRINGS ? coder : UTF16;
}
@Native static final byte LATIN1 = 0;
@Native static final byte UTF16  = 1;

不同编码实现的简单区别如下:

编码格式编码单元长度BOM字节序
UTF-8-无BOM1 ~ 4 字节大端序
UTF-81 ~ 4 字节EF BB BF大端序
UTF-16-无BOM2 / 4 字节大端序
UTF-16BE(默认)2 / 4 字节FE FF大端序
UTF-16LE2 / 4 字节FF FE小端序
UTF-32-无BOM4 字节大端序
UTF-32BE(默认)4 字节00 00 FE FF大端序
UTF-32LE4 字节FF EE 00 00小端序

关于字符编码的更多内容,见: 计算机基础:今天一次把 Unicode 和 UTF-8 说清楚

3. String & StringBuilder & StringBuffer 的区别

3.1 效率

String 是不可变的,每次操作都会创建新的变量,而另外两个是可变的,不需要创建新的变量;另外,StringBuffer 的每个操作方法都使用 synchronized 关键字保证线程安全,增加了更多加锁 & 释放锁的时间。因此,操作效率的简单排序为:StringBuilder > StringBuffer > String。

3.2线程安全

String 不可变,所以 String 和 StringBuffer 都是线程安全的,而 StringBuilder 是非线程安全的。

类型操作效率线程安全
String安全(final)
StringBuffer安全(synchronized)
StringBuilder非安全

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

4.1如何让 String 不可变?

《Effective Java》中 可变性最小化原则 ,阐述了不可变类的规则:

  • 1、不对外提供修改对象状态的任何方法;
  • 2、保证类不会被扩展(声明为 final 类或 private 构造器);
  • 3、声明所有域为 final;
  • 4、声明所有域为 private;
  • 5、确保对于任何可变性组件的互斥访问。

以上规则 String 均满足。

4.2 为什么 String 要设计为不可变?

  • 1、不可变类 String 可以避免修改后无法定位散列表键值对: 假设 String 是可变类,当我们在 HashMap 中构建起一个以 String 为 Key 的键值对时,此时对 String 进行修改,那么通过修改后的 String 是无法匹配到刚才构建过的键值对的,因为修改后的 hashCode 可能是变化的。而不可变类可以规避这个问题。
  • 2、线程安全: 不可变对象本质是线程安全,不需要同步;

提示:反射可以破坏 String 的不可变性。

5. String + 的实现原理

String + 操作符是编译器语法糖,编译后会被替换为 StringBuilder#append(...) 语句,例如:

示例程序

// 源码:String string = null;
for (String str : strings) {string += str;
}
return string;// 编译产物:String string = null;
for(String str : strings) {StringBuilder builder = new StringBuilder();builder.append(string);builder.append(str);string = builder.toString();
}// 字节码:0 aconst_null1 astore_12 aload_03 astore_24 aload_25 arraylength6 istore_37 iconst_08 istore 4
10 iload 4
12 iload_3
13 if_icmpge 48 (+35)
16 aload_2
17 iload 4
19 aaload
20 astore 5
22 new #7 <java/lang/StringBuilder>
25 dup
26 invokespecial #8 <java/lang/StringBuilder.<init>>
29 aload_1
30 invokevirtual #9 <java/lang/StringBuilder.append>
33 aload 5
35 invokevirtual #9 <java/lang/StringBuilder.append>
38 invokevirtual #10 <java/lang/StringBuilder.toString>
41 astore_1
42 iinc 4 by 1
45 goto 10 (-35)
48 aload_1
49 areturn

可以看到,如果在循环里直接使用字符串 + ,会生成非常多中间变量,性能非常差。应该在循环外新建一个 StringBuilder ,在循环内统一操作这个对象。

6. String 对象的内存分配

6.1"abc" 与 new String("abc") 的区别

  • "abc" => 虚拟机首先检查 运行时常量池 中是否存在 "abc",如果存在则直接返回,否则在字符串常量池中创建 "abc" 对象并返回。因此,多次声明使用的是同一个对象;
  • new String("abc") => 在编译过程中,Javac 会将 "abc" 加入到 Class 文件常量池 中。在类加载时期,Class 文件常量池会被加载进运行时常量池。在调用 new 字节码指令时,虚拟机会在堆中新建一个对象,并且引用常量池中的 "abc" 对象。

6.2String#intern() 的实现原理

如果字符串常量池中已经包含一个等于此 String 对象的字符串,则返回常量池中的这个字符串;否则,先将此 String 对象包含的字符串拷贝到常量池中,在常量池中的这个字符串。

从 JDK 1.7 开始, String#intern() 不再拷贝字符串到常量池中,而是在常量池中生成一个对原 String 对象的引用,并返回。

// 举例:
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);// 输出结果为:
JDK1.6以及以下:false false
JDK1.7以及以上:false true

7. 为什么 String#haseCode() 要使用 31 作为因子?

public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;
}
  • 原因 1 - 31 可以被编译器优化 $31 * i = (i << 5) - i$,位运算和减法运算的效率比乘法运算高。
  • 原因 2 - 31 是一个质数: 质数是只能被 1 和自身整除的数,使用质数作为乘法因子获得的散列值,在将来进行取模时,得到相同 index 的概率会降低,即降低了哈希冲突的概率。
  • 原因 3 - 31 是一个不大不小的质数: 质数太小容易造成散列值聚集在一个小区间,提供散列冲突概率;质数过大容易造成散列值超出 int 的取值范围(上溢),丢失部分数值信息,散列冲突概率不稳定。

 

这篇关于金九银十,收下这份 Java String 面试题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/584337

相关文章

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

破茧 JDBC:MyBatis 在 Spring Boot 中的轻量实践指南

《破茧JDBC:MyBatis在SpringBoot中的轻量实践指南》MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数... 目录一、什么是 MyBATis二、 MyBatis 入门2.1、创建项目2.2、配置数据库连接字符串2.3、入

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Apache Ignite 与 Spring Boot 集成详细指南

《ApacheIgnite与SpringBoot集成详细指南》ApacheIgnite官方指南详解如何通过SpringBootStarter扩展实现自动配置,支持厚/轻客户端模式,简化Ign... 目录 一、背景:为什么需要这个集成? 二、两种集成方式(对应两种客户端模型) 三、方式一:自动配置 Thick

Spring WebClient从入门到精通

《SpringWebClient从入门到精通》本文详解SpringWebClient非阻塞响应式特性及优势,涵盖核心API、实战应用与性能优化,对比RestTemplate,为微服务通信提供高效解决... 目录一、WebClient 概述1.1 为什么选择 WebClient?1.2 WebClient 与

Java.lang.InterruptedException被中止异常的原因及解决方案

《Java.lang.InterruptedException被中止异常的原因及解决方案》Java.lang.InterruptedException是线程被中断时抛出的异常,用于协作停止执行,常见于... 目录报错问题报错原因解决方法Java.lang.InterruptedException 是 Jav