String不可变性
Java 8
中,String 使用 char 数组存储数据
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final char[] value; }
Java 9
中,String 类改用 byte 数组存储字符串,同时用coder
来标识使用哪种编码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { private final byte[] value; private final byte coder; }
String 是一个 final 类,意味着它不可以被继承,并且存储数据的 value 数组也被声明为 final,意味着 value 数组初始化后不能再引用其他数组,再加上 String 内部没有更改 value 数组数据的方法,所以 String 不可变。
public static void main(String[] args) { String s1 = "abc"; //不可变,意味着s1数据不能更改,所以下列过程是重新创建一个新对象赋给s1 s1 = "def"; }
String Pool
常量池的介绍参考此文:https://www.cnblogs.com/jinggod/p/8425748.html
字符串常量池(String Pool
)保存着所有字符串字面量。当程序第一次使用某个字符串字面量时,会使用常量池缓存该字面量,后续程序需要再次使用该字面量时,会直接从常量池中取。
public static void main(String[] args) { //第一次使用字符串字面量,会在常量池创建一个字符串对象,s1指向该对象 String s1 = "Love You"; //s2的结果在编译期间就已经计算出来,是字符串字面量,从常量池中取数据,s2指向常量池中的字符串对象 String s2 = "Love" + " You"; //s3结果在编译期无法计算出,运行时才能计算,所以需要在堆中创建对象 String s3 = s2 + ""; //使用new直接在堆中创建对象 String s4 = new String("Love You"); System.out.println(s1 == s2); //true System.out.println(s1 == s3); //false System.out.println(s1 == s4); //false }
在运行过程,可以使用 String 的 intern()
方法将字符串添加到 String Pool
中。当一个字符串调用 intern()
方法时,如果 String Pool
已经存在一个字符串和该字符串值相等(使用 equals()
方法进行确定,那么就会返回 String Pool
中字符串的引用;否则,就会在 String Pool
中添加一个新的字符串,并返回这个新字符串的引用。
public static void main(String[] args) { String s1 = new String("aaa"); String s2 = new String("aaa"); System.out.println(s1 == s2); //false String s3 = s1.intern(); //常量池中无数据,添加 String s4 = s2.intern(); //常量池中有数据,从常量池中取 System.out.println(s3 == s4); //true }
在 Java 7
之前,String Pool
被放在运行时常量池中,它属于永久代。而在 Java 7
,String Pool
被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
new String("abc") 解析
使用这种方式一共会创建两个字符串对象(前提是 String Pool
中还没有 "abc" 字符串对象)。
- "abc" 属于字符串字面量,因此编译时期会在
String Pool
中创建一个字符串对象,存储该字符串字面量; - 运行期时,使用 new 的方式会在堆中创建一个字符串对象。
String类方法
构造方法
//构造一个空字符串对象 String(); //通过byte数组构造字符串对象 String(byte[] bytes); //指定字符集解析byte数组 String(byte[] bytes, Charset charset); //通过byte数组,从offset开始,总共length长的字节构造字符串对象 String(byte[] bytes,int offset,int length); //指定字符集 String(byte[] bytes,int offset,int length, Charset charset); //通过char数组构造字符串对象 String(char[] value); //通过char数组,从offset开始,总共length长的字节构造字符串对象 String(char[] char,int offset,int length); //构造一个original的副本,拷贝一个original String(String original); //通过StringBuffer对象构造字符串对象 String(StringBuffer buffer); //通过StringBuilder对象构造字符串对象 String(StringBuilder builder);
获取序列长度 length
//返回当前字符串长度 int length();
获取指定字符 charAt
//返回字符串指定位置的字符,index取值0到length()-1 char charAt(int index);
获取数组对象 getBytes/toCharArray
//将String对象转换成byte数组 byte[] getBytes(); //指定字符集 byte[] getBytes(String charsetName)
//将该String对象转换成char数组 char[] toCharArray();
比较字符串 compareTo/compareToIgnoreCase
//比较字符串大小,相等返回0;不相等时,返回第一个不相等字符的字典序差; //如果有个字符串为另一个字符串前缀,则返回长度差,即this.length()-anotherString.length() int compareTo(String anotherString); //不考虑大小写 int compareToIgnoreCase(String anotherString);
比较字符串 equals/equalsIgnoreCase/contentEquals
//String类重写了equal方法。字符串与指定对象的字符序列相等返回true,不等返回false boolean equals(Object anObject); //不区分大小写比较 boolean equalsIgnoreCase(Object anObject); //内部实现: 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 与 StringBuilder 的字符序列进行比较,相等返回 true,不能返回 false boolean contentEquals(StringBuffer sb); //内部实现: public boolean contentEquals(StringBuffer sb) { synchronized (sb) { return contentEquals((CharSequence) sb); } }
此处顺便一提,=
运算符比较的是是否为同一个字符串对象的引用,而不是比较字符序列,所以比较字符序列使用 equals
方法。
连接字符串 concat/"+"
//与运算符“+”号功能相同 String concat(String str);
截取字符串 substring
//获取从 beginIndex 位置开始到结束的子字符串 String substring(int beginIndex);
//获取从 beginIndex 位置开始到 endIndex 位置(不包括)的子字符串 String substring(int beginIndex, int endIndex);
去掉首尾空字符 trim
//去掉该字符串首尾空字符 String trim();
字符串拆分 split
//regex -- 正则表达式分隔符 //limit -- 分割的份数 String[] split(String regex); String[] split(String regex, int limit);
String str = new String("Welcome-to-Blog"); for (String retval: str.split("-")){ System.out.println(retval); } //输出 Welcome to Blog for (String retval: str.split("-", 2)){ System.out.println(retval); } //输出 Welcome to-Blog
大小写 toLowerCase/toUpperCase
//将字符串转换成小写 String toLowerCase(); //将字符串转换成大写 String toUpperCase();
替换 replace/replaceAll/replaceFirst
//用newChar字符替换字符串中出现的所有oldChar字符,并返回替换后的新字符串 String replace(char oldChar, char newChar); //用字符序列replacement替换所有出现的target,内部实现机理是replaceAll。 String replace(CharSequence target, CharSequence replacement); //使用给定的参数replacement替换字符串中所有匹配给定的正则表达式的子字符串。 String replaceAll(String regex, String replacement); //使用给定的参数replacement替换字符串中第一个匹配给定的正则表达式的子字符串 String replaceFirst(String regex, String replacement);
前/后缀匹配 startsWith/endsWith
//用于检测字符串是否以指定的前缀开始 boolean startsWith(String prefix); //toffset--字符串中开始查找的位置。 boolean startsWith(String prefix, int toffset); //用于检测字符串是否以指定的后缀结束 boolean endsWith(String suffix);
定位 indexOf/lastIndexOf/contains
//返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。 int indexOf(int ch); //返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引 int indexOf(int ch, int fromIndex); //返回指定字符序列在字符串中第一次出现处的索引 int indexOf(String str); //返回从 fromIndex 位置开始查找指定字符序列在字符串中第一次出现处的索引 int indexOf(String str, int fromIndex); //同理,LastIndexOf 具有同样的方法,只是返回的是最后一次出现处的索引 //是否包含子字符串 boolean contains(CharSequence s);
StringBuffer和StringBuilder
String、StringBuffer、StringBuilder都为字符串类,三者都实现了 CharSequence 接口,可认为 CharSequence 是一个字符串的协议接口。
三者的区别:
- String 是不可变类,而 StringBuffer 和 StringBuilder 是可变的。
- String 是不可变类,所有线程安全;StringBuffer 是线程安全的,内部使用 synchronized 进行同步;而 StringBuilder 不是线程安全的。
通常情况下,如果需创建序列可变的字符串对象,优先考虑 StringBuilder 类,因为线程无需同步,性能高。
常用方法
StringBuffer 和 StringBuilder 方法大多与 String 差不多,但是由于他们是可变的,所以可以进行增、删、改等操作,下面以 StringBuffer 为例:
追加字符串 append
//将指定的字符串追加到此字符序列 StringBuffer append(String s);
删除指定字符序列 delete
//移除此序列的子字符串中的字符(包括 start, 不包括 end) delete(int start, int end);
替换字符串序列 replace
//使用给定的Str替换此序列的子字符串中的字符(包括 start,不包括 end) replace(int start, int end, String str);
插入字符序列 insert
//在offset位置插入字符串str StringBuffer insert(int offset, String str);
设置指定字符 setCharAt
//将index位置的字符设置为ch void setCharAt(int index, char ch);
反转字符串 reverse
//将此字符序列用其反转形式取代 StringBuffer reverse();