String

115 阅读8分钟

String

-   String的底层是被final修饰的char数组
-   String该类被声明为final的,不可以被继承
-   String表示字符串属于引用数据类型,不属于基本数据类型。
-   直接使用双引号声明出来的 String 对象会直接存储在常量池中。比如:String info="hello"
-   String字符串具有不可变性,当字符串重新赋值时,不会在原来的内存地址进行修改,而是重新分配新的内存地址进行赋值。**

String JDK1.8 - JDK1.9变化

开发者发现人们在使用字符串的时候,多数使用的是英文单个只占一个字节,JDK1.8底层实现是使用的char数组,一个char类型所占字节数为2,所以在char数组存储英文的时候浪费了一个字节的内存空间。往往我们在使用字符串的时候都是大量使用的,浪费一个字节的内存空间必然会导致GC频繁,性能低下。所以开发者在JDK1.9将char数组改为了byte数组!

-    String在JDK1.8中的底层实现   ->  private final char value[];
-    String在JDK1.9中的底层实现   ->  private final byte[] value;

String内存分析及案例

引号创建的字符串,JVM会首先在常量池中寻找是否已经存在"abc"常量,如果没有则创建该常量,并且将此常量的引用返回给String str1;如果已有"abc" 常量,则直接返回常量池中"abc" 的引用给String str2 此创建方法之会在常量池中创建对象。

(new)出来的对象,实际上 "abc" 本身就是字符串常量池中的一个对象,在运行 new String() 时,把字符串常量池中的字符串 "abc"  复制到堆中,因此该方式不仅会在常量池中,还会在堆中创建 "abc" 字符串对象。 最后把堆中对象的引用返回st1 和 str2

String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);  // true  

str1 = new String("abc");
str2 = new String("abc");
System.out.println(str1 == str2);  // false

-   `==` 基本类型比较的是值,引用类型比较的是地址值  
-   `new` 出来的常量会在内存创建出两个对象,一个在堆内存,一个在常量池
String str = "a" + "b";      
System.out.println(str == "ab");  // true  

-   Java中有常量优化机制,"a","b"本身就是字符串常量,所以在编译时,会直接把"a""b"合并成"ab"字符串直接将 "ab" 丢入常量池 
String str = "ab";
String str1 = str + "c";  
System.out.println(str1 =="abc");   // false 

-   当变量与字面量(str + "A")或变量与变量(str1 + str2)进行拼接时,会在堆中创建一个StringBuilde对象,
    然后使用StringBuilder的append()方法进行拼接,最后调用toString()方法转成String对象
String str = "abc";
String str1 = str.intern(); 
System.out.println(str == str1);  //true

-   使用intern()这个方法时,首先会检查字符串常量池中是否有对应的字符串,
    如果存在则返回这个字符串的引用;否则会先将字符串添加到字符串常量池中,然后再返回这个字符串的引用
final String str1 ="a";
String str2 = "abc";
String str3 = s1 + "bc";
System.out.println(s2 == s3);   // true    很多人会疑惑为什么呢?????

-   此时的str1用final进行修饰,表示的是一个常量,所以此时str1+"bc"就相当于"a"+"bc",结果仍然是一个常量,被分配到常量池
String str1 = "ab";
String str2 = "a"+"b";
System.out.println(str1 == str2);    // true
System.out.println(str1.equals(str2));    // true

-   `==` 比较地址值,都在常量值中,相等。
-   `equals` String重写equals方法,比较的是数值,相等
-   `+` 运算符会在堆中建立起两个 String 对象 -> ["a","b"]
    也就是说从字符串常量池中复制这两个值,然后再堆中创建两个对象。然后再建立对象str2,然后将"ab"的堆地址赋给str2,实际上共创建了3String 对象。

String中 “+” 和 StringBuffer 中的 append 性能上有啥区别?

每次对String 类型进行 "+" 的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String对象。
所以经常改变内容的字符串最好不要用String"+"。因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。

-   StringBuffer
使用StringBuffer 类的 `append` 方法时每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。
-   需要线程安全使用 StringBuffer (因为StringBuffer在对应的`append`放啊发上加了`synchronized`)

StringBuffer 和 StringBuilder (可变字符串)

public StringBuilder() {     public StringBuilder(int capacity) {    public StringBuilder(String str) {                                             
    super(16);                 super(capacity);                          super(str.length() + 16);   
}                            }                                           append(str); 
                                                                       }

StringBuffer、StringBuilder和String类似,底层也是用一个数组来存储字符串的值,并且数组的默认长度为16, 即一个空的StringBuffer对象数组长度为16。实例化一个StringBuffer对象即创建了一个大小为16个字符的字符串缓冲区

当我们调用有参构造函数创建一个StringBuffer对象时,数组长度就不再是16了,而是根据当前对象的值来决定数组的长度,数组的长度为“当前对象的值的长+16”。所以一个 StringBuffer 创建完成之后,有16个字符的空间可以对其值进行修改。 如果修改的值范围超出了16个字符,会先检查StringBuffer对象的原char数组的容量能不能装下新的字符串,如果装不下则会对 char 数组进行扩容。

那StringBuffer是怎样进行扩容的呢?

扩容的逻辑就是创建一个新的 char 数组,将现有容量扩大一倍再加上2,如果还是不够大则直接等于需要的容量大小。扩容完成之后,将原数组的内容复制到新数组,最后将指针指向新的 char 数组。

-   1.  StringBuffer几乎所有的方法都使用synchronized实现了同步,线程比较安全,在多线程系统中可以保证数据同步,
    但是效率比较低;而StringBuilder 没有实现同步,线程不安全,在多线程系统中不能使用 StringBuilder,但是效率比较高。
    
-   2.  如果我们在实际开发过程中需要对字符串进行频繁的修改,不要使用String,否则会造成内存空间的浪费;
    当需要考虑线程安全的场景下使用 StringBuffer,如果不需要考虑线程安全,追求效率的场景下可以使用 StringBuilder。

StringBuffer和StringBuilder中常用API

StringBuffer append(xxx):拼接字符串
StringBuffer delete(int start,int end):删除指定范围的内容,左开右闭
StringBuffer replace(int start, int end, String str):替换指定范围的内容
StringBuffer insert(int offset, xxx):在指定位置插入指定的内容
StringBuffer reverse() :把当前字符序列逆转

public int indexOf(String str) : 返回指定子字符串在当前字符串中第一次出现处的索引
public String substring(int start,int end) :返回指定范围的子字符串
public int length() : 返回字符串的长度
public char charAt(int n ) : 获取指定索引处的字符
public void setCharAt(int n ,char ch) : 设置指定索引处的字符

String 常用API

int length():返回字符串的长度
char charAt(int index):返回指定索引处的字符
boolean isEmpty():判断字符串是否为空
String toLowerCase():将字符串中的所有字符转换为小写
String toUpperCase():将字符串中的所有字符转换为大写
String trim():返回字符串的副本,去掉前导空白和尾部空白,中间的空白不会被去掉
boolean equals(Object obj):比较字符串的内容是否相同
boolean equalsIgnoreCase(String anotherString):忽略大小写,比较字符串的内容是否相同
String concat(String str):将指定字符串连接到此字符串的结尾,等价于用"+"
int compareTo(String anotherString):比较两个字符串的大小
String substring(int beginIndex):返回从beginIndex到末尾的子字符串
String substring(int beginIndex, int endIndex):返回从beginIndex到endIndex前一位的子字符串,不包括endIndex

boolean endsWith(String suffix): 判断字符串是否以指定的后缀结束
boolean startsWith(String prefix):判断字符串是否以指定的前缀开始
boolean startsWith(String prefix, int toffset):判断字符串在指定索引开始的子字符串是否以指定前缀开始
boolean contains(CharSequence s):判断当前字符串中是否包含指定的字符串

int indexOf(String str):返回指定子字符串在当前字符串中第一次出现处的索引
int indexOf(String str, int fromIndex):返回从指定的索引后,指定子字符串在当前字符串中第一次出现处的索引
int lastIndexOf(String str):返回指定子字符串在当前字符串中最后一次出现处的索引
int lastIndexOf(String str, int fromIndex):返回从指定的索引后,指定子字符串在当前字符串中最后一次出现处的索引
-   注:indexOf和lastIndexOf方法如果未查找到指定子字符串时,返回值都为-1。

String replace(char oldChar, char newChar):替换当前字符串中指定的子字符串
String[] split(String regex):根据指定的符号拆分当前字符串,然后返回一个String数组