1 String 概述
String 被声明为 final,因此它不可被继承。
在 Java 8 中, String 内部使用 char 数组 存储数据。
在 Java 9 之后, String 类的实现改用 byte 数组存储字符串,同时使用coder来标识使用了哪种编 码。
value 数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内 部没有改变 value数组的方法,因此可以保证 String 不可变。
2 String不可变的好处
所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象。
可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得hash 值也不可变,因此只需要进行一次计算 。
String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用 。只有 String 是不可变的,才可能使用 String Pool(确实也是,如果改变的话,那么多个引用指向内存中字符串地址,那么就不对应了)。
安全性
String 经常作为参数, String 不可变性可以保证参数不可变 。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中 ,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
线程安全
String 不可变性天生具备线程安全 ,可以在多个线程中安全地使用。
3 String,StringBuffer和 StringBuilder的区别
String 和 StringBuilder 最大的区别在于 String 的内容无法修改,而 StringBuilder 的内容可 以修改 。频繁修改字符串的情况考虑使用 StringBuilder 。
注意: String 和 StringBuilder 类不能直接转换。如果要想互相转换,可以采用如下原则 :
String 变为 StringBuilder: 利用 StringBuilder 的构造方法或 append() 方法
StringBuilder 变为 String: 调用 toString() 方法
可变性
String的内容不可修改
StringBuffffer与StringBuilder的内容可以修改
StringBuffffer与StringBuilder大部分功能是相似的
线程安全
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步
4 字符串常量池(String Pool)
字符串常量池(String Pool)保存着所有字符串字面量 (literal strings),这些字面量在编译时期就确定 。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。 当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等( 使 用 equals() 方法进行确定 ),那么就会返回String Pool中字符串的引用 ;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
下面示例中, s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。 intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。
如果是采用"bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。
5 new String
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。
● "abc" 属于字符串字面量,因此编译时期会在String Pool中创建一个字符串对象 ,指向这个 "abc" 字符串字面量;
● 而使用 new 的方式会在堆中创建一个字符串对象。
创建一个测试类,其main 方法中使用这种方式来创建字符串对象。
如果是直接赋值的,String s = “abc”。那么就会先去字符串常量池看看,如果有,就直接赋值
没有就在字符串常量池中创建一个再赋值。但是 new出来的是不会入池的,想要入池还要用到
intern方法。(intern 是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中)
以下是 String 构造函数的源码,可以看到, 在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组 。
String类中两种对象实例化的区别
JDK1.8 中:
1. String str = "hello"
只会开辟一块堆内存空间,保存在字符串常量池中,然后 str 共享常量池中的 String 对象
2. String str = new String("hello")
会开辟两块堆内存空间,字符串 "hello" 保存在字符串常量池中,然后用常量池中的 String 对象给新开辟的String 对象赋值。
3. String str = new String(new char[]{'h', 'e', 'l', 'l', 'o'})
现在堆上创建一个 String 对象,然后利用 copyof 将重新开辟数组空间,将参数字符串数组中内容拷贝到 String对象中。
6 String 常用方法
字符串构造
String 是引用类型,内部并不存储字符串本身
String 类常用的构造方式以下三种:
public static void main(String[] args) { // 使用常量串构造 String s1 = "hello bit"; System.out.println(s1); // 直接newString对象 String s2 = new String("hello bit"); System.out.println(s1); // 使用字符数组进行构造 char[] array = {'h','e','l','l','o','b','i','t'}; String s3 = new String(array); System.out.println(s1); }
String对象的比较
1. == 比较是否引用同一个对象
注意:对于内置类型, == 比较的是变量中的值;对于引用类型 == 比较的是引用中的地址。
public static void main(String[] args) { int a = 10; int b = 20; int c = 10; // 对于基本类型变量,==比较两个变量中存储的值是否相同 System.out.println(a == b); // false System.out.println(a == c); // true // 对于引用类型变量,==比较两个引用变量引用的是否为同一个对象 String s1 = new String("hello"); String s2 = new String("hello"); String s3 = new String("world"); String s4 = s1; System.out.println(s1 == s2); // false System.out.println(s2 == s3); // false System.out.println(s1 == s4); // true }
2. boolean equals(Object anObject) 方法:按照字典序比较
字典序:字符大小的顺序
String 类重写了父类 Object 中 equals 方法, Object 中 equals 默认按照 == 比较, String 重写 equals 方法后,按照如下规则进行比较,比如: s1.equals(s2)
public boolean equals(Object anObject) { // 1. 先检测this 和 anObject 是否为同一个对象比较,如果是返回true if (this == anObject) { return true; } // 2. 检测anObject是否为String类型的对象,如果是继续比较,否则返回false if (anObject instanceof String) { // 将anObject向下转型为String类型对象 String anotherString = (String)anObject; int n = value.length; // 3. this和anObject两个字符串的长度是否相同,是继续比较,否则返回false if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; // 4. 按照字典序,从前往后逐个字符进行比较 while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } public static void main(String[] args) { String s1 = new String("hello"); String s2 = new String("hello"); String s3 = new String("Hello"); // s1、s2、s3引用的是三个不同对象,因此==比较结果全部为false System.out.println(s1 == s2); // false System.out.println(s1 == s3); // false // equals比较:String对象中的逐个字符 // 虽然s1与s2引用的不是同一个对象,但是两个对象中放置的内容相同,因此输出true // s1与s3引用的不是同一个对象,而且两个对象中内容也不同,因此输出false System.out.println(s1.equals(s2)); // true System.out.println(s1.equals(s3)); // false }
3. int compareTo(String s) 方法: 按照字典序进行比较
与 equals 不同的是, equals 返回的是 boolean 类型,而 compareTo 返回的是 int 类型。具体比较方式:
1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
2. 如果前 k 个字符相等 (k 为两个字符长度最小值 ) ,返回值两个字符串长度差值
public static void main(String[] args) { String s1 = new String("abc"); String s2 = new String("ac"); String s3 = new String("abc"); String s4 = new String("abcdef"); System.out.println(s1.compareTo(s2)); // 不同输出字符差值-1 System.out.println(s1.compareTo(s3)); // 相同输出 0 System.out.println(s1.compareTo(s4)); // 前k个字符完全相同,输出长度差值 -3 }
4. int compareToIgnoreCase(String str) 方法:与 compareTo 方式相同,但是忽略大小写比较
public static void main(String[] args) { String s1 = new String("abc"); String s2 = new String("ac"); String s3 = new String("ABc"); String s4 = new String("abcdef"); System.out.println(s1.compareToIgnoreCase(s2)); // 不同输出字符差值-1 System.out.println(s1.compareToIgnoreCase(s3)); // 相同输出 0 System.out.println(s1.compareToIgnoreCase(s4)); // 前k个字符完全相同,输出长度差值 -3 }
字符串查找
方法 |
功能 |
char charAt(int index) |
返回index位置上字符,如果index为负数或者越界,抛出 IndexOutOfBoundsException异常 |
int indexOf(int ch) |
返回 ch 第一次出现的位置,没有返回 -1 |
int indexOf(int ch, int fromIndex) |
从 fromIndex 位置开始找 ch 第一次出现的位置,没有返回 -1 |
int indexOf(String str) |
返回 str 第一次出现的位置,没有返回 -1 |
int indexOf(String str, int fromIndex) |
从 fromIndex 位置开始找 str 第一次出现的位置,没有返回 -1 |
int lastIndexOf(int ch) |
从后往前找,返回 ch 第一次出现的位置,没有返回 -1 |
int lastIndexOf(int ch, int fromIndex) |
从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有返 回-1 |
int lastIndexOf(String str) |
从后往前找,返回str第一次出现的位置,没有返回-1 |
int lastIndexOf(String str, int fromIndex) |
从fromIndex位置开始找,从后往前找str第一次出现的位置,没有返 回-1 |
转化
1. 数值和字符串转化
// 数字转字符串 String s1 = String . valueOf ( 1234 ); // 字符串转数字 int data1 = Integer . parseInt ( "1234" ); double data2 = Double . parseDouble ( "12.34" );
2. 大小写转换
// 小写转大写 s1 . toUpperCase (); // 大写转小写 s2 . toLowerCase ();
3.字符串转数组
// 字符串转数组 char [] ch = s . toCharArray (); // 数组转字符串 String s2 = new String ( ch );
4. 格式化
public static void main ( String [] args ) { String s = String . format ( "%d-%d-%d" , 2019 , 9 , 14 ); System . out . println ( s ); }
字符串替换
方法 |
功能 |
String replaceAll(String regex, String replacement) |
替换所有的指定内容 |
String replaceFirst(String regex, String replacement) |
替换首个内容 |
注意事项: 由于字符串是不可变对象, 替换不修改当前字符串, 而是产生一个新的字符串.
字符串拆分
方法 | 功能 |
String[] split(String regex) |
将字符串全部拆分 |
String[] split(String regex, int limit) |
将字符串以指定的格式,拆分为 limit 组 |
注意事项 :
1. 字符 "|","*","+" 都得加上转义字符,前面加上 "\\" .
2. 而如果是 "\" ,那么就得写成 "\\\\" .
3. 如果一个字符串中有多个分隔符,可以用 "|" 作为连字符
示例: 多次拆分
String str = "name=zhangsan&age=18" ; String [] result = str . split ( "&" ) ; for ( int i = 0 ; i < result . length ; i ++ ) { String [] temp = result [ i ]. split ( "=" ) ; System . out . println ( temp [ 0 ] + " = " + temp [ 1 ]); }
字符串截取
方法 | 功能 |
String substring(int beginIndex) |
从指定索引截取到结尾 |
String substring(int beginIndex, int endIndex) |
截取部分内容 |
1. 索引从 0 开始
2. 注意前闭后开区间的写法 , substring(0, 5) 表示包含 0 号下标的字符 , 不包含 5 号下标
其他操作方法
方法 | 功能 |
String trim() |
去掉字符串中的左右空格 , 保留中间空格 |
String toUpperCase() |
字符串转大写 |
String toLowerCase() |
字符串转小写 |