Java基础面试题之String类,最常用的类型,不了解下吗

72 阅读4分钟

0、前言

String类在Java中是个特殊的存在,它太常用以至于Java对于String设计了一些特殊的机制,它不是个基本数据类型,但是也和其他普通的类有一些区别。今天就对String类型做一个详细的总结,同时聊一下面试中常见的问题。

1、为什么String不可变?

首先要明确,这个不可变是指:每次对字符串进行修改时都会生成新的String对象。下面段代码可以解释:

String a= "111";
System.out.println("a="+a);
a="222";
System.out.println("a="+a);
//输出
a=111
a=222

在我们看来,a的值其实是变化了的,但是并不能说明String类是可变的。其实String a= "111";这段代码是创建了一个新的对象"111",而a="222";这段代码又新建了一个对象"222",但是原来的对象还存在于内存中,a只不过不是对象的一个引用,改变a只是将它引用的对象改变了。

因此:每次对字符串进行修改时都会生成新的String对象,这可能在频繁操作大量字符串的情况下导致性能问题。

那么String类是怎么实现不可变的

这要从源码中查看原因:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
    /**
     * The value is used for character storage.
     */
    //java8之前
    private final char value[];
    //java9开始
    @Stable
    private final byte[] value;

字符串实际上就是一个 char 数组,在Java9后改为了byte数组,为了节约内存,不过思路一样。并且char 数组是被 final 修饰的,只要是对char数组的改变,方法内部都是返回一个新的 String 实例。

2、String 常量池

Java字符串常量池是Java语言为了优化String类,在JVM中开辟一块区域用来存储String常量,它用于缓存字符串常量,以减少字符串的创建和销毁,提高字符串的使用效率。

String str1 = "hello";  //在常量池中创建字符串常量
String str2 = new String("world");  //堆内存中创建对象,同时也会在常量池中创建常量
String str3 = new String("hello");  //堆内存中创建对象,常量池已有常量,不会创建
System.out.println(str1==str3);
System.out.println(str1.equals(str3));

//输出
false
true

str1str3两个对象的地址不同,两个对象的值相等,都存在常量池中,所以equals方法判断二者值相等。

String str1 = "hello";  //在常量池中创建字符串常量
String str4 = "hello";  //常量池中已有此常量,不会创建新对象
System.out.println(str1==str4);
System.out.println(str1.equals(str4));

//输出
true
true

在创建str4的时候,因为常量池中已存在"hello",所以直接返回了str1的引用,因此str1和str4的地址是相同的。

String str1 = "ab";
String str2 = "a"+"b";
String str3 = "a".concat("b");
System.out.println(str1==str2); //true
System.out.println(str1==str3); //false

concat方法是通过复制数组在通过 char 数组进行拼接生成一个新的对象,所以地址值会有变动。而+会由于Java中的常量优化机制,会判断拼接后的常量在不在常量池中,若不在才会生成新对象。

3、String、StringBuffer、StringBuilder 的区别

因为String是不可变的,所以在使用String类的时候可能会频繁创建对象,造成资源浪费。因此官方提供了StringBuffer、StringBuilder两个类,可以在原字符串上进行修改,二者的区别在于是否线程安全。

三者具体区别主要用:

  • String类是不可变的,它的值是常量,因此在多线程环境下是安全的。但是,由于String类是不可变的,因此它的性能较差,特别是在频繁地修改字符串的情况下。
  • StringBuilder类也是可变的,它的值可以被修改,但是是线程不安全的,因此只能在单线程环境下使用,但由于不需要考虑线程同步问题,因此在单线程环境下比 StringBuffer 更高效。
  • StringBuffer类是可变的,它的值可以被修改,而且是线程安全的,因此可以在多线程环境下使用。

总之:

如果需要频繁对字符串进行修改,并且在多线程环境下使用,应该使用 StringBuffer;如果只在单线程环境下使用,建议使用 StringBuilder,因为它比 StringBuffer 更高效;如果不需要对字符串进行修改,应该使用 String。

4、字符串一些常用方法

String s="abc";
System.out.println(s.length());
System.out.println(s.startsWith("a"));
System.out.println(s.substring(0, 1));
System.out.println(s.toCharArray());
System.out.println(s.charAt(1));
System.out.println(s.contains("ab"));

//输出
3
true
a
abc
b
true
  • int length() 返回此字符串的长度。
  • boolean startsWith(String prefix)检查字符串是否以指定的前缀开始。
  • String substring(int beginIndex, int endIndex)根据下标返回原字符串的一个子串。
  • char[] toCharArray()将此字符串转换为一个新的字符数组。
  • char charAt(int index)返回指定处的字符。
  • contains(CharSequence chars)判断是否包含指定的字符或字符串。
  • String[] split(String regex)根据给定正则表达式的匹配拆分此字符串。
  • isEmpty()判断字符串是否为空。

5、小结

String类可以说是Java中最常用的类了,掌握了String的各种特性,才会在学习和工作中游刃有余。以上就是今天的全部内容啦!