JavaseLearn21-String和StringBuffer

JavaseLearn21-String&StringBuffer&StringBuilder

一、String

1. String字符串的存储原理

创建以下几个对象:

String s1 = "abc";
String s2 = "abc";
String s3 = "abc" + "def";
String s4 = new String("fff");
String s5 = new String("fff");

以上对象在JVM内存图中表示为:

//s1:在方法区常量池中创建一个"abc"
String s1 = "abc";
//s2:不会创建新对象,直接指向已经存在的"abc"
String s2 = "abc";
//s3:在常量池中创建两个对象,一个是"def",一个是"abcdef", "abc"由于s1已经创建过了,直接拿来使用
String s3 = "abc" + "def";
//s4:创建两个对象,一个是位于方法区常量池中的"fff",一个是位于堆内存中的String对象,存储的是常量池中"fff"的内存地址
String s4 = new String("fff");
//s5:只在堆内存中创建一个对象,存放的是s4已经在方法区常量池中创建了的"fff"内存地址
String s5 = new String("fff");

1.1为什么说String是不可变的呢?

String底层是一个被final修饰的byte[]数组。因为数组一旦创建,长度不可变,而且被final修饰的引用无法指向新的对象,所以说String是不可变的。

2. String字符串的比较运算

创建下列字符串对象:

String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");

内存图:

2.1通过"=="进行比较

通过“==”比较的是String字符串对象的内存地址

//s1 == s2:
//s1和s2存储的都是常量池中"abc"的内存地址,
//以下语句相当于:0x1111 == 0x1111 结果为:true
System.out.println("s1 == s2:" + (s1 == s2));

//s3 == s4:
//s3和s4存储的是各自在堆中的对象的内存地址,
//以下语句相当于:0x4444 == 0x6843 结果为:false
System.out.println("s3 == s4:" + (s3 == s4));

//s1 == s3:
//s1存储的都是常量池中"abc"的内存地址,s4存储的是在堆中的对象的内存地址,
//以下语句相当于:0x1111 == 0x4444 结果为:false
System.out.println("s1 == s3:" + (s1 == s3));

//"abc" == s1:
//这里相当于拿常量池中"abc"的内存地址与s1中存储的常量池中"abc"的内存地址相比较,
//以下语句相当于:0x1111 == 0x1111 结果为:true
System.out.println("\"abc\" == s1:" + ("abc" == s1));

//"abc" == s3:
//这里相当于拿常量池中"abc"的内存地址与s3中存储的在堆中对象的内存地址相比较,
//以下语句相当于:0x1111 == 0x4444 结果为:false
System.out.println("\"abc\" == s3:" + ("abc" == s3));

运行结果:

s1 == s2:true
s3 == s4:false
s1 == s3:false
"abc" == s1:true
"abc" == s3:false

2.2通过"equals()"进行比较

String类已经重写过equals()方法,使得可以比较String对象中的值

System.out.println("s1.equals(s2):" + (s1.equals(s2)));
System.out.println("s3.equals(s4):" + (s3.equals(s4)));
System.out.println("s1.equals(s3):" + (s1.equals(s3)));
//"abc"是一个String字符串对象,故可以调用equals方法
System.out.println("\"abc\".equals(s1):" + ("abc".equals(s1)));
//建议使用"abc".equals(s3)这种形式,可以避免空指针异常
System.out.println("\"abc\".equals(s3):" + ("abc".equals(s3)));

因为所有引用都指向常量池中的"abc",故都是true

运行结果:

s1.equals(s2):true
s3.equals(s4):true
s1.equals(s3):true
"abc".equals(s1):true
"abc".equals(s3):true

3. String类的常用方法

3.1 String类常用的构造方法

3.1.1 String s = "";

最常用

String s1 = "Hello";
//输出字符串对象的话,不会输出对象内存地址,输出的是字符串本身,因为String类已经重写了toString()
System.out.println("s1:" + s1);
s1:Hello

3.1.2 String s = new String("");

String s2 = new String("Hello");
System.out.println("s2:" + s2);
s2:Hello

3.1.3 String s = new String(byte数组);

//97->"a",98->"b",99->"c"
byte[] bytes = {97,98,99};
/*
String(字节数组)
将byte数组转换成字符串
 */
String s3 = new String(bytes);
System.out.println("s3:" + s3);
s3:abc

3.1.4 String s = new String(byte数组,起始下标,长度);

byte[] bytes = {97,98,99};
/*
String(byte数组,数组元素下标的起始位置,长度)
将byte数组里的一部分转换成字符串
 */
String s4 = new String(bytes,1,2);
System.out.println("s4:" + s4);
s4:bc

3.1.5 String s = new String(char数组);

char[] chars = {'字','符','串'};
/*
String(char数组)
将char数组转换成字符串
 */
String s5 = new String(chars);
System.out.println("s5:" + s5);
s5:字符串

3.1.6 String s = new String(char数组,起始下标,长度);

char[] chars = {'字','符','串'};
/*
String(char数组,起始下标,长度)
将char数组里的一部分转换成字符串
 */
String s6 = new String(chars,1,2);
System.out.println("s6:" + s6);
s6:符串

3.2 String类常用的实例方法

3.2.1 charAt

char charAt(int index)

返回字符串指定索引处的字符。

String s1 = "abc";
System.out.println(s1.charAt(1));
b

3.2.2 compareTo

int compareTo(String anotherString)

按字典顺序比较两个字符串。

换句话说就是将两个字符串中的每个字符按顺序挨个按各自的ASCII码进行相减。

如果前面的字符串等于后面的字符串,那么比较结果就为0,

如果前面的字符串大于后面的字符串,那么比较结果就为正数,

如果前面的字符串小于后面的字符串,那么比较结果就为负数。

System.out.println("字符a的ASCII码为:" + (int)'a');
System.out.println("字符A的ASCII码为:" + (int)'A');
System.out.println("字符c的ASCII码为:" + (int)'c');
System.out.println("字符d的ASCII码为:" + (int)'d');
System.out.println("字符i的ASCII码为:" + (int)'i');
//两个字符串中每个字符都相等,ASCII码也一样,故相减结果为0
System.out.println("abc".compareTo("abc"));//0
//拿前面的字符串中第一个字符'A'和后面的字符串中第一个字符'a'比较,如果分出胜负,后面的字符就不比了。
//'A'的ASCII码为65,'a'的ASCII码为97。
//拿65减去97,得-32。
System.out.println("Abc".compareTo("abc"));//-32
System.out.println("abc".compareTo("abd"));//-1
System.out.println("abd".compareTo("abc"));//1
System.out.println("iab".compareTo("abi"));//8

结果:

字符a的ASCII码为:97
字符A的ASCII码为:65
字符d的ASCII码为:99
字符e的ASCII码为:100
字符i的ASCII码为:105
0
-32
-1
1
8

3.2.3 compareToIgnoreCase

int compareToIgnoreCase(String str)

按字典顺序比较两个字符串,忽略字符大小写。

System.out.println("Abc".compareToIgnoreCase("abc"));

结果:

0

3.2.4 contains

boolean contains(CharSequence s)

判断前面字符串中是否包含后面的字符串,如果包含,返回true,否则返回false.

System.out.println("abcdef".contains("def"));
System.out.println("abcdef".contains("woc"));
true
false

3.2.5 startsWith&endsWith

boolean endsWith(String suffix)

判断当前字符串是否以指定的字符串结尾,是为true,不是为false。

System.out.println("HelloWorld.java".endsWith(".java"));
System.out.println("HelloWorld.java".endsWith(".c"));
System.out.println("abcdef".endsWith("ef"));
true
false
true

boolean startsWith(String prefix)

判断当前字符串是否以指定的字符串开头,是为true,不是为false。

System.out.println("http://www.baidu.com".startsWith("http://"));
System.out.println("http://www.baidu.com".startsWith("https://"));
true
false

3.2.6 equals

boolean equals(Object anObject)

将当前字符串与其他字符串进行比较。相同为true,不同为false

String s1 = new String("abc");
System.out.println("abc".equals(s1));
System.out.println("Abc".equals(s1));
true
false

3.2.7 equalsIgnoreCase

boolean equalsIgnoreCase(String anotherString)

将当前字符串与其他 String比较,忽略字符串中字符的大小写。

System.out.println("Abc".equalsIgnoreCase("abc"));
true

3.2.8 getBytes

byte[] getBytes()

将字符串转换成字节数组

byte[] bytes = "abc".getBytes();
int i = bytes.length - 1;
while (i >= 0) {
    System.out.println(bytes[i]);
    i--;
}
99
98
97

3.2.9 isEmpty

boolean isEmpty()

判断某个字符串是否为空,为空返回true,不为空返回false

String s2 = "";
String s3 = "a";
System.out.println(s2.isEmpty());
System.out.println(s3.isEmpty());
true
false

3.2.10 length()

int length()

返回字符串的长度

String s4 = "abc";
System.out.println(s4.length());
3

面试题:判断字符串长度和数组长度

String s4 = "abc";
System.out.println(s4.length());
String[] strings = {"a","b","c"};
System.out.println(strings.length);

判断字符串长度使用length()方法。

判断数组长度使用length属性。

3.2.11 indexOf

int indexOf(int ch)

返回指定字符在当前字符串内第一次出现处的下标。

System.out.println("abcdef".indexOf(97));
0

int indexOf(String str)

返回指定字符串在当前字符串内第一次出现处的下标。

System.out.println("abcdef".indexOf("def"));
3

int lastIndexOf(String str)

返回指定字符串在当前字符串内最后一次出现处的下标。

System.out.println("javaHellojava".lastIndexOf("java"));
9

3.2.13 valueOf

static String valueOf(boolean b)

将非字符串的元素转换成字符串。

String类里唯一的静态方法

int i = 100;
String str1 = String.valueOf(i);
System.out.println(str1);
100

当使用valueof转换一个对象时,底层会自动调用toString方法

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}
public class StringDemo04 {
    public static <Char, Chat> void main(String[] args) {
        String str2 = String.valueOf(new Cat("加菲猫"));
        System.out.println(str2);
    }
}
class Cat {
    private String name;
    public Cat() {
    }
    public Cat(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}
加菲猫

由此我们可以查看println打印的底层源码:

参数为int类型数字时:

public void println(int x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}
public void print(int i) {
    write(String.valueOf(i));
}

参数为对象时:

public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
}

可见,println方法底层调用了valueof方法,进而调用了toString方法。

所以说,System.out.println();输出任何数据时,都先转换成字符串,再打印。

3.2.14 substring

String substring(int beginIndex)

截取一个新的字符串,从下标beginIndex开始

String str1 = "abcdefg";
String str2 = str1.substring(2);
cdefg

String substring(int beginIndex, int endIndex)

返回一个新字符串,它是该字符串的一个子字符串。该字符串从指定的下标beginIndex开始,到下标endIndex-1处结束。前闭后开。

//前闭后开
System.out.println(str2);
String str3 = str1.substring(2,5);
System.out.println(str3);
cde

3.2.15 toCharArray

char[] toCharArray()

将此字符串转换为新的字符数组。

String str1 = "abcde";
char[] arr = str1.toCharArray();
for (char c : arr) {
    System.out.println(c);
}
a
b
c
d
e

3.2.16 replace

String replace(CharSequence target, CharSequence replacement)

替换指定字符串

String的父接口就是CharSequence

String newStr1 = "HelloWorld.txt".replace("txt","java");
System.out.println(newStr1);
String newStr2 = "时/分/秒".replace("/",":");
System.out.println(newStr2);
HelloWorld.java
时:分:秒

3.2.17 toLowerCase&toUpperCase

String toLowerCase()

将字符串转换成小写

String str1 = "asdfGHJK";
System.out.println(str1.toLowerCase());
asdfghjk

String toUpperCase()

将字符串转换成大写

String str1 = "asdfGHJK";
System.out.println(str1.toUpperCase());
ASDFGHJK

3.2.18 split

String[] split(String regex)

将字符串从指定字符串处拆分成一个字符串数组

String[] str1 = "2021-07-05".split("-");
for (String s : str1) {
    System.out.println(s);
}
String[] str2 = "id=007&userName=zhangsan&passWord=12345".split("&");
for (String s : str2) {
    String[] str3 = s.split("=");
    for (String s1 : str3) {
        System.out.println(s1);
    }
}
2021
07
05
id
007
userName
zhangsan
passWord
12345

3.2.20 trim

String trim()

去除字符串前后空白

System.out.println("      Hello      World.java       ".trim());
Hello      World.java

二、StringBuffer和StringBuilder

思考:我们在实际开发中,如果需要进行字符串的频繁拼接,会有什么问题?

因为java中String字符串是不可变的,所以每一次拼接都会产生新的字符串。

String str1 = "a";
str1 = str1 + "b";

这两句就能导致在方法区常量池里创建三个对象:"a"、"b"、"ab",且"a"、"b"不会消失,会变成未使用对象。

String str1 = "";
for (int i = 0; i < 100; i++) {
    str1 += i;
    System.out.println(str1);
}

以上代码块中,每循环一次,就会进行一次字符串的拼接,在常量池里就会生成新的未使用对象。当拼接次数过多时,会给常量池造成很大的负荷。

所以我们在进行频繁的字符串拼接时,为了避免这种情况,要使用到StringBuffer或StringBuilder。

1. StringBuffer

1.1 StringBuffer概述

与String类不同的是,StringBuffer类的对象能够被多次修改,且不会产生新的未使用对象。

StringBuffer底层实际上是一个byte[]数组,未被final修饰。

往StringBuffer中放字符串,实际上是放到byte[]数组里了。

StringBuffer的初始化容量是16。

StringBuffer是线程安全的。

1.2 StringBuffer的追加

1.2.1 StringBuffer的追加需要调用append()方法

//创建一个初始化容量为16的byte[]数组(字符串缓存区对象)
StringBuffer sb1 = new StringBuffer();
sb1.append("a");
sb1.append("b");
sb1.append("c");
sb1.append(100);
//append方法在进行追加时,如果byte数组满了,会自动扩容
sb1.append(true);
System.out.println(sb1);
String s = "abc";
abc100true

1.2.2 为什么说StringBuffer的追加不会产生新的未使用对象

String底层是一个byte[]数组,被final修饰。

  • 数组一旦创建长度不可变。

  • 被final修饰的引用一旦指向某个对象后,不可再指向其他对象。

  • String进行拼接操作时,底层数组引用无法指向新的数组,无法进行扩容操作,只能创建新的字符串对象。

  • 而旧的字符串对象不会被垃圾回收器回收,会造成空间上的浪费。

StringBuffer底层也是一个byte[]数组,但未被final修饰。

  • StringBuffer进行拼接操作时,底层数组引用可以指向新的数组。如果容量满了,可以进行扩容操作。
  • 当扩容完毕时,旧的数组可以被垃圾回收器回收,故不会产生新的未使用对象。

1.2.3 append的底层源码

以StringBuffer append(String str) 为例。

/**
 * StringBuffer中append方法
 */
@Override
//由synchronized修饰,线程不可并发,线程是安全的,但效率低
public synchronized StringBuffer append(String str) {
    toStringCache = null;
    //调用父类中的append方法
    super.append(str);
    return this;
}
/**
 * 父类AbstractStringBuilder中的append方法
 */
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;
}
/**
 * 确保容量方法
 */
private void ensureCapacityInternal(int minimumCapacity) {
    // 如果容量满了,则进行扩容
    if (minimumCapacity - value.length > 0)
        expandCapacity(minimumCapacity);
}
/**
 * 扩容方法
 */
void expandCapacity(int minimumCapacity) {
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    //调用Arrays类里的拷贝方法,将旧的数组和需要扩容的长度newCapacity传入
    value = Arrays.copyOf(value, newCapacity);
}
/**
 * Arrays类里的拷贝方法
 * 将旧数组中的数据拷贝到一个新的,长度扩容后的数组里
 */
public static char[] copyOf(char[] original, int newLength) {
    char[] copy = new char[newLength];
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

2.StringBuilder

StringBuilder与StringBuffer的区别在于:

  • StringBuffer由synchronized关键字修饰,是线程同步的,是线程安全的,但是效率慢。
  • 而StringBuilder未被synchronized修饰,不是线程安全的,但是效率高。

其他与StringBuffer一样。

posted @ 2021-07-06 11:49  TSCCG  阅读(56)  评论(0编辑  收藏  举报