java字符串及其特性和内置函数

32 阅读9分钟

字符串介绍

  • String类的构造方法原型

image.png

  • 字符串被创建后可以理解为是一个字符串对象,左边的引用只是当前字符串变量的管理者(或者叫指针对象),而变量类型(string)则被称为字符串的类,右边则为字符串对象,同时字符串也和数组一样是引用数据类型
//定义字符串的两种方式 
String h= "hello World";//更常用,这种定义方式可以称为“字面量”
String h = new String("hello world");//这是不是有点像数组的创建方法,没错字符串就是一种特殊的数组
  • 也可以通过传入字符串数组来创建字符串,和用字面量创建的方式是类似的
char[] chars = {'h', 'e', 'l', 'l', 'o'};
String str = new String(chars);
System.out.println(str); // 输出:hello
  • 我们也可以读取用户输入的字符串
//读取用户输入的字符串 
//读取空格、回车、\t(tab)之前的数据
String next = scanner.next(); 
//一整行(即为忽略空格和制表符tab)
String nextLine = scanner.nextLine();
  • 因为字符串也是一种特殊的数组,即也为引用数据类型,所以不可以用字符串的变量名(引用名)对比两个字符串是否相等进行比较,因为字符串引用名存放的实际上是字符串存放的地址,比较字符串名相当于比较字符串存放的地址,两个字符串存放的地址肯定是不同的,所以这样比较返还必为false
String hello2 = new String("Hello World");
String hello3 = new String("Hello World");
//对比hello2和hello3的内存地址,换句话说,是在对比,这两个变量指向的是不是同一个对象
System.out.println(hello2 == hello3);//false
  • 和数组一样java也为我们提供了对比两个字符串是否相等的方法
String hello2 = new String("Hello World");
String hello3 = new String("Hello World");
//使用hello2字符串对象中继承而来的equals方法
System.out.println(hello2.equals(hello3));//true
  • 同样像数组一样我们也不可以通过赋值字符串变量名来对字符串进行复制
String hello2 = new String("Hello World");
String hello4 = hello2;
System.out.println(hello2 == hello4);//true
//返回为true说明这两个数组变量指向了同一块内存空间,这显然是一个错误的复制操作

字符串常量池

  • 使用""去定义字符串,这种形式就会被java当成是常量,会被放在常量池当值
String hello5 = "Hello World";
String hello6 = "Hello World";
  • 同时JVM为了减少对于内存空间的浪费会将在常量池中的两个相同的字符串合并到一起,使得多个字符串的字符串名同时指向一块内存空间,如下方代码所示
String hello5 = "Hello World";
String hello6 = "Hello World";
System.out.println(hello5 == hello6); //true
//两个字符串变量是相等的说明它们引用的是同一块内存空间

两种创建字符串的区别

  • 而对于字面量创建,当你使用字符串字面量创建字符串时,例如 String s = "abc";,这个字符串实际上是存储在字符串常量池中的。字符串常量池是方法区内的一块内存区域,通常与类的其他信息一起存储。这种方式创建的字符串会检查字符串常量池中是否存在相同的字符串字面量,如果存在,则返回那个字符串的引用;否则,会在常量池中创建一个新的字符串。

image.png

  • 实际上使用new关键字的字符串的内容也是存放在常量池中的,当使用 new String("abc") 创建字符串时,尽管字符串字面量 "abc" 存储在字符串常量池中,但 new 关键字会导致在堆上创建一个新的 String 对象。这个对象包含一个指向常量池中 "abc" 的引用。这种方式不会检查字符串常量池中是否已经存在相同的字符串,总是会在堆上创建一个新的对象。

image.png

  • 当两个字符串拼接时,则会调用StringBuilder方法,将常量池StringTable中的字符串拼接,放入一个新的字符串对象中,所以拼接的字符串会新建一个新的字符串对象,但实际上如果是拼装单个字符,实际上是不会创建新的对象的 image.png
  • 但实际上如果是拼装单个字符,实际上是不会创建新的对象的,这种方式jvm会认为是通过字符数组(public String(char[] chs))来创建字符串的方式 image.png
  • 但是如果是单个字符的字符变量拼接,则还是会调用StringBuilder方法创建对象,如下方代码所示和图片所示
public class StringTest1{
    public static void main(String[] args){
        String s1 = "abc";
        String s2 = 'a';
        String s3 = 'b';
        String s4 = 'c';
        String s5 = s2 + s3 + s4
        System.out.println(s1 == s5);//Flse
    }
}

image.png

字符串可以修改吗

不能直接修改

  • 在java中字符串是不可以修改的,无论是字符串字面量创建,还是使用new关键字创建,因为这两种方法一旦创建字符串的值都会放入常量池中,因此在String类中就没有给我们修改字符串的方法

修改字符串的方法

方法一:强制转换为字符型数组

public class ModifyStringExample {
    public static void main(String[] args) {
        String str = "Hello";
        char[] charArray = str.toCharArray(); // 将字符串转换为字符数组

        // 修改第 2 个字符(索引为 1)
        charArray[1] = 'a';

        // 将字符数组转换回字符串
        String modifiedStr = new String(charArray);
        System.out.println(modifiedStr); // 输出 "Hallo"
    }
}

方法二:使用StringBuffer或StringBuilder类

  • StringBuffer和StringBuilder类是可变字符串类型,允许直接修改字符串字符
public class ModifyStringExample {
    public static void main(String[] args) {
        String str = "Hello";
        StringBuilder sb = new StringBuilder(str); // 将字符串转换为 StringBuilder

        // 修改第 2 个字符(索引为 1)
        sb.setCharAt(1, 'a');

        // 将 StringBuilder 转换回字符串
        String modifiedStr = sb.toString();
        System.out.println(modifiedStr); // 输出 "Hallo"
    }
}

方法三:使用字符串拼接

public class ModifyStringExample {
    public static void main(String[] args) {
        String str = "Hello";

        // 修改第 2 个字符(索引为 1)
        String modifiedStr = str.substring(0, 1) + 'a' + str.substring(2);

        System.out.println(modifiedStr); // 输出 "Hallo"
    }
}

字符串拼接的方法(各方法优劣势)

  • 可以直接使用加号拼接,也可以使用StringBuilder拼接,但这两个方法中StringBulider的性能和效率比加号拼接的方式要高的多,通过下面的代码举例可以看出StringBulider比直接使用加号拼接效率要高上不少:
public class StringBuilderDemo {
    // 通过加号的方式拼接
    private static void oldAppend() {
        long startTime = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < 100000; i++) {
            str += i;
        }
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }
    // 通过StringBuilder拼接
    private static void extracted() {
        long startTime = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 100000; i++) {
            sb.append(i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println(endTime - startTime);
    }

    public static void main(String[] args) {
        System.out.println("通过加号拼接");
        oldAppend();//4439
        System.out.println("通过StringBuilder拼接");
        extracted();//5
    }
}

image.png

  • 下面通过JVM内存图来分析一下有如此大的性能差距的原因
    • 通过加号的方式对字符串进行拼接,就像前面提到的拼接字符串,每次都会去创建一个StringBuilder对象,然后调用对象中的toString方法将字符串拼接好之后生成一个字符串对象,每进行一次拼接就要创建两个对象,n次就创建2*n个对象,时间复杂度和空间复杂度都为O(n) image.png
    • 而通过创建StringBuilder字符串再进行拼接,可以直接调用SringBuilder内部的方法从常量池中直接取字符串进行拼接,时间复杂度大幅下降为O(1)image.png
  • 除了StringBuilder还有StringBuffer方法,它自带线程锁(synchronized),是线程安全的方法
  • 如何选择
    • String是不可变字符串,每次修改都会产生新的字符串,所以它效率比较低,但它线程是非常安全的
    • StringBuilder的是可变字符串,它修改不产生新对象,效率最高,但它线程并不安全。
    • StringBuffer是可变字符串效率中的线程安全方法,自带锁(synchronized)

字符串内置函数(常用操作)

内置函数概念

  • 字符串被定义出来的时候可以说是一个String类的对象,每次创建时我们都要使用new String()来创建对象,在字符串对象中继承了类中的很多方法,我们称之为字符串的内置函数(字符串的常用操作),比如:
//这里就是使用hello2字符串对象从类中中继承而来的equals方法
System.out.println(hello2.equals(hello3));//true
//所以equals方法的原型为equals(String),equal方法的代码在String类中
//实际上换成这样也是可以的
System.out.printiln(hello3.equals(hello2));
//任然为True

字符串的常用内置函数

获取长度

  • 获取字符串长度
System.out.println(hello2.length());//11

获取位置

通过字符找下标
  • 查到返回下标,没有查到返回-1
  • 获取字符/字符串在字符串中第一次出现的位置(下标)
String hello2 = new String("Hello World");
System.out.println(hello2.indexOf('o'));//4
ystem.out.println(hello2.indexOf("wor"));//6
  • 那想要获得非第一次出现的位置要咋做,实际上只需要给indexof传入一个整型值告诉它从上一次找到的位置开始下一次查找,依次类推
String hello2 = new String("Hello World");
int temp = hello2.indexOf("o");
System.out.print(hello2.indexOf("o",temp+1));

//我们还可以实际一个循环,让此算法自动帮我们获取所有该字母”o“的位置(下标)
String hello2 = new String("Hello World");  
int temp;  
temp = hello2.indexOf("o");  
while(temp != -1){  
    System.out.println(temp);  
    temp = hello2.indexOf("o",temp+1);  
}
通过下标找字符
  • 通过下标找字符
System.out.println(hello2.charAt(4));//o
//当传入的下标越界会保存

替换操作

  • 替换操作并不会更改原先的字符串,而是会返回一个替换后的新字符串
String newHello2 = hello2.replace("ld","llld");
System.out.println(newHello2); //Hello Worllld

截取操作

  • 类型于pytho的切片操作,并不会改变原来的字符串而是返回一个新的字符串
String hello2 = new String("Hello World");
System.out.println(hello2.substring(3));  //lo World
System.out.println(hello2.substring(3,6)); //lo
System.out.println(hello2); //Hello World

转换大小写的操作

  • 转换大小写,并不会改变原来的字符串而是返回一个新的字符串
String hello2 = new String("Hello World");
System.out.println(hello2.toUpperCase());//HELLO WORLD
System.out.println(hello2.toLowerCase());//hello world
System.out.println(hello2);//Hello World

6ed2bcfdbe526e31710309a908aa557fc396eff7.jpg@1256w_888h_!web-article-pic.webp