1.String详解
- String 底层是一个最终类,即字符串是一个常量,不可变也不可继承,且String底层是一个不可变的char数组,所以每次对字符串的操作都为创建一个新的数组
- String实现的接口Serializable是序列化的标识,仅表示序列化语义;Comparable接口用于实现对象的自然排序,该接口只有一个方法:compareTo()方法,用于比较对象,小于返回负整数,等于返回0,大于返回正整数
- String实现的接口 CharSequence(可读字符序列),可以实现对不同类型的char数组只读访问,StringBuffer和StringBuilder也实现了这个接口
package java.lang;
import java.io.ObjectStreamField;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Formatter;
import java.util.Locale;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // 哈希值的缓冲,默认为o
- String支持多种参数类型的初始化方法,但本质上就是将接收的参数传递给成员变量value[],由于String内部实际上是由char[]数组实现的,所以String的方法本质上都是在内部调用数组的方法
/**
* Initializes a newly created {@code String} object so that it represents
* the same sequence of characters as the argument; in other words, the
* newly created string is a copy of the argument string. Unless an
* explicit copy of {@code original} is needed, use of this constructor is
* unnecessary since Strings are immutable.
*
* @param original
* A {@code String}
*/
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
/**
* Allocates a new {@code String} so that it represents the sequence of
* characters currently contained in the character array argument. The
* contents of the character array are copied; subsequent modification of
* the character array does not affect the newly created string.
*
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
-
1.1.equals()和HashCode()方法
- equals()方法先比较地址值,如果地址值即引用相同,返回true;String重写了equals方法,加上比较两个对象的内容,如果长度相遍历每个字符相等且顺序相同则返回true,所以equals方法是String能广泛用于Map[key,value]中的key的关键所在,另外String也提供了contentEquals()方法只用于比较内容
- HashCode()方法,由于String封装了一个哈希值的缓存hash,当对象不是新创建时直接调用hash将哈希值赋给对象,新建对象的hash缓存默认为0,需要根据哈希公式计算,Java 底层提供了一个计算公式(使用质数31可以有效的减少哈希值计算相同导致的哈希碰撞)
//String的equals()方法,比较的是地址值或内容
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
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;
}
//String的HashCode方法,使用了hash缓存,hash是String实例化的HashCode的一个缓存,因为字符串常用于比较,将HashCode缓存在hash中,进行比较时就不需要重新计算哈希值,即可直接比较,提高程序的效率
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
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.2.字符串常量池
String—亨元模式(Flyweigth):可以理解为缓存(cache)是一种设计模式或者说优化策略
-
常量池(Constant pool)被确定的,并且直接保存在已编译的class文件中的一些数据,包括类,方法,接口中的常量以及字符串常量等,程序执行时,常量池会储存在方法区.
-
JVM在内存中专门开辟了一块内存区域用于存放字符串对象,成为字符串常量池,在Java1.7以后字符串常量池移到了堆内存区域
在JDK6.0及之前版本中,String Pool里放的都是字符串常量 在JDK7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用.
-
Java中的八种基本类型以及String类型,由于会频繁使用这些类型,为了使其运行更快,节省内存,Java引入了常量池的概念,常量池就是类似一个Java提供的缓存
1.3.Java中有两种创建字符串的方式:
String a = "Hello World";
String b = "Hello World";
System.out.print(a==b);//true
1.采用字面值创建String对象,JVM会先去字符串常量池中查找是否有 "Hello World"这个对象,如果没有则在池中创建一个 "Hello World"对象,然后将这个对象的引用赋给a,a就会指向池中的 "Hello World"对象,但创建b对象时,由于JVM在池中找到了 "Hello World"对象,所以JVM会将池中这个对象的引用直接赋给b,这样a,b两个引用都指向了同一个对象,所以a==b为true
String c = new String("Hello World");
String d = new String("Hello World");
System.out.print(c==d);//false
2.采用new创建对象,JVM会先去字符串常量池中查找是否有 "Hello World"这个对象,如果没有则在池中创建一个 "Hello World"对象,并将引用指向堆中,然后new String在堆中开辟了一个空间,栈中的引用a指向这个空间,每new一次都会在堆中开辟一个空间,所以b指向了堆中新开辟的new String空间,c,d在堆中的地址值不同,即c==d为false.
-
String字面值运算时,并不会把每个String对象放入池中,只会把计算结果放入池中
-
JVM对字符串常量池的优化可以总结为:未声明放结果,已声明放引用,所以字符串常量池中可以同时放字符串常量和引用
2.AbstractStringBuilder
AbstractStringBuilder 在java.lang 包中,是一个抽象类,实现 Appendable 接口和 CharSequence 接口,这个类的诞生是为了解决 String 类在创建对象的时候总是创建新的对象的问题的。AbstractStringBuilder 有两个子类,分别是 StringBuilder 和 StringBuffer.
AbstractStringBuilder类和String类很类似,String类内部维护了一个char数组,用final修饰,即不可变.
AbstractStringBuilder类中同样维护了一个char数组但是是个变量,即是可变数组,这就是为了解决String每次操作都要创建新对象的关键所在.
package java.lang; import sun.misc.FloatingDecimal; import java.util.Arrays; abstract class AbstractStringBuilder implements Appendable, CharSequence { /** * The value is used for character storage. */ char[] value;//可变数组 /** * The count is the number of characters used. */ int count;//计数 /** * This no-arg constructor is necessary for serialization of subclasses. */ AbstractStringBuilder() {//无参构造 } /** * Creates an AbstractStringBuilder of the specified capacity. */ AbstractStringBuilder(int capacity) {//含参构造 value = new char[capacity]; } /** * Returns the length (character count). * * @return the length of the sequence of characters currently * represented by this object */
2.1.主要方法:
1.length(),返回char[]的实际长度(count),重写的CharSequence接口的length()方法.
2.capacity():capacity意思是’容量’,即得到目前该value数组的实际大小
@Override public int length() { return count; } public int capacity() { return value.length;//初始容量为16 } StringBuilder builder = new StringBuilder("adadsagfaesgsa"); System.out.println(builder.length());//14,数组的长度 System.out.println(builder.capacity());//30,数组的容量,不够时自动扩容(扩容机制:容量不够用时,先将当前容量+1的二倍(newCapacity)与需要的容量(minimumCapacity)比较如果比需要的容量大,那就将容量扩大到容量+1的二倍;如果比需要的容量小,那就直接扩大到需要的容量。)
3.toString是AbstractStringBuilder抽象类唯一的一个抽象方法;唯一的一个final方法:getValue(),得到value数组。可以对其直接操作
@Override public abstract String toString();//继承自Object类 final char[] getValue() { return value; }
4.append()方法—AbstractStringBuilder类及其子类中最重要的操作;Appendable接口的具体实现,源码中所有的append()方法返回值都是原类型AbstractStringBuilder,所以append()可以无限调用.
/*append(Object obj):利用Object(或任何对象)的toString方法转成字符串然后添加到该value[]中*/ public AbstractStringBuilder append(Object obj) { return append(String.valueOf(obj)); } /*public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }*/ /**/ public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len);//扩容 str.getChars(0, len, value, count);//将原数组拼接到目标数组(value从str下标为0处开始拼接count长度) count += len; return this; } // Documentation in subclasses because of synchro difference public AbstractStringBuilder append(StringBuffer sb) { if (sb == null) return appendNull(); int len = sb.length(); ensureCapacityInternal(count + len); sb.getChars(0, len, value, count); count += len; return this; } // Documentation in subclasses because of synchro difference @Override public AbstractStringBuilder append(CharSequence s) { if (s == null) return appendNull(); if (s instanceof String)//判断s是否是String的实例 return this.append((String)s); if (s instanceof AbstractStringBuilder) return this.append((AbstractStringBuilder)s); return this.append(s, 0, s.length()); } private AbstractStringBuilder appendNull() { int c = count; ensureCapacityInternal(c + 4); final char[] value = this.value;//如果str 是 null,就賦予str = "null",即字符串长度为4 value[c++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; return this; }
5.setCharAt(int index, char ch):直接设置下标为index的字符为ch
public void setCharAt(int index, char ch) { if ((index < 0) || (index >= count)) throw new StringIndexOutOfBoundsException(index); value[index] = ch; }
6.replace(int start, int end, String str):用字符串str替换掉value[]数组的[start,end)部分–左闭右开
public AbstractStringBuilder replace(int start, int end, String str) { if (start < 0) throw new StringIndexOutOfBoundsException(start); if (start > count) throw new StringIndexOutOfBoundsException("start > length()"); if (start > end) throw new StringIndexOutOfBoundsException("start > end"); if (end > count) end = count; int len = str.length(); int newCount = count + len - (end - start); ensureCapacityInternal(newCount); System.arraycopy(value, end, value, start + len, count - end); str.getChars(value, start); count = newCount; return this; }
7.insert(int index, char str[], int offset,int len):在value[]的下标为index位置插入数组str的一部分[offset,offest+len)–左闭右开
public AbstractStringBuilder insert(int offset, Object obj) { return insert(offset, String.valueOf(obj));//插入数组 } public AbstractStringBuilder insert(int index, char[] str, int offset, int len) { if ((index < 0) || (index > length())) throw new StringIndexOutOfBoundsException(index); if ((offset < 0) || (len < 0) || (offset > str.length - len)) throw new StringIndexOutOfBoundsException( "offset " + offset + ", len " + len + ", str.length " + str.length); ensureCapacityInternal(count + len);//扩容 System.arraycopy(value, index, value, index + len, count - index); System.arraycopy(str, offset, value, index, len);//数组增加 count += len; return this; }
8.在value[]查找字符串str,返回其下标
public int indexOf(String str) { return indexOf(str, 0);//返回str在value[]第一次出现的下标值 } public int lastIndexOf(String str) { return lastIndexOf(str, count);//返回str在value[]最后一次出现的下标值 }
9.删除方法,删除value[]中下标为[start,end)的一段数组,左闭右开
public AbstractStringBuilder delete(int start, int end) { if (start < 0) throw new StringIndexOutOfBoundsException(start); if (end > count) end = count; if (start > end) throw new StringIndexOutOfBoundsException(); int len = end - start; if (len > 0) { System.arraycopy(value, start+len, value, start, count-end); count -= len;//删除指定长度数组 } return this; }
3.StringBuilder
StringBuilder类继承自AbstractStringBuilder类,是一个可变的字符序列(char[] value,可变数组),相对于String,有其特有的append(),insert(),delete()方法,由于StringBuilder的字符串拼接调用的是append()方法,所以不会产生大量的临时新对象,提高了拼接效率,线程不安全,在JDK1.5之后引入,也是作为String对象在进行"+"操作时,其实本质上就是调用的StringBuilder的append()方法.
String a = "HelloWorld"; String b = "Hello" + "World"; String c = "Hello"; String d = "World"; String e = c + d; c System.out.println(a);//输出为HelloWorld System.out.println(e);//HelloWorld System.out.println(a == e);//false,两个String对象+操作时,底层调用的是StringBuiler的append()方法,先拼接Hello,再拼接上World,返回的结果e存入一个新的对象,所以说String字符串引用相加的计算效率要比直接相加的效率低 System.out.println(a == b);//true System.out.println(b == e);//false
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
public StringBuilder() {
super(16);//初始化容量
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);//使用str初始化,容量str大小的基础上加16
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
}
4.StringBuffer
StringBuffer和StringBuilder都是继承自AbstractStringBuilder类(char[] value,可变数组),两者实现的方法功能基本一致,不过StringBuffer的方法都是被 synchronized关键字修饰,所以StringBuffer是线程安全的,但是保证安全的同时牺牲了效率(同步锁),所以效率:StringBuilder>StringBuffer.
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence{
@Override
private transient char[] toStringCache;
public synchronized StringBuffer append(String str) {//同步关键字--线程安全
toStringCache = null;
super.append(str);
return this;
}
}
总结:1.String,StringBuilder和StringBuffer的区别
String | StringBuilder | StringBuffer |
---|---|---|
最终类不可继承 | 最终类不可继承 | 最终类不可继承 |
String字符串是常量不可变 | 变量可变 | 变量可变 |
线程不安全 | 线程安全 | |
进行大量字符串拼接时,回新建大量临时对象,浪费内存空间且效率低下 | 采用append()拼接字符串,可变数组拼接,效率高,常用于字符串拼接 | 与StirngBuilder相比,加入了同步锁(synchronized),保证了线程安全,当降低了效率 |
String继承自Object类 | StringBuilder继承自AbstractStringBuilder类 | StringBuffer继承自AbstractStringBuild |