Java String的相关性质分析
引言
String可以说是在Java开发中必不可缺的一种类,String容易忽略的细节也很多,对String的了解程度也反映了一个Java程序员的基本功。下面就由一个面试题来引出对String的剖析。
1. String在源码里究竟是如何实现的,它有哪些方法,有什么作用?
从源码可以看出,String有三个私有方法,底层是由字符数组来存储字符串
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/**存储字符串的字符数组*/
private final char value[];
/** 缓存字符串的hashcode */
private int hash; // 默认是0
/** 用于验证一致性来是否进行反序列化 */
private static final long serialVersionUID = -6849794470754667710L;
1.1 String重要构造方法
// String 为参数的构造方法
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
// char[] 为参数构造方法
public String(char value[]) {
//重新复制一份char数组的值和信息,保证字符串不会被修改传回
this.value = Arrays.copyOf(value, value.length);
}
// StringBuffer 为参数的构造方法
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
// StringBuilder 为参数的构造方法
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
1.2 String重要的方法
1.2.1 equals()方法
/**比较两个字符串是否相等,返回值为布尔类型*/
public boolean equals(Object anObject) {//比较类型可以是object
/*引用对象相同时返回true*/
if (this == anObject) {
return true;
}
/*判断引用对象是否为String类型*/
if (anObject instanceof String) { //instanceof用来判断数据类型是否一致
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
//将两个比较的字符串转换成字符数组
char v1[] = value;
char v2[] = anotherString.value;
//一个一个字符进行比较
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
equals()方法首先通过instanceof判断数据类型是否一致,是则进行下一步将两个字符串转换成字符数组逐一判断。最后再返回判断结果。
1.2.2 compareTo()方法
/*比较两个字符串是否相等,返回值为int类型*/
public int compareTo(String anotherString) {//比较类型只能是String类型
int len1 = value.length;
int len2 = anotherString.value.length;
/*获得两字符串最短的字符串长度lim*/
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
/*逐一比较两字符组的字符*/
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
//若两字符不相等,返回c1-c2
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
compareTo()通过逐一判断两字符串中的字符,不相等则返回两字符差,反之循环结束最后返回0
小结
compareTo()和equals()都能比较两字符串,当equals()返回true,compareTo()返回0时,都表示两字符串完全相同。- 同时两者也有区别:
- 返回类型
compareTo()是boolean,equals()是int。 - 字符类型
compareTo()是Object,equals()只能是String类型。
- 返回类型
1.3其他方法
indexOf():查询字符串首次出现的下标位置lastIndexOf():查询字符串最后出现的下标位置contains():查询字符串中是否包含另一个字符串toLowerCase():把字符串全部转换成小写toUpperCase():把字符串全部转换成大写length():查询字符串的长度trim():去掉字符串首尾空格replace():替换字符串中的某些字符split():把字符串分割并返回字符串数组join():把字符串数组转为字符串
知道了String的实现和方法,下面就要引出常见的String面试问题
2. String常见的面试问题
2.1 为什么String类型要用final修饰?
- 从上面的代码可以看出,String类是被private final修饰的不可继承类。那么为何要用final修饰呢?
Java 语言之父 James Gosling 的回答是,他会更倾向于使用 final,因为它能够缓存结果,当你在传参时不需要考虑谁会修改它的值;如果是可变类的话,则有可能需要重新拷贝出来一个新值进行传参,这样在性能上就会有一定的损失。
James Gosling 还说迫使 String 类设计成不可变的另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题,这是迫使 String 类设计成不可变类的一个重要原因。
所以只有当字符串不可改变时,才能利用字符常量池,保证在使用字符的时候不会被修改。
那么问题来了,我们在使用final修饰一个变量时,不变的是引用地址,引用地址对应的对象是可以发生变化的。如:
import java.util.Arrays;
public class IntTest{
public static void main(String args[]){
final char[] arr = new char[]{'a', 'b', 'c', 'd'};
System.out.println("arr的地址1:" + arr);
System.out.println("arr的值2:" + Arrays.toString(arr));
//修改arr[2]的值
arr[2] = 'b';
//修改arr数组的地址,这里会发生编译错误,所以无法修改引用地址
//arr = new char[]{'1', '2', '3'};
System.out.println("arr的地址2:" + arr);
System.out.println("arr的值2:" + Arrays.toString(arr)); }
}
/*运行结果:
arr的地址1:[C@15db9742
arr的值1:[a b c d]
arr的地址2:[C@15db9742
arr的值2:[a b b d] 显然不变的是引用地址,引用地址所指对象的内容可以被修改
*/
而在上述源码中,String类下有一个私有的char数组成员
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/**存储字符串的字符数组*/
private final char value[];
那么是否可以通过修改char数组所指对象的内容,来改变string的值呢?来试一试:
import java.util.Arrays;
public class IntTest{
public static void main(String args[]){
char[] arr = new char[]{'a','b','c','d'};
String str = new String(arr);
System.out.println("arr的地址1:" + arr);
System.out.println("str= " + str);
System.out.println("arr[]= "+Arrays.toString(arr));
//修改arr[2]的值
arr[2]='b';
System.out.println("arr的地址2:" + arr);
System.out.println("str= "+str);
System.out.println("arr[]= "+Arrays.toString(arr)); }
}
/*运行结果:
arr的地址1:[C@15db9742
str= abcd
arr[]= [a, b, c, d]
arr的地址2:[C@15db9742
str= abcd
arr[]= [a, b, b, d]
*/
显然无法修改字符串,这是为何,我们再看看构造方法
// String 为参数的构造方法
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
// char[] 为参数构造方法
public String(char value[]) {
//重新复制一份char数组的值和信息,保证字符串不会被修改传回
this.value = Arrays.copyOf(value, value.length);
}
发现string的构造方法里将原来的char数组的值和信息copy了一份,保证字符串不会被修改传回。
2.2 equals()和 == 的区别
2.2.1 先说结论:
==在基本类型中比较其对应的值,在引用类型中比较其地址值
equals()在未被重写时和 == 完全一致,被重写后是比较字符串的值
public class StringTest {
public static void main(String args[]) {
String str1 = "Java"; //放在常量池中
String str2 = new String("Java"); //在堆中创建对象str2的引用
String str3 = str2; //指向堆中的str2的对象的引用
String str4 = "Java"; //从常量池中查找
String str5 = new String("Java");
System.out.println(str1 == str2); //false
System.out.println(str1 == str3); //false
System.out.println(str1 == str4); //true
System.out.println(str2 == str3); //true
System.out.println(str2 == str5); //false
System.out.println(str1.equals(str2)); //true
System.out.println(str1.equals(str3)); //true
System.out.println(str1.equals(str4)); //true
System.out.println(str2.equals(str3)); //true
}
}
实际上
equals()方法也是继承Object的equals()方法。public boolean equals(Object obj) {
return (this == obj);
}
从上面的
equals()方法的源码可以看出,String在继承方法后对应修改了方法中的相关内容,所以上述代码的equals()方法输出都是true。 类似于
String str1 = "Java";的和String str2 = new String("Java");形式有很大的区别,String str1 = "Java";形式首先在编译过程中Java虚拟机就会去常量池中查找是否存在“Java”,如果存在,就会在栈内存中开辟一块地方用于存储其常量池中的地址。所以这种形式有可能创建了一个对象(常量池中),也可能一个对象也没创建,即str1是直接在常量池中创建“Java”字符串,str4是先在常量池中查找有“Java”,所以直接地址直接指向常量池中已经存在的”Java“字符串。 而
String str2 = new String("Java");的形式在编译过程中,先去常量池中查找是否有“Java”,没有则在常量池中新建"Java"。到了运行期,不管常量池中是否有“Java”,一律重新在堆中创建一个新的对象,然如果常量池中存在“Java”,复制一份放在堆中新开辟的空间中。如果不存在则会在常量池中创建一个“Java”后再复制到堆中。所以这种形式至少创建了一个对象,最多两个对象。因此str1和str2的引用地址必然不相同。
2.3 string中的intern()方法
调用intern方法时,如果常量池中存在该字符串,则返回池中的字符串。否则将此字符串对象添加到常量池中,并返回该字符串的引用。
String s1 = new String("Java");
String s2 = s1.intern();//直接指向常量池中的字符串
String s3 = "Java";
System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true

2.4 String和StringBuilder、StringBuffer的区别
关于这三者的区别,主要借鉴这篇博文String,StringBuffer与StringBuilder的区别??首先,String是字符串常量,后两者是字符串变量。其中StringBuffer是线程安全的,下面说说他们的具体区别。
String适用于字符串不可变的情况,因为在经常改变字符串的情形下,每次改变都会在堆内存中新建对象,会造成 JVM GC的工作负担,因此在这种情形下,需要使用字符串变量。
再说StringBuffer,它是线程安全的可变字符序列,它提供了append和insert方法用于字符串拼接,并用synchronized来保证线程安全。并且可以对这些方法进行同步,像以串行顺序发生,而且该顺序与所涉及的每个线程进行的方法调用顺序一致。
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
最后是StringBuilder,因为StringBuffer要保证线程安全,所以性能不是很高,于是在JDK1.5后引入了StringBuilder,在没有了synchronize后性能得到提高,而且两者的方法基本相同。所以在非并发操作下,如单线程情况可以使用StringBuilder来对字符串进行修改。
2.5 String中的“ + ”操作符
其实在2.4中提到,String是字符串常量,具有不可变性。所以在拼接字符串、修改字符串时,尽量选择StringBuilder和StringBuffer。下面再谈一谈String中出现“+”操作符的情况:
String s1 = "Ja";
String s2 = "va";
String s3 = "Java";
String s4 = "Ja" + "va"; //在编译时期就在常量池中创建
String s5 = s1 + s2; //实际上s5是stringBuider,这个过程是stringBuilder的append
System.out.println("s3 == s4 " + (s3 == s4));
System.out.println("s3 == s5 " + (s3 == s5));
/**
运行结果:
s3 == s4 true
s3 == s5 false
*/
为什么s4==s3结果是true? 反编译看看:
1 String s = "Ja";//s1
2 String s1 = "va";//s2
3 String s2 = "Java";//s3
4 String s3 = "Java";//s4
5 String s4 = (new StringBuilder()).append(s).append(s1).toString();//s5
6 System.out.println((new StringBuilder()).append("s3 == s4").append(s2 == s3).toString());
7 System.out.println((new StringBuilder()).append("s3 == s5").append(s2 == s4).toString());
从第5行代码中看出s4在编译时期就已经将“Ja”+“va”编译“Java” ,这就是JVM的优化
第6行的代码说明在s5 = s1 +s2;执行过程,s5变成StringBuilder,并利用append方法将s1和s2拼接。
因此在String类型中使用“+”操作符,编译器一般会将其转换成new StringBuilder().append()来处理。
Java String的相关性质分析的更多相关文章
- 面试之Java String 编码相关
实话说,作为一个多年Java老年程序员,直到近来,在没有决心花时间搞清楚Java String的编码相关问题之前, 自己也都还是似懂非懂,一脸懵逼的.设想如果在面试中,有同学能够条理清晰的回答下面的问 ...
- java基础知识回顾之---java String final类 容易混淆的java String常量池内存分析
/** * 栈(Stack) :存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放 在常量池中). 堆(heap):存 ...
- Java String类相关知识梳理(含字符串常量池(String Pool)知识)
目录 1. String类是什么 1.1 定义 1.2 类结构 1.3 所在的包 2. String类的底层数据结构 3. 关于 intern() 方法(重点) 3.1 作用 3.2 字符串常量池(S ...
- Java String对象面试题分析
- java String.split()函数的用法分析
java String.split()函数的用法分析 栏目:Java基础 作者:admin 日期:2015-04-06 评论:0 点击: 3,195 次 在java.lang包中有String.spl ...
- 手写代码 - java.lang.String/StringBuilder 相关
语言:Java 9-截取某个区间的string /** * Returns a string that is a substring of this string. The * substring b ...
- 从Java String实例来理解ANSI、Unicode、BMP、UTF等编码概念
转(http://www.codeceo.com/article/java-string-ansi-unicode-bmp-utf.html#0-tsina-1-10971-397232819ff9a ...
- Java总结篇系列:Java String
String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处. 1 ...
- Java语言基础相关问题
*动手动脑: 问题1: 仔细阅读示例: EnumTest.java,运行它,分析运行结果? 源代码: public class EnumTest { public static void main ...
随机推荐
- DDD之3实体和值对象
图中是一个别墅的模型,代表实体,可以真实的看得到.那么在DDD设计方法论中,实体和值对象是什么呢? 背景 实体和值对象是领域模型中的领域对象,是组成领域模型的基础单元,一起实现实体最基本的核心领域逻辑 ...
- (Java实现) 洛谷 P1605 迷宫
题目背景 迷宫 [问题描述] 给定一个N*M方格的迷宫,迷宫里有T处障碍,障碍处不可通过.给定起点坐标和 终点坐标,问: 每个方格最多经过1次,有多少种从起点坐标到终点坐标的方案.在迷宫 中移动有上下 ...
- Java实现 LeetCode 762 二进制表示中质数个计算置位(位运算+JDK的方法)
762. 二进制表示中质数个计算置位 给定两个整数 L 和 R ,找到闭区间 [L, R] 范围内,计算置位位数为质数的整数个数. (注意,计算置位代表二进制表示中1的个数.例如 21 的二进制表示 ...
- (Java实现)洛谷 P1164 小A点菜
题目背景 uim神犇拿到了uoi的ra(镭牌)后,立刻拉着基友小A到了一家--餐馆,很低端的那种. uim指着墙上的价目表(太低级了没有菜单),说:"随便点". 题目描述 不过ui ...
- Java实现 洛谷 P3916 图的遍历(反向DFS+记忆化搜索)
P3916 图的遍历 输入输出样例 输入 4 3 1 2 2 4 4 3 输出 4 4 3 4 import java.io.BufferedReader; import java.io.IOExce ...
- Java实现 LeetCode 645 错误的集合(暴力)
645. 错误的集合 集合 S 包含从1到 n 的整数.不幸的是,因为数据错误,导致集合里面某一个元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复. 给定一个数组 n ...
- Java中IO软件包的详细介绍
一.Java Io流 Java Io流的概念 java的io是实现输入和输出的基础,可以方便的实现数据的输入和输出操作.在java中把不同的输入/输出源(键盘,文件,网络连接等)抽象表述为" ...
- java实现 蓝桥杯 算法提高 Problem S4: Interesting Numbers 加强版
1 问题描述 Problem Description We call a number interesting, if and only if: 1. Its digits consists of o ...
- SpringCloud+Ehcache
1.pom文件引入 <!-- https://mvnrepository.com/artifact/org.ehcache/ehcache --><dependency>< ...
- win10系统无法删除文件的解决方法
方法/步骤 1:首先进入不能删除的文件所在的文件夹 2:右键单击此文件夹,选择授予访问权限 3:在授权界面选择删除权限 4:在删除权限中点击更改共享权限 5:我们选择administrator级别,点 ...