Saul's blog Saul's blog
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)

Saul.J.Wu

立身之本,不在高低。
首页
后端
分布式
前端
更多
分类
标签
归档
友情链接
关于
GitHub (opens new window)
  • Java入门基础

  • Java核心基础

    • 多线程

    • Java常用类

      • 字符串相关的类
        • String
          • Sttring的特性
          • 内存解析
          • String对象的创建
          • 字面量和new字符串区别
          • 字符串对象如何存储
          • 面试题
          • 练习
          • String使用陷阱
          • 小结
          • 面试题
          • String常用方法
          • 常见算法题目
        • StringBuffer
          • 特性
          • 构造方式
          • 常用方法
        • StringBuilder
        • 三者效率测试
        • 面试题
        • 总结
      • JDK8之前日期时间API
      • JDK8中新日期时间API
      • Java比较器
      • 其他类
      • Java随机数
    • 枚举类与注解

    • Java集合

    • 数据结构与算法

    • 泛型

    • IO流

    • 网络编程

    • 反射

    • 函数式编程

  • 设计模式

  • Web开发

  • SpringBoot

  • 微服务

  • Elasticsearch

  • 运维

  • 后端
  • Java核心基础
  • Java常用类
SaulJWu
2020-12-22

字符串相关的类

# String

# Sttring的特性

  • String类:代表字符串。Java 程序中的所有字符串字面值(如"abc" )都作为此类的实例实现。
  • String是一个final类,代表**不可变的字符序列*,不可被继承。简称不可变性。
    • 体现:当字符串重新赋值(包括连接操作或replace()方法)时,需要重写指定内存区域赋值,不能使用原有的value进行赋值。
  • 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
  • String实现了Serializable接口,表示字符串是支持序列化的。
  • String实现了Comparable接口,表示String可以比较大小。
  • String对象的字符内容是存储在一个字符数组value[]中的。

image-20201223000330420

# 内存解析

image-20201223001721451

String通过字面量的方式(区别于new关键字)给一个字符串赋值,此时的字符串值声明在字符串的常量池中。

String首次会去字符串常量池中找是否有相同值的内容,如果有直接将引用指向该地址,

如果没有,就在字符串常量池在开辟新的空间,再将引用指向这个地址。

所以字符串常量池中是不会有相同内容。

# String对象的创建

//字面量的方式,最常用
String str = "hello";

//本质上this.value = new char[0];
String  s1 = newString(); 

//this.value = original.value;
String  s2 = newString(String original); 

//this.value = Arrays.copyOf(value, value.length);
String  s3 = newString(char[] a); 

//从char数组中截取成字符串,startIndex开始截取的位置,count截取多少位
String  s4 = newString(char[] a,int startIndex,int count);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 字面量和new字符串区别

image-20201223003555800

  • 通字面量定义的方式,数据是声明在方法区的字符串常量池中。
  • 我们都知道凡是用new关键字+构造器的方式,都会在堆内存中开辟新的空间,将引用指向该地址,如果是字符串还要在常量池中新建一个char[],存储具体值,堆空间站的结构再指向常量池的地址。
@Test
public void test03() {
    String s1= "java";
    String s2= "java";
    String s3 = new String("java");
    String s4 = new String("java");
    System.out.println(s1 == s2);//true
    System.out.println(s1 == s3);//false
    System.out.println(s1 == s4);//fasle
    System.out.println(s3 == s4);//fasle
}
1
2
3
4
5
6
7
8
9
10
11

# 字符串对象如何存储

image-20201223004226970

image-20201223004253850

# 面试题

请问下面方式在内存中创建连几个对象?

String s = new String("abc");
1

两个,一个是堆空间中new结构,另个一个是char[]对应常量池中的数据:“abc”

# 练习

@Test
public void test04() {
    String s1 = "javaEE";
    String s2 = "hadoop";
    
    String s3 = "javaEEhadoop";
    String s4 = "javaEE" + "hadoop";
    String s5 = s1 + "hadoop";
    String s6 = "javaEE" + s2;
    String s7 = s1 + s2;
    
    System.out.println(s3 == s4);//true
    System.out.println(s3 == s5);//false
    System.out.println(s3 == s6);//false
    System.out.println(s3 == s7);//false
    System.out.println(s5 == s6);//false
    System.out.println(s5 == s7);//false
    System.out.println(s6 == s7);//false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

image-20201223005542261

# String使用陷阱

String s1 = "a"; 
1

说明:在字符串常量池中创建了一个字面量为"a"的字符串。

s1 = s1 + "b"; 
1

说明:实际上原来的“a”字符串对象已经丢弃了,现在在堆空间中产生了一个字符串s1+"b"(也就是"ab")。如果多次执行这些改变串内容的操作,会导致大量副本字符串对象存留在内存中,降低效率。如果这样的操作放到循环中,会极大影响程序的性能。

String s2 = "ab";
1

说明:直接在字符串常量池中创建一个字面量为"ab"的字符串。

String s3 = "a" + "b";
1

说明:s3指向字符串常量池中已经创建的"ab"的字符串。

String s4 = s1.intern();
1

说明:堆空间的s1对象在调用intern()之后,会将常量池中已经存在的"ab"字符串赋值给s4。

# 小结

  • 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
  • 只要其中有一个是变量,结果就在堆中。
    • 如果是加了fianl修饰的变量,就是常量。常量就是在常量池中。
  • 如果拼接的结果调用intern()方法,返回值就在常量池中。

# 面试题

下面输出结果是?

public class StringTest02 {
    String str = new String("good");

    char[] ch = {'t', 'e', 's', 't'};


    private void change(String str, char[] ch) {
        str = "test ok";
        ch[0] = 'b';
    }

    public static void main(String[] args) {
        StringTest02 ex = new StringTest02();
        ex.change(ex.str, ex.ch);
        System.out.print(ex.str + " and ");
        System.out.println(ex.ch);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

输出结果

good and best
1

因为调用change方法时,新建2个引用,str和ch,str是值传递,ch是引用传递

当str赋值新的值时,跟原来的值没关系,str是局部变量,出了change方法就无效了。

ch[0]虽然是新建的引用,但是和原来类的属性都是指向同一个地址,当ch[0]改变时,原来的也会改变。

增加输出语句试试:。

private void change(String str, char[] ch) {
    str = "test ok";
    ch[0] = 'b';
    //增加输出语句
    System.out.println("局部变量:" + str);
    System.out.println("局部变量:" + ch);
    System.out.println("局部变量:" + Arrays.toString(ch));
    System.out.println("成员变量:" + this.str);
    System.out.println("成员变量:" + this.ch);
    System.out.println("成员变量:" + Arrays.toString(this.ch));
}
1
2
3
4
5
6
7
8
9
10
11

输出结果:

局部变量:test ok
局部变量:[C@135fbaa4
局部变量:[b, e, s, t]
成员变量:good
成员变量:[C@135fbaa4
成员变量:[b, e, s, t]
good and best
1
2
3
4
5
6
7

很明显,

两个ch都是指向同一个地址,它们是同一个对象,

两个str是两个对象,只不过值相同而已。

# String常用方法

  • int length():返回字符串的长度:return value.length

  • char charAt(int index):返回某索引处的字符return value[index]

  • boolean isEmpty():判断是否是空字符串:return value.length == 0

  • String toLowerCase():使用默认语言环境,将String中的所有字符转换为小写

  • String toUpperCase():使用默认语言环境,将String中的所有字符转换为大写

  • String trim():返回字符串的副本,忽略前导空白和尾部空白

  • boolean equals(Object obj):比较字符串的内容是否相同

  • boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写

  • String concat(Stringstr):将指定字符串连接到此字符串的结尾。等价于用“+”

  • int compareTo(String anotherString):比较两个字符串的大小

  • String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。

  • String substring(int beginIndex,int endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

  • boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束

  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始

  • boolean startsWith(String prefix,int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始

  • boolean contains(CharSequence s):当且仅当此字符串包含指定的char 值序列时,返回true

  • int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引,如果未找到都是返回-1

  • int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始,如果未找到都是返回-1

  • int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引,如果未找到都是返回-1

  • int lastIndexOf(String str, int fromIndex):返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索,如果未找到都是返回-1

  • String replace(char oldChar,char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。

  • String replace(CharSequence target,CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

  • String replaceAll(String regex,String replacement):使用给 定 的replacement替换此字符串所有匹配给定的正则表达式的子字符串。

  • String replaceFirst(String regex,String replacement):使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串。

  • boolean matches(String regex):告知此字符串是否匹配给定的正则表达式。

  • String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。

  • String[] split(String regex,int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

小技巧:凡是endIndex都是左闭右开原则。a<=x<b。

# 练习

@Test
public void test1() {
    String str = "12hello34world5java7891mysql456";//把字符串中的数字替换成,,如果结果中开头和结尾有,的话去掉
    String string = str.replaceAll("\\d+", ",").replaceAll("^,|,$", "");
    System.out.println(string);//hello,world,java,mysql
}
1
2
3
4
5
6
@Test
public void test2() {
    String str = "12345";//判断str字符串中是否全部有数字组成,即有1-n个数字组成
    boolean matches = str.matches("\\d+");
    System.out.println(matches);//true
}
1
2
3
4
5
6
@Test
public void test3() {
    String tel = "0571-4534289";//判断这是否是一个杭州的固定电话
    boolean result = tel.matches("0571-\\d{7,8}");
    System.out.println(result);//true
}
1
2
3
4
5
6
@Test
public void test4() {
    String str = "hello|world|java";
    String[] strs = str.split("\\|");
    for (int i = 0; i < strs.length; i++) {
        System.out.println(strs[i]);
    }
    System.out.println();
    String str2 = "hello.world.java";
    String[] strs2 = str2.split("\\.");
    for (int i = 0; i < strs2.length; i++) {
        System.out.println(strs2[i]);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
hello
world
java

hello
world
java
1
2
3
4
5
6
7

# String与基本数据类型转换

其实前面都讲过了,复习一遍吧。

字符串->基本数据类型、包装类

  • Integer包装类的public static int parseInt(String s):可以将由“数字”字符组成的字符串转换为整型。
  • 类似地,使用java.lang包中的Byte、Short、Long、Float、Double类调相应的类方法可以将由**“数字”字符**组成的字符串,转化为相应的基本数据类型parseXXX

基本数据类型、包装类->字符串

  • 调用String类的public String valueOf(int n)可将int型转换为字符串。
  • 相应的valueOf(byte b)、valueOf(long l)、valueOf(float f)、valueOf(double d)、valueOf(booleanb)可由参数的相应类型到字符串的转换。

# String和char[]转换

char数组->字符串

  • String(byte[]):通过使用平台的默认字符集解码指定的byte 数组,构造一个新的String。
  • String(byte[],int offset,int length):用指定的字节数组的一部分,即从数组起始位置offset开始取length个字节构造一个字符串对象。

字符串->char数组

  • toCharArray():把字符串转换为char[]

# String与byte[]转换

编码:字符串->字节数组

  • public byte[] getBytes():使用平台的默认字符集将此String 编码为byte 序列,并将结果存储到一个新的byte 数组中。
  • public byte[] getBytes(String charsetName):使用指定的字符集将此String 编码到byte 序列,并将结果存储到新的byte 数组。

解码:字节->字符串

  • new String(byte[] bytes),使用构造器。

UTF-8:每个汉字占用3个字节。

gbk:每个汉字占用2个字节。

在web开发中,数据库,后台,前台编码集必须一致,否则会出现乱码。

# 练习

@Test
public void test5() throws UnsupportedEncodingException {
    String str = "中";
    System.out.println(str.getBytes("ISO8859-1").length);// -128~127
    System.out.println(str.getBytes("GBK").length);
    System.out.println(str.getBytes("UTF-8").length);
    System.out.println(new String(str.getBytes("ISO8859-1"), "ISO8859-1"));// 乱码,表示不了中文System.out.println(newString(str.getBytes("GBK"), "GBK"));System.out.println(newString(str.getBytes("UTF-8"), "UTF-8"));
}
1
2
3
4
5
6
7
8

输出结果:

1
2
3
?
1
2
3
4

# 常见算法题目

1、模拟一个trim方法,去除字符串两端的空格。

/**
 * 模拟一个trim方法,去除字符串两端的空格。
 * @param str 字符串
 */
public String myTrim(String str) {
    int beginIndex = 0;
    int endIndex = str.length();
    char[] arr = str.toCharArray();
    //从左边获取不是空格的索引
    while ((beginIndex < endIndex) && (arr[beginIndex] == ' ')) {
        beginIndex++;
    }
    //才右边获取不是空格的索引
    while ((beginIndex < endIndex) && (arr[endIndex - 1] == ' ')) {
        endIndex--;
    }
    if (beginIndex > 0 || endIndex < arr.length) {
        return str.substring(beginIndex, endIndex);
    } else {
        return str;
    }
}

@Test
public void test6() {
    String str = "   asdf   asaf  ";
    System.out.println(str);
    System.out.println(this.myTrim(str));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

输出结果:

   asdf   asaf  
asdf   asaf
1
2

2.将一个字符串进行反转。将字符串中指定部分进行反转。

比如“abcdefg”反转为”abfedcg”

// 方式一:
public String reverse1(String str, int start, int end) {// start:2,end:5
    if (str != null) {
        // 1.
        char[] charArray = str.toCharArray();
        // 2.
        for (int i = start, j = end; i < j; i++, j--) {
            char temp = charArray[i];
            charArray[i] = charArray[j];
            charArray[j] = temp;
        }
        // 3.
        return new String(charArray);

    }
    return null;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 方式二:
public String reverse2(String str, int start, int end) {
    // 1.
    String newStr = str.substring(0, start);// ab
    // 2.
    for (int i = end; i >= start; i--) {
        newStr += str.charAt(i);
    } // abfedc
    // 3.
    newStr += str.substring(end + 1);
    return newStr;
}
1
2
3
4
5
6
7
8
9
10
11
12
// 方式三:推荐 (相较于方式二做的改进)
public String reverse3(String str, int start, int end) {// ArrayList list = new ArrayList(80);
    // 1.
    StringBuffer s = new StringBuffer(str.length());
    // 2.
    s.append(str.substring(0, start));// ab
    // 3.
    for (int i = end; i >= start; i--) {
        s.append(str.charAt(i));
    }

    // 4.
    s.append(str.substring(end + 1));

    // 5.
    return s.toString();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

3.获取一个字符串在另一个字符串中出现的次数。

比如:获取“ ab”在“abkkcadkabkebfkabkskab” 中出现的次数

public int getCount(String mainStr, String subStr) {
    if (mainStr.length() >= subStr.length()) {
        int count = 0;
        int index = 0;
        // while((index = mainStr.indexOf(subStr)) != -1){
        // count++;
        // mainStr = mainStr.substring(index + subStr.length());
        // }
        // 改进:
        while ((index = mainStr.indexOf(subStr, index)) != -1) {
            index += subStr.length();
            count++;
        }

        return count;
    } else {
        return 0;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

4.获取两个字符串中最大相同子串。

比如:str1 = "abcwerthelloyuiodef“;str2 = "cvhellobnm"

提示:将短的那个串进行长度依次递减的子串与较长的串比较。

// 如果只存在一个最大长度的相同子串
public String getMaxSameSubString(String str1, String str2) {
    if (str1 != null && str2 != null) {
        String maxStr = (str1.length() > str2.length()) ? str1 : str2;
        String minStr = (str1.length() > str2.length()) ? str2 : str1;

        int len = minStr.length();

        for (int i = 0; i < len; i++) {// 0 1 2 3 4 此层循环决定要去几个字符

            for (int x = 0, y = len - i; y <= len; x++, y++) {

                if (maxStr.contains(minStr.substring(x, y))) {

                    return minStr.substring(x, y);
                }

            }

        }
    }
    return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 如果存在多个长度相同的最大相同子串
// 此时先返回String[],后面可以用集合中的ArrayList替换,较方便
public String[] getMaxSameSubString1(String str1, String str2) {
    if (str1 != null && str2 != null) {
        StringBuffer sBuffer = new StringBuffer();
        String maxString = (str1.length() > str2.length()) ? str1 : str2;
        String minString = (str1.length() > str2.length()) ? str2 : str1;

        int len = minString.length();
        for (int i = 0; i < len; i++) {
            for (int x = 0, y = len - i; y <= len; x++, y++) {
                String subString = minString.substring(x, y);
                if (maxString.contains(subString)) {
                    sBuffer.append(subString + ",");
                }
            }
            System.out.println(sBuffer);
            if (sBuffer.length() != 0) {
                break;
            }
        }
        String[] split = sBuffer.toString().replaceAll(",$", "").split("\\,");
        return split;
    }

    return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 如果存在多个长度相同的最大相同子串:使用ArrayList
public List<String> getMaxSameSubString1(String str1, String str2) {
    if (str1 != null && str2 != null) {
        List<String> list = new ArrayList<String>();
        String maxString = (str1.length() > str2.length()) ? str1 : str2;
        String minString = (str1.length() > str2.length()) ? str2 : str1;

        int len = minString.length();
        for (int i = 0; i < len; i++) {
            for (int x = 0, y = len - i; y <= len; x++, y++) {
                String subString = minString.substring(x, y);
                if (maxString.contains(subString)) {
                    list.add(subString);
                }
            }
            if (list.size() != 0) {
                break;
            }
        }
        return list;
    }

    return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

5.对字符串中字符进行自然顺序排序。

提示:

1)字符串变成字符数组。

2)对数组排序,选择,冒泡,Arrays.sort();

3)将排序后的数组变成字符串。

@Test
public void testSort() {
    String str = "abcwerthelloyuiodef";
    char[] arr = str.toCharArray();
    Arrays.sort(arr);

    String newStr = new String(arr);
    System.out.println(newStr);
}
1
2
3
4
5
6
7
8
9

# StringBuffer

# 特性

  • java.lang.StringBuffer代表可变的字符序列,JDK1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。
  • 很多方法与String相同。
  • 作为参数传递时,方法内部可以改变值。

image-20201223181538170

  • 线程安全,很多方法都是用synchronized

image-20201223182133972

# 构造方式

**StringBuffer类不同于String,其对象必须使用构造器生成。**有三个构造器:

  • StringBuffer():初始容量为16的字符串缓冲区。
  • StringBuffer(int size):构造指定容量的字符串缓冲区。
  • StringBuffer(String str):将内容初始化为指定字符串内容。

如上这些方法支持方法链操作。

源码分析

public StringBuffer() {
    super(16);
}
public StringBuffer(int capacity) {
    super(capacity);
}
public StringBuffer(String str) {
    super(str.length() + 16);
    append(str);
}
1
2
3
4
5
6
7
8
9
10

父类方法:

AbstractStringBuilder(int capacity) {
    value = new char[capacity];
}
1
2
3

相当于底层创建了一个长度是16的数组,如果是带String的,就增长16个空间。

注意:并不是说length的长度是16,而是有16个空间,实际长度还是以数组长度为准。

@Test
public void test1(){
    StringBuffer sb1 = new StringBuffer();
    System.out.println("sb1.length() = " + sb1.length());//0
}
1
2
3
4
5
  • 当append和insert时,如果原来value数组长度不够,可扩容。方法链原理:
@Override
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    super.append(str);
    return this;
}
1
2
3
4
5
6
public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}
1
2
3
4
5
6
7
8
9
private void ensureCapacityInternal(int minimumCapacity) {
    // overflow-conscious code
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}
1
2
3
4
5
6
7
private int newCapacity(int minCapacity) {
    // overflow-conscious code
    int newCapacity = (value.length << 1) + 2;
    if (newCapacity - minCapacity < 0) {
        newCapacity = minCapacity;
    }
    return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
        ? hugeCapacity(minCapacity)
        : newCapacity;
}
1
2
3
4
5
6
7
8
9
10

先获取追加字符串的长度,确保容量是否够,如果不够,就去扩容底层的数组,扩容完后,再把原来的数据赋值到新的数组中,再返回。

扩容:

默认情况下扩容长度为原来的2倍,再加2

如果这时长度还不够,就拿要追加的长度设置为长度。

指导意义:

建议开发中使用StringBuffer(int size):构造指定容量的字符串缓冲区。避免扩容,每次扩容都会复制数据,效率会更高。

# 常用方法

  • append(xxx):提供了很多的append()方法,用于进行字符串拼接
  • delete(int start,int end):删除指定位置的内容
  • replace(int start,int end,String str):把[start,end)位置替换为str
  • insert(int offset,xxx):在指定位置插入xxx
  • public int indexOf(String str)
  • public String substring(int start,int end)
  • public int length()
  • public char charAt(int n ):返回n位置的字符。
  • public void setCharAt(int n ,char ch):设置n位置的字符改为ch。

总结:

  • 增:append(xxx)
  • 删:delete(int start,int end)
  • 改:setCharAt(int n ,char ch)或replace(int start,int end,String str)
  • 查:charAt(int n )
  • 插:insert(int offset,xxx)
  • 长度:length()
  • 遍历:for + charAt(int n)或toString,这个用得不多

# StringBuilder

StringBuilder和StringBuffer 非常类似,均代表可变的字符序列,而且提供相关功能的方法也一样,不过它的自带的方法没有使用synchronized关键字,所以线程不安全而已,其他跟StringBuffer一模一样。

所以它的底层原理跟StringBuffer 也是一样的。

既然StringBuilder没有用到同步方法,所以效率是比StringBuffer 高的。

# 三者效率测试

对比String、StringBuffer、StringBuilder的效率。

@Test
public void test2() {
    //初始设置
    long startTime = 0L;
    long endTime = 0L;
    String text = "";
    StringBuffer buffer = new StringBuffer("");
    StringBuilder builder = new StringBuilder("");
    //开始对比
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
        buffer.append(String.valueOf(i));
    }
    endTime = System.currentTimeMillis();
    System.out.println("StringBuffer的执行时间:" + (endTime - startTime));
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
        builder.append(String.valueOf(i));
    }
    endTime = System.currentTimeMillis();
    System.out.println("StringBuilder的执行时间:" + (endTime - startTime));
    startTime = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
        text = text + i;
    }
    endTime = System.currentTimeMillis();
    System.out.println("String的执行时间:" + (endTime - startTime));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

输出结果:

StringBuffer的执行时间:3
StringBuilder的执行时间:2
String的执行时间:1236
1
2
3

这里也证明了效率:StringBuilder > StringBuffer > String

在没有要求极限效率的情况下还是可以用StringBuffer ,因为它是线程安全的。

# 面试题

下面输出结果是?

@Test
public void test3() {
    String a = "123";
    String b = "123";
    String c = new String("123");
    String d = new String("123");
    System.out.println("a.equals(b) = " + a.equals(b));
    System.out.println("a==b = " + (a == b));
    System.out.println("c.equals(d) = " + c.equals(d));
    System.out.println("c==d = " + (c == d));
    System.out.println("a.equals(c) = " + a.equals(c));
    System.out.println("a==c = " + (a == c));
}
1
2
3
4
5
6
7
8
9
10
11
12
13

输出结果:

a.equals(b) = true
a==b = true
c.equals(d) = true
c==d = false
a.equals(c) = true
a==c = false
1
2
3
4
5
6

结论:

  • equals是比较具体值,String已经默认重写了它的方法,只要具体值相同都是返回true
  • ==是比较地址,地址相同才返回true,new关键字都是开辟新的空间,字面量是引用常量池。

# 总结

String、StringBuffer、StringBuilder三者的异同?

String(JDK1.0):不可变的字符序列

StringBuffer(JDK1.0):可变的字符序列;线程安全的,效率低一些。

StringBuilder(JDK5.0):可变的字符序列;线程不安全的,效率高一些。

三者底层都是使用char[]继续进行存储。

注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。

如果开发当中,要对字符串频繁的修改,那么就要用可变的字符串序列,涉及到多线程就使用StringBuffer,否则使用StringBuilder

StringBuffer和StringBuilder扩容问题

如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。默认情况下,扩容为原来容量的2倍+2,同时将原有数组中的元素复制到新的数组中。

建议开发中使用StringBuffer(int size)或StringBuilder(int size):构造指定容量的字符串缓冲区。避免扩容,每次扩容都会复制数据,效率会更高。

String str = new String();//相当于char[] value = new char[0]
String str1 = new String("abc");//相当于char[] value = new char[]{'a','b','c'}

StringBuffer sb1 = new StringBuffer();//相当于char[] value = new char[16]
sb1.append('a');//相当于value[0] = 'a';
sb1.append('b');//相当于value[1] = 'b';

//相当于char[] value = new char["abc".length() + 16];append(str);长度
StringBuffer sb2 = new StringBuffer("abc");
System.out.println(sb2.length())//3
1
2
3
4
5
6
7
8
9
10
帮我改善此页面 (opens new window)
#String#StringBuffer#StringBuilder
上次更新: 2020/12/24, 12:01:43
Callable和线程池
JDK8之前日期时间API

← Callable和线程池 JDK8之前日期时间API→

最近更新
01
zabbix学习笔记二
02-28
02
zabbix学习笔记一
02-10
03
Linux访问不了github
12-08
更多文章>
Theme by Vdoing | Copyright © 2020-2022 Saul.J.Wu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式