一、概述
字符串
什么是字符串?如果直接按照字面意思来理解就是多个字符连接起来组合成的字符序列。
字符串的分类
字符串分为可变的字符串和不可变的字符串两种。
- 不可变的字符串:当字符串对象创建完毕之后,该对象的内容(上述的字符序列)是不能改变的,一旦内容改变就会创建一个新的字符串对象;Java中的String类的对象就是不可变的。
- 可变的字符串:StringBuilder类和StringBuffer类的对象就是可变的;当对象创建完毕之后,该对象的内容发生改变时不会创建新的对象,也就是说对象的内容可以发生改变,当对象的内容发生改变时,对象保持不变,还是同一个。
String 类
String类表示不可变的字符串,当前String类对象创建完毕之后,该对象的内容(字符序列)是不变的,因为内容一旦改变就会创建一个一个新的对象。
String对象的创建:
方式一:通过字面量赋值创建,String s1 = “laofu”; 需要注意这里是双引号:“”,区别与字符char类型的单引号:‘’;
方式二:通过构造器创建, String s2 = new String(“laofu”);
String对象在JVM中是如何分布的
字符串常量池
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
方式一和方式二有何不同?
方式一:String s1 = “laofu”; 有可能只创建一个String对象,也有可能创建不创建String对象;如果在常量池中已经存在”laofu”,那么对象s1会直接引用,不会创建新的String对象;否则,会先在常量池先创建常量”laofu”的内存空间,然后再引用。
方式二:String s2 = new String(“laofu”); 最多会创建两个String对象,最少创建一个String对象。可使用new关键字创建对象是会在堆空间创建内存区域,这是第一个对象;然后对象中的字符串字面量可能会创建第二个对象,而第二个对象如方式一中所描述的那样,是有可能会不被创建的,所以至少创建一个String对象。
二、源码
1.String类的部分源码如下
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; // Default to 0
...
}
1)String类被final关键字修饰,意味着String类不能被继承,并且它的成员方法都默认为final方法;字符串一旦创建就不能再修改。
2)String类实现了Serializable、CharSequence、 Comparable接口。
3)String实例的值是通过字符数组实现字符串存储的。
2.String类的方法源码如下
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = count;
int i = -1;
char[] val = value; /* avoid getfield opcode */
int off = offset; /* avoid getfield opcode */
while (++i < len) {
if (val[off + i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
for (int j = 0 ; j < i ; j++) {
buf[j] = val[off+j];
}
while (i < len) {
char c = val[off + i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(0, len, buf);
}
}
return this;
}
从上面的三个方法可以看出,无论是sub操、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。
在这里要永远记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
3. “+”连接符的原理
Java语言为“+”连接符以及对象转换为字符串提供了特殊的支持,字符串对象可以使用“+”连接其他对象。其中字符串连接是通过 StringBuilder(或 StringBuffer)类及其append 方法实现的,对象转换为字符串是通过 toString 方法实现的,该方法由 Object 类定义,并可被 Java 中的所有类继承。有关字符连接和转换的更多信息,可以参阅 Gosling、Joy 和 Steele 合著的 《The Java Language Specification》
我们可以通过反编译验证一下
/**
* 测试代码
*/
public class Test {
public static void main(String[] args) {
int i = 10;
String s = "abc";
System.out.println(s + i);
}
}
/**
* 反编译后
*/
public class Test {
public static void main(String args[]) { //删除了默认构造函数和字节码
byte byte0 = 10;
String s = "abc";
System.out.println((new StringBuilder()).append(s).append(byte0).toString());
}
}
由上可以看出,Java中使用"+"连接字符串对象时,会创建一个StringBuilder()对象,并调用append()方法将数据拼接,最后调用toString()方法返回拼接好的字符串。
4. +”连接符的效率
使用“+”连接符时,JVM会隐式创建StringBuilder对象,这种方式在大部分情况下并不会造成效率的损失,不过在进行大量循环拼接字符串时则需要注意。
这样由于大量StringBuilder创建在堆内存中,肯定会造成效率的损失,所以在这种情况下建议在循环体外创建一个StringBuilder对象调用append()方法手动拼接。与此之外还有一种特殊情况,也就是当"+“两端均为编译期确定的字符串常量时,编译器会进行相应的优化,直接将两个字符串常量拼接好。
综上,“+”连接符对于直接相加的字符串常量效率很高,因为在编译期间便确定了它的值,也就是说形如"I”+“love”+“java”; 的字符串相加,在编译期间便被优化成了"Ilovejava"。对于间接相加(即包含字符串引用,且编译期无法确定值的),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。
三、常用方法
1.常见String类的获取功能
- public int length(): 获取字符串的长度。
- public char charAt(int index): 获取指定索引位置的字符
- public int indexOf(int ch): 返回指定字符在此字符串中第一次出现处的索引。
- public int indexOf(String str): 返回指定字符串在此字符串中第一次出现处的索引。
- public int indexOf(int ch,int fromIndex):返回指定字符在此字符串中从指定位置后第一次出现处的索引。
- public int indexOf(String str,int fromIndex): 返回指定字符串在此字符串中从指定位置后第一次出现处的索引。
- public String substring(int start): 从指定位置开始截取字符串,默认到末尾。
- public String substring(int start,int end): 从指定位置开始到指定位置结束截取字符串。
2.常见String类的判断功能
- public boolean equals(Object obj): 比较字符串的内容是否相同,区分大小写
- public boolean equalsIgnoreCase(String str): 比较字符串的内容是否相同,忽略大小写
- public boolean contains(String str): 判断字符串中是否包含传递进来的字符串
- public boolean startsWith(String str): 判断字符串是否以传递进来的字符串开头
- public boolean endsWith(String str): 判断字符串是否以传递进来的字符串结尾
- public boolean isEmpty(): 判断字符串的内容是否为空串""。
3.常见String类的转换功能
- public byte[] getBytes(): 把字符串转换为字节数组。
- public char[] toCharArray(): 把字符串转换为字符数组。
- public static String valueOf(char[] chs): 把字符数组转成字符串。
- public static String valueOf(int i): 把int类型的数据转成字符串。(String类的valueOf方法可以把任意类型的数据转成字符串。)
- public String toLowerCase(): 把字符串转成小写。
- public String toUpperCase(): 把字符串转成大写。
- public String concat(String str): 把字符串拼接。
4.常见String类的其他常用功能
- public String replace(char old,char new) 将指定字符进行互换
- public String replace(String old,String new) 将指定字符串进行互换
- public String trim() 去除两端空格
- public int compareTo(String str) 会对照ASCII 码表 从第一个字母进行减法运算 返回的就是这个减法的结果,如果前面几个字母一样会根据两个字符串的长度进行减法运算返回的就是这个减法的结果,如果连个字符串一摸一样 返回的就是0
- public int compareToIgnoreCase(String str) 跟上面一样 只是忽略大小写的比较
5.常见String类中int与String的相互转换
int转String有三种方式
- num + “”
- String.valueOf(num)
- Integer.toString(num)
String转int有两种方式 - Integer.parseInt(str)
- Integer.valueOf(str).intValue()
6.Java中的charAt()方法
java.lang.String.charAt() 方法返回指定索引处的char值。索引范围是从0到length() - 1。
四、String、StringBuffer、StringBuilder的区别
(1)可变与不可变:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。
(2)是否多线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。
(3)String、StringBuilder、StringBuffer三者的执行效率:
StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。比如String str = “hello”+ "world"的效率就比 StringBuilder st = new StringBuilder().append(“hello”).append(“world”)要高。因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:
当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;
当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。
五、Apache.commons.lang3包下的StringUtils工具类常用方法
1.检查字符串是否为空:
static boolean isBlank(CharSequence str) 判断字符串是否为空或null;
static boolean isNotBlank(CharSequence str) 判断字符串是否非空或非null;
StringUtils.isBlank(“a”); 返回结果为: false;
2.缩进字符串:
static String abbreviate(String str, int maxWidth) 缩进字符串,第二个参数至少为4(包括…)
StringUtils.abbreviate(“abcdefg”, 20);
返回结果为:abcdefg (正常显示)
StringUtils.abbreviate(“abcdefg”, 4);
返回结果为:a…
3.首字母大写:
static String capitalize(String str) 首字母大写
static String uncapitalize(String str)首字母小写
StringUtils.capitalize(“abcdefg”);
返回结果:Abcdefg
4.字符串显示在一个大字符串的位置:
static String center(String str, int size); 默认以空格填充
static String center(String str, int size, String padString); 其余位置字符串填充
public static String leftPad(String str,int size); 左侧空格填充
public static String leftPad(String str,int size,String padStr);左侧字符串填充
public static String rightPad(String str,int size); 左侧空格填充
public static String rightPad(String str,int size,String padStr);左侧字符串填充
StringUtils.center(“abcdefg”, 20);
返回结果: abcdefg
StringUtils.center(“abcdefg”, 20,"_");
返回结果:*_abcdefg*_
StringUtils.leftPad(“abc”, 10, “*”);
返回结果:*******abc
5.重复字符串次数
static String repeat(String str, int repeat);
StringUtils.repeat(“abc”, 5);
返回结果:abcabcabcabcabc
6.是否全是大写,是否全是小写(3.0版本)
public static boolean isAllLowerCase(String str);
public static boolean isAllUpperCase(String str);
StringUtils.isAllLowerCase(“abC”);
返回结果:false
7.是否都是由字母组成:
public static boolean isAlpha(String str); 只由字母组成
public static boolean isAlphaSpace(String str); 只有字母和空格组成
public static boolean isAlphanumeric(String str);只由字母和数字组成
public static boolean isAlphanumericSpace(String str);只由字母数字和空格组成
public static boolean isNumeric(String str);只由数字组成
public static boolean isNumericSpace(String str);只由数字和空格组成
StringUtils.isAlpha(“a2bdefg”);
返回结果:false
8.小字符串在大字符串中的匹配次数
public static int countMatches(String str,String sub);
StringUtils.countMatches(“ababsssababa”, “ab”);
返回结果:4
9.字符串倒转
public static String reverse(String str);
StringUtils.reverse(“abcdef”);
返回结果:fedcba
10.大小写转换,空格不动
public static String swapCase(String str);
StringUtils.swapCase("I am a-Aa")
返回结果:i AM A-aA