Java基础知识之String详解

前言:面试的时候,String类型经常会被问到。虽然是一个小的知识点,但是面试的时候也容易被搞死,现在系统的学习下String知识点,以作记录。

一、String必备知识点

1.什么是String,它是什么数据类型

String是定义在 java.lang 包下的一个类。它不是基本数据类型。属于引用数据类型。

2.String对象的声明方式

1.同使用其他类一样通过new关键字来创建。

使用这种方式时,JVM创建字符串对象但不存储于字符串池。我们可以调用intern()方法将该字符串对象存储在字符串池,如果字符串池已经有了同样值的字符串,则返回引用。

2.使用双引号直接创建。

使用这种方式时,JVM去字符串池找有没有值相等字符串,如果有,则返回找到的字符串引用。否则创建一个新的字符串对象并存储在字符串池。

String name = "abc";
String name = new String("abc");

3.String 常用方法详解

网上找的一篇文章以作推荐
其中详细说明String的intern()方法:

  • 通过new操作符创建的字符串对象不指向字符串池中的任何对象,但是可以通过使用字符串的intern()方法来指向其中的某一个。

  • 当intern()方法被调用,如果字符串池中含有一个字符串和当前调用方法的字符串eqauls相等,那么就会返回池中的字符串。如果池中没有的话,则首先将当前字符串加入到池中,然后返回引用。

// Create three strings in three different ways.
String s1 = "Hello";
String s2 = new StringBuffer("He").append("llo").toString();
String s3 = s2.intern();

// Determine which strings are equivalent using the ==
// operator
System.out.println("s1 == s2? " + (s1 == s2)); // false
System.out.println("s1 == s3? " + (s1 == s3)); // true

4.String判断两个变量是否相等

想判断String变量是否相等,先了解String 对象存储在哪里问题,首先了解一下JVM堆、栈、方法区。 由图可知:


存储的是对象,每个对象都包含一个与之对应的class JVM只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定


每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象)每个栈中的数据(原始类型和对象引用)都是私有的

方法区(其中字符串常量池存在于方法区)
方法区:用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。方法区包含所有的class和static变量。跟堆一样,被所有的线程共享

在这里插入图片描述
字符串对象创建解析:

String str1 = “abc”;
String str2 = “abc”;
String str3 = “abc”;
String str4 = new String(“abc”);
String str5 = new String(“abc”);

在这里插入图片描述

String str4 = new String(“abc”) 创建多少个对象?
解析:1.在常量池中查找是否有“abc”对象,有则返回对应的引用实例,没有则创建对应的实例对象。2.在堆中 new 一个 String(“abc”) 对象,将对象地址赋值给str4,创建一个引用
综述,常量池中没有“abc”字面量则创建两个对象,否则创建一个对象,以及创建一个引用

5.String、StringBuffer、StringBuilder的区别

  1. String是不可变类,每当我们对String进行操作的时候,总是会创建新的字符串。操作String很耗资源,所以Java提供了两个工具类来操作String
    —>StringBuffer和StringBuilder。
  2. StringBuffer和StringBuilder是可变类,StringBuffer是线程安全的,StringBuilder则不是线程安全的。所以在多线程对同一个字符串操作的时候,我们应该选择用StringBuffer。由于不需要处理多线程的情况,StringBuilder的效率比StringBuffer高。
String s1 = "abc";
StringBuffer s2 = new StringBuffer(s1);
System.out.println(s1.equals(s2));
输入false,因为s2不是String类型,String的equals方法进行了类型判断。

6.Java中String为什么是不可变的?

查看源码:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}    

通过源码得知原因:这里就点明了String是怎么保证不变的。

  • String类是个final类,这就意味着String类不可被继承,不可被继承就不能带来熊孩子,在根源上防止熊孩子带来的破坏!
  • String类的主要字段char value数组也是被final修饰的

以上两点保证了String不可变,但是还有一个原因必须说明。

  • value虽然是被final修饰了,但是这只是修饰了value,但是value是一个数组,这个数组是可变的如果这个数组改变了,那么string也就变了。数组的数据结构如下:
    在这里插入图片描述

这可以看出value这个变量只不过是在栈上的一个引用,数组实际的内容是在堆上的,使用final修饰value只能限定栈里面这个value引用地址不可以变了,不能再指向其他地址,但是不能限制堆上的内容不可以改变。


下述代码在ide中直接会提示第3行错误,编译不会通过的,原因就是这里定义的value是final类型的,一旦初始化之后就不可以再次指向别的引用;但是也可以看到value指向的char数组是可以改变内容的,所以说String的不可变并不完全是由final修饰的value数组决定。

  final char[] value = {'a','b','c'};
       //out:abc
       System.out.println(value);
       
       //error:Array initializer is not allowed here
       value = {'1','2', '3'};

       value[0] = '1';
       //out:1bc
       System.out.println(value);

那是由什么决定的呢?
当然是由这个类代码的作者决定的,通读String类的源码的话,会发现后面所有String的方法都很小心的没有去修改这个数组的内容,也没有对外暴露成员,设置为private类型,再加上String类不可以被继承,避免被其他继承类修改,这样就完美实现了String的不可变。

二、String常见面试题

1.如何判断两个String是否相等?

有两种方式判断字符串是否相等,使用" == “或者使用equals方法。当使用”=="操作符时,比较引用的内存地址,equals方法比较字符串的值。大多数情况下,我们只需要判断值是否相等,此时用equals方法比较即可。还有一个equalsIgnoreCase可以用来忽略大小写进行比较。

String s1 = "abc";
        String s2 = "abc";
        String s3= new String("abc");
        System.out.println("s1 == s2 ? "+(s1==s2)); //true
        System.out.println("s1 == s3 ? "+(s1==s3)); //false
        System.out.println("s1 equals s3 ? "+(s1.equals(s3))); //true

2.String +操作判断是否相等

 String str1 = "java";
 String str2 = "java";
 String str = "javajava";
 String str5 = str1 + str2;
 String str6 = "java" + str1;
 String str7 = "java" + "java";
 String str8 = "java" + str1;
 //print false
 System.out.println(str5 == str);
 //print false
 System.out.println(str6 == str);
 //print true
System.out.println(str7 == str);
//print false
System.out.println(str6 == str8);

当对两个String变量的引用进行操作时,无论他们是通过何种方式赋值,都会在堆内存中开辟出一个新的空间存储操作结果。所以str5 == str返回false.
当字符常量和引用类型操作时,一样会在堆内存中进行操作。所以str6 == str 返回false.
当两个字符常量进行操作时,就可以直接在字符串常量区操作。操作结果如何在常量池已经存在则直接将值赋给引用变量。所以str7 == str返回true.

使用包含变量的字符串连接符如"java" + str1创建的对象是运行期才创建的,存储在heap中;只要str1是变量,不论str1指向池中的字符串对象还是堆中的字符串对象,运行期str1+“java”操作实际上是编译器创建了StringBuilder对象进行了append操作后通过toString()返回了一个字符串对象存在heap上。

public static final String FINAL_STR1  = "maven_";
public static final String FINAL_STR2 = "jdk";
String finalStr = FINAL_STR1 + FINAL_STR2;
String finalStr3 = "maven_jdk";
//print true
System.out.println(finalStr == finalStr3);

FINAL_STR1和FINAL_STR2都是用static final修饰的,
在编译期会编译进字符串常量池,因此finalStr也是在编译阶段就编译进了字符串常量池。
所以返回true

3.为什么我们在使用HashMap的时候总是用String做key?

因为字符串是不可变的,当创建字符串时,它的它的hashcode被缓存下来,不需要再次计算。因为HashMap内部实现是通过key的hashcode来确定value的存储位置,所以相比于其他对象更快。这也是为什么我们平时都使用String作为HashMap对象。

4.写一个方法来判断一个String是否是回文?

回文就是正反都一样的词,如果需要判断是否是回文,只需要比较正反是否相等即可。String类并没有提供反转方法供我们使用,但StringBuffer和StringBuilder有reverse方法。

private static boolean isPalindrome(String str) {
        if (str == null)
            return false;
        StringBuilder strBuilder = new StringBuilder(str);
        strBuilder.reverse();
        return strBuilder.toString().equals(str);
    }

假设面试官让你不使用任何其他类来实现的话,我们只需要首尾一一对比就知道是不是回文了。

private static boolean isPalindromeString(String str) {
        if (str == null)
            return false;
        int length = str.length();
        System.out.println(length / 2);
        for (int i = 0; i < length / 2; i++) {
            if (str.charAt(i) != str.charAt(length - i - 1))
                return false;
        }
        return true;
    }

5.假设字符串类似这样的aba和aab就相等,现在随便给你二组字符串,请编程比较他们看是否相等

/**
	 * 第一种方式:
	 * 实现思路:将字符串通过getBytes方法转换为byte数组,或者通过toCharArray()转换为char数组
	 * 然后先调用Arrays的sort方法进行排序,再调用Arrays的equels方法判断是否相等;
	 * 
	 * @param str1
	 * @param str2
	 * @return
	 */
	public static boolean equels(String str1, String str2) {
		byte[] sa1 = str1.getBytes();
		byte[] sa2 = str2.getBytes();
		Arrays.sort(sa1);
		Arrays.sort(sa2);
		return Arrays.equals(sa1, sa2);
	}
	
	/**
	 * 第二种方式:
	 * 实现思路:将其中一个字符串放到一个StringBuffer中,然后遍历另一个String;
	 * 判断另一个String的每个字符在StringBuffer中是否存在,如果有的话,删除StringBuffer中对应的第一个字符;
	 * 遍历结束后,查看StringBuffer的长度是否为0;
	 * 
	 * @param str1
	 * @param str2
	 * @return
	 */
	public static boolean stringSame(String str1, String str2) {
		// 先判断长度
		if (str1.length() != str2.length()) {
			return false;
		}
		
		// 把str2放到一个StringBuffer,
		StringBuffer str2Buffer = new StringBuffer();
		str2Buffer.append(str2);

		// 循环字符串1的字符,查看字符串2是否有相同字符,有的话删除
		for (int i = 0; i < str1.length(); i++) {
			char temp = str1.charAt(i);
			int index = str2Buffer.toString().indexOf(temp);
			if (index != -1) {
				str2Buffer.deleteCharAt(index);
			} else {
				return false;
			}
		}
		
		// 删除结束,确认长度为0则相等
		if (str2Buffer.toString().length() == 0) {
			return true;
		}

		return false;
	}

	public static void main(String[] args) {
		System.out.println(equels("aa23b235", "23ab235a"));
		System.out.println(stringSame("aa23b2我35", "23ab235a我"));
	}

6.为什么重写equals方法,还必须要重写hashcode方法

equals()相等的两个对象,hashcode()一定相等。
equals()不相等的两个对象,hashcode()可能相等(冲突)。

   // object.equals 源码
   public boolean equals(Object obj) {
        return (this == obj);
    }

保证是同一个对象,如果重写了equals方法,而没有重写hashcode方法,会出现equals相等的对象,hashcode不相等的情况,重写hashcode方法就是为了避免这种情况的出现。
详解

文章参考:
https://mp.weixin.qq.com/s/m4K28KP65GsBeUUaVtRgMg
https://www.cnblogs.com/niew/p/9597379.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值