siteground建站教程,重庆关键词自然排名,网站外包怎么做,企业网址注册由于字符串操作是计算机程序中最常见的操作之一#xff0c;在面试中也是经常出现。本文从基本用法出发逐步深入剖析String的结构和性质#xff0c;并结合面试题来帮助理解。
String基本用法
在Java中String的创建可以直接像基本类型一样定义#xff0c;也可以new一个
Str…由于字符串操作是计算机程序中最常见的操作之一在面试中也是经常出现。本文从基本用法出发逐步深入剖析String的结构和性质并结合面试题来帮助理解。
String基本用法
在Java中String的创建可以直接像基本类型一样定义也可以new一个
String s1 Hello World
String s2 new String(Hello World)
String可以通过实现合并
String s Hello
s World
String包装方法
为了方便操作String包装了一堆操作大多可以看名字直接使用
public boolean isEmpty(//判断字符串是否为空
public int length() //获取字符串长度
public String substring(int beginIndex) //取子字符串
public String substring(int beginIndex, int endIndex) //取子字符串
public int indexOf(int ch)//查找字符返回第一个找到的索引位置没找到返回-1
public int indexOf(String str)//查找子串返回第一个找到的索引位置没找到返回-1
public int lastIndexOf(int ch)//从后面查找字符
public int lastIndexOf(String str)//从后面查找子字符串
public boolean contains(CharSequence s)//判断字符串中是否包含指定的字符序列
public boolean startsWith(String prefix) //判断字符串是否以给定子字符串开头
public boolean endsWith(String suffix) //判断字符串是否以给定子字符串结尾
public boolean equals(Object anObject) //与其他字符串比较看内容是否相同
public boolean equalsIgnoreCase(String anotherString)//忽略大小写比较是否相同
public int compareTo(String anotherString) //比较字符串大小
public int compareToIgnoreCase(String str) //忽略大小写比较
public String toUpperCase()//所有字符转换为大写字符返回新字符串原字符串不变
public String toLowerCase(//所有字符转换为小写字符返回新字符串原字符串不变
public String concat(String str) //字符串连接返回当前字符串和参数字符串合并结果
public String replace(char oldChar, char newChar) //字符串替换替换单个字符
public String replace(CharSequence target, CharSequence replacement)//字符串替换替换字符序列返回新字符串原字符串不变
public String trim()//删掉开头和结尾的空格返回新字符串原字符串不变
public String[] split(String regex)//分隔字符串返回分隔后的子字符串数组
String内部结构
String类内部用一个字符数组表示字符串实例变量定义为
private final char value[];
String有两个使用字符数组的构造方法会根据参数新创建一个数组并复制内容而不会直接用参数中的字符数组。
public String(char value[])
public String(char value[], int offset, int count)
可以看出String底层是由字符数组实现并结合构造方法实现常用方法
length()方法返回的是这个数组的长度
indexOf()方法查找字符或子字符串时是在这个数组中进行查找
substring()方法是根据参数调用构造方法String(char value[], int offset, int count)新建了一 个字符串
String不可变性
与包装类类似String类也是不可变类即对象一旦创建就没有办法修改了。根据上面的源码String类也声明为了final,不能被继承内部char数组value也是final的保存字符串的数组被 final 修饰且为私有的并且String 类没有提供/暴露修改这个字符串的方法。String 类被 final 修饰导致其不能被继承进而避免了子类破坏 String 不可变。
String类中提供了很多看似修改的方法其实是通过创建新的String对象来实现的原来的String对象不会被修改。比如concat()方法的代码通过Arrays.copyOf方法创建了一块新的字符数组复制原内容然后通过new创建了一个新的String。
public String concat(String str) {int otherLen str.length();if(otherLen 0) {return this;}int len value.length;char buf[] Arrays.copyOf(value, len otherLen);str.getChars(buf, len);return new String(buf, true);
}
与包装类类似定义为不可变类程序可以更为简单、安全、容易理解。但如果频繁修改字符串而每次修改都新建一个字符串那么性能太低这时应该考虑Java中的另两个类StringBuilder和StringBuffer。
字符串常量
Java中的字符串常量是非常特殊的除了可以直接赋值给String变量外它自己就像一个String类型的对象可以直接调用String的各种方法。
System.out.println(a.length());
实际上这些常量就是String类型的对象在内存中它们被放在一个共享的地方这个地方称为字符串常量池它保存所有的常量字符串每个常量只会保存一份被所有使用者共享。当通过常量的形式使用一个字符串的时候使用的就是常量池中的那个对应的String类型的对象。
所以下面的代码会输出true因为是同一个对象。
String namel a
String name2 a
System.out.println(name1name2);
需要注意的是如果不是通过常量直接赋值而是通过new创建就不会返回true了
String namel new String(a)
String name2 new String(a)
System.out.println(namelname2);
这是因为String类中以String为参数的构造方法代码如下hash是String类中另一个实例变量表示缓存的hashCode值。
public String(String original) {this.value original.value;this.hash original.hash;
}
hash变量缓存了hashCode方法的值也就是说第一次调用hashCode方法的时候会把结果保存在hash这个变量中以后再调用就直接返回保存的值。
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;
}如果缓存的hash不为0就直接返回了否则根据字符数组中的内容计算hash,计算方法是 s[0]*31^(n-1) s[1]*31^(n-2) ... s[n-1]。使用这个式子可以让hash值与每个字符的值有关也与每个字符的位置有关位置i(i1)的因素通过31的(n-i)次方表示。使用31大致是因为两个原因一方面可以产生更分散的散列即不同字符串hash值也一般不同另一方面计算效率比较高31*h与32*h-h即(h5)-h等价可以用更高效率的移位和减法操作代替乘法操作。
从下图可以看出通过new创建name1和name2指向两个不同的String对象只是这两个对象内部的value值指向相同的char数组。所以name1!name2但是name1.equals(name2)的值是true。 String的八股考点
string为什么要设计成不可变类
在Java中将 String设计成不可变的是综合考虑到各种因素的结果。主要的原因主要有以下三点 (1)字符串常量池的需要字符串常量池是Java堆内存中一个特殊的存储区域当创建一个String对象时假如此字符串值已经存在于常量池中则不会创建一个新的对象而是引用已经存在的对象 (2)允许 String对象缓存 HashCode Java中String对象的哈希码被频繁地使用比如在HashMap等容器中。字符串不变性保证了 hash码的唯一性因此可以放心地进行缓存。这也是一种性能优化手段意味着不必每次都去计算新的哈希码 (3)String被许多的 Java类库用来当做参数例如网络连接地址URL、文件路径path、还有反射机制所需要的 String参数等假若 String不是固定不变的将会引起各种安全隐患。
String s1 new String(abc);这行代码创建了几个对象
如果字符串常量池中不存在字符串对象“abc”的引用那么它会在堆上创建两个字符串对象其中一个字符串对象的引用会被保存在字符串常量池中。
如果字符串常量池中已存在字符串对象“abc”的引用则只会在堆中创建 1 个字符串对象“abc”。
String a new String(aa) bb;这行代码创建了多少个对象
共创建了3-4个String对象假设常量池中没有对象“aa”第一个是常量池中的对象“aa”,如果常量池中没有就创建并添加进常量池中。第二个是new出来的以“aa”为初始值创建的 String对象第三个“bb”同“aa”,第四个是通过“”拼接前两个对象创建出的新String对象。
String的equals() 和 Object的equals() 有何区别
String 中的 equals 方法是被重写过的比较的是 String 字符串的值是否相等。 Object 的 equals 方法是比较的对象的内存地址。
String对象最多可以存放多少个字符
String在源码中使用 char[]来维护字符序列的而char[]的长度是 int类型所以理论上 String的长度最大为2^31-1占用空间大约为4GB,不过根据实际JVM的堆内存限制编译时String长度最多可以是2的16次方减2运行时长度最多可以是2的31次方减1意思是可以在编译时定义一些短的字符串运行时可以进行拼接长一点也可以。
string常量池放在哪
Java 8以前被放在永久代中Java 8及以后被放在方法区的元数据 metadata中。
String str i 和 String str new String(i)有什么区别
不一样因为内存的分配方式不一样。String str “i”的方式Java虚拟机会将其分配到常量池中而String str new String(“i)则会被分到堆内存中。
public class StringTest {public static void main(String[] args) {String strl abc;String str2 abc;String str3 new String(abc);String str4 new String(abc);System.out.println(str1 str2); // trueSystem.out.println(str1 str3); // falseSystem.out.println(str3 str4); // falseSystem.out.println(str3.equals(str4)); // true
在执行String str1“abc”的时候JVM会首先检查字符串常量池中是否已经存在该字符串对象如果已经存在那么就不会再创建了直接返回该字符串在字符串常量池中的内存地址如果该字符串还不存在字符串常量池中那么就会在字符串常量池中创建该字符串对象然后再返回。所以在执行Stringstr2“abc”的时候因为字符串常量池中已经存在“abc”字符串对象了就不会在字符串常量池中再次创建了所以栈内存中str1和str2的内存地址都是指向“abc”在字符串常量池中的位置所以str1str2 的运行结果为 true。
而在执行 String str3 new String(“abc)的时候JVM会首先检查字符串常量池中是否已经存在“abc”字符串如果已经存在则不会在字符串常量池中再创建了如果不存在则就会在字符串常量池中创建“abc”字符串对象然后再到堆内存中再创建一份字符串对象把字符串常量池中的“abc”字符串内容拷贝到内存中的字符串对象中然后返回堆内存中该字符串的内存地址即栈内存中存储的地址是堆内存中对象的内存地址。String str4 newString(“abc”)是在堆内存中又创建了一个对象所以 str3 str4运行的结果是 false。
String修改的实现原理
当用String类型来对字符串进行修改时其实现方法是首先创建一个StringBuilder其次调用 StringBuilder的 append(方法最后调用StringBuilder 的 toString()方法把结果返回。