String

String特性

  • 不变性:String 对象创建后则不能被修改,是不可变的,所谓的修改其实是创建了新的对象,所指向的内存空间不同。

不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
只读特性:指向它的任何引用都不可能改变它的值

不可变的好处

  1. 可以缓存hash值
    因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。 不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算
  2. String Pool 的需要
    如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。 只有 String 是不可变的,才可能使用 String Pool。
  3. 安全性
    String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
  4. 线程安全
    String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
    • 常量池优化:String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
    • final:使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性。

String上的操作

  • String的构造方法 ```java // String的构造方法 public String() //空构造

public String(byte[] bytes) //把字节数组转成字符串

public String(byte[] bytes,int index,int length) //把字节数组的一部分转成字符串

public String(char[] value) //把字符数组转成字符串

public String(char[] value,int index,int count) //把字符数组的一部分转成字符串,第三个参数表示的是数目

public String(String original) //把字符串常量值转成字符串

  1. - **String类的判断功能**
  2. ```java
  3. boolean equals(Object obj) //比较字符串的内容是否相同,区分大小写
  4. boolean equalsIgnoreCase(String str) //比较字符串的内容是否相同,忽略大小写
  5. boolean contains(String str) //判断大字符串中是否包含小字符串
  6. boolean startsWith(String str) //判断字符串是否以某个指定的字符串开头
  7. boolean endsWith(String str) //判断字符串是否以某个指定的字符串结尾
  8. boolean isEmpty()// 判断字符串是否为空
  • 注意:字符串内容为空和字符串对象为空。

    1. String s = "";//字符串内容为空
    2. String s = null;//字符串对象为空
  • 分析:

    ==和equals的区别

  • ==:比较引用类型,比较的是地址值是否相同
    equals:比较引用类型,默认也是比较地址值是否相同
    String类重写了equals()方法,比较的是内容是否相同。

    String s = new String(“hello”)和String s = “hello”的区别?

  • 前者会创建2个对象,后者创建1个对象。更具体的说,前者会创建2个或者1个对象,后者会创建1个或者0个对象。
    每次 new 一个字符串就是产生一个新的对象,即便两个字符串的内容相同,使用 ”==” 比较时也为 ”false” ,如果只需比较内容是否相同,应使用 ”equals()” 方法

  • String类的获取功能 ```java int length() //获取字符串的长度。

char charAt(int index) //获取指定索引位置的字符

int indexOf(int ch) //返回指定字符在此字符串中第一次出现处的索引。为什么这里参数int类型,而不是char类型?原因是:’a’和97其实都可以代表’a’

int indexOf(String str) //返回指定字符串在此字符串中第一次出现处的索引。

int indexOf(int ch,int fromIndex) //返回指定字符在此字符串中从指定位置后第一次出现处的索引。

int indexOf(String str,int fromIndex) //返回指定字符串在此字符串中从指定位置后第一次出现处的索引。

String substring(int start) //从指定位置开始截取字符串,默认到末尾。

String substring(int start,int end) //从指定位置开始到指定位置结束截取字符串。左闭右开

  1. - **String的转换功能**
  2. ```java
  3. byte[] getBytes() //字符串转换为字节数组。
  4. char[] toCharArray() //把字符串转换为字符数组。
  5. static String valueOf(char[] chs) //把字符数组转成字符串。
  6. static String valueOf(int i) //把int类型的数据转成字符串。注意:String类的valueOf方法可以把任意类型的数据转成字符串。
  7. String toLowerCase() //把字符串转成小写。
  8. String toUpperCase() //把字符串转成大写。
  9. String concat(String str) //把字符串拼接。
  • String类的其他功能 ```java //替换功能: String replace(char old,char new) String replace(String old,String new)

//去除字符串两端空格 String trim()

//按字典顺序比较两个字符串 int compareTo(String str) int compareToIgnoreCase(String str)

  1. - **String的正则表达式工具**<br />正则表达式:<br />`\d`表示一位数字<br />`\\\\`表示一个普通的反斜线<br />`\n\t`表示换行和制表符<br />`+`表示一个或多个之前的表达式<br />可能有一个负号,后面跟着一位或者多位数字:`-?\\d+`<br />可能以一个加号或者负号开头:`(-|\\+)?`<br />`\w(小写)` 表示一个单词字符<br />`\W(大写)`表示非单词字符
  2. ```java
  3. // 将字符串从正则表达式匹配的地方切开
  4. String[] String.split(String s)
  5. // 将字符串从正则表达式匹配的地方切开,限制分割次数
  6. String[] String.split(String s,int limit)
  7. // 替换第一个匹配regex的部分
  8. String replaceFirst(String regex,String replacement)
  9. // 替换所有匹配regex的部分
  10. String replaceAll(String regex,String replacement)

一旦一个字符串在内存中创建,则这个字符串将不可改变。如果需要一个可以改变的字符串,我们可以使用StringBuffer或者StringBuilder

StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。

StringBuffer

  1. //StirngBuffer的构造方法
  2. public StringBuffer() //无参构造方法
  3. public StringBuffer(int capacity) //指定容量的字符串缓冲区对象
  4. public StringBuffer(String str) //指定字符串内容的字符串缓冲区对象
  5. //StringBuffer的方法
  6. public int capacity() //返回当前容量。 理论值
  7. public int length() //返回长度(字符数)。 实际值
  • StringBuffer的添加功能 ```java //可以把任意类型数据添加到字符串缓冲区里面,并返回字符串缓冲区本身 public StringBuffer append(String str)

//在指定位置把任意类型的数据插入到字符串缓冲区里面,并返回字符串缓冲区本身 public StringBuffer insert(int offset,String str)

  1. - **StringBuffer的删除功能**
  2. ```java
  3. //删除指定位置的字符,并返回本身
  4. public StringBuffer deleteCharAt(int index)
  5. //删除从指定位置开始指定位置结束的内容,并返回本身
  6. public StringBuffer delete(int start,int end)
  • StringBuffer的替换功能

    1. //从start开始到end用str替换
    2. public StringBuffer replace(int start,int end,String str)
  • StringBuffer的反转功能

    1. public StringBuffer reverse()
  • StringBuffer的截取功能 ```java //TODO:注意截取返回的是String,而不是StringBuffer了 public String substring(int start)

public String substring(int start,int end)

  1. <a name="StringBuilder"></a>
  2. ## StringBuilder
  3. - **StringBuilder的常用成员方法**
  4. ```java
  5. // 追加内容至StringBuilder对象末尾
  6. public StringBuilder append(String str)
  7. //将内容插入至StringBuilder对象的指定位置后
  8. public StringBuilder insert(int offset,String str)
  9. // 将StringBuilder对象转换为String对象
  10. public String toString()
  11. // 获取字符串的长度
  12. public int length()

三者的比较

1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

2. 线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部方法使用 synchronized 进行同步

3. 性能

  • Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢
  • StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用
  • StringBuilder每次都会对StringBuilder对象本身进行操作,而不是生成新的对象并改变对象引用。 相同情况下使用StringBuilder相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却冒多线程不安全的风险。

三者使用的总结

  • 操作少量的数据,使用String
  • 单线程操作字符串缓冲区下操作大量数据,使用StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据,使用StringBuffer

String和StringBuffer的互相转换
  • String —> StringBuffer

    1. //通过构造方法
    2. StringBuffer sb = new StringBuffer(s);
    3. //通过append()方法
    4. StringBuffer sb = new StringBuffer();
    5. sb.append(s);
  • StringBuffer —> String

    1. //通过构造方法
    2. String s = new String(buffer);
    3. //通过toString()方法
    4. String s = buffer.toString();

String Pool

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。
字符串常量池位于堆内存中,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。

不仅如此,还可以使用 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 引用的是同一个字符串。

  1. String s1 = new String("aaa");
  2. String s2 = new String("aaa");
  3. System.out.println(s1 == s2); // false
  4. String s3 = s1.intern();
  5. String s4 = s1.intern();
  6. System.out.println(s3 == s4); // true

如果是采用 “bbb” 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。

  1. String s5 = "bbb";
  2. String s6 = "bbb";
  3. System.out.println(s5 == s6); // true

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。 而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

  1. String s1="ab"; String s2="a"+"b"; String s3="a"; String s4="b"; s5=s3+s4;
  2. 在编译过程中,编译器会将s2直接优化为"ab",会将其放置在常量池当中
  3. s5则是被创建在堆区,相当于s5=new String(“ab”)