首页 > 基础资料 博客日记

Java(八)——String类

2024-07-03 21:00:07基础资料围观234

本篇文章分享Java(八)——String类,对你有帮助的话记得收藏一下,看Java资料网收获更多编程知识

String类

C语言中没有专门的字符串类型,一般使用字符数组或字符指针表示字符串,而字符串的函数需要包含头文件才能使用,这种数据与方法分离的做法不符合面向对象的思想。

Java中提供了String类来表示字符串


String的构造及内存分布

构造

字符串的构造方式主要有三种:

  1. 常量串构造

    String str1 = "hello";
    
  2. 直接new String对象

    String str2 = new String("hello");
    
  3. 使用字符数组进行构造

    char[] ch = new char[]{'h', 'e', 'l', 'l', 'o'};
    String str3 = new String(ch);
    

内存分布

核心就是 字符串常量池

JDK6及以前,字符串常量池存放在在永久代(内存的永久保存区域)

JDK7之后,字符串常量池存放在

字符串常量构造字符串时,Java在字符串常量池中寻找该字符串的内存地址,找到则将地址赋值给变量,如果没有找到,则在常量池开辟空间并将这块空间赋值该字符串,将这块地址赋值给变量。

new构造字符串时,Java先在堆区开辟一块空间,存放String对象,然后在字符串常量池中寻找字符串的内存地址,找到则将该地址的引用传给堆区的对象,如果没有找到,则在常量池开辟空间并将这块空间赋值该字符串,将地址的引用传给堆区的对象,最后,将堆区对象的地址赋值给变量。

如下代码及内存分布:

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        String s4 = new String("hello");
    }

我们在IDEA中观察String的源码:

我们可以看到,String类中包含很多的成员变量,我们仅关注value数组即可,它存放的就是字符串常量池的某个字符串的地址,其内存分布简单表示如下图:

String str1 = new String("hello");

字符串非常重要,我们接下来介绍一下Java中字符串的常用方法。


常用方法

判等

字符串的判等区分两种:

  1. ==:判断是否引用同一个对象
  2. boolean equals():判断字符串的内容是否相等

字符串类型是引用类型,==判断两个字符串的地址,而equals()方法判断两个字符串的内容是否相等(无关地址)

我们看下面一段代码,体会==equals()的区别:

public class Test {

    public static void main(String[] args) {
        //new了三个不同的对象
        String str1 = new String("hello");
        String str2 = new String("HELLO");
        String str3 = new String("hello");

        System.out.println(str1 == str2);//不同对象的地址不同
        System.out.println(str1 == str3);//不同对象的地址不同
        System.out.println(str1.equals(str2));//两个字符串的内容不同
        System.out.println(str1.equals(str3));//两个字符串的内容相同

        System.out.println("=============");
        str2 = str1;//将引用变量str1的值赋给str2,此时str1和str2指向同一个对象

        System.out.println(str1 == str2);//指向同一个对象后,地址相同
        System.out.println(str1.equals(str2));//指向同一个对象,内容自然也相同
    }
}

打印观察:


我们再来看一段代码:

    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "HELLO";
        String str3 = "hello";
        
        System.out.println(str1 == str2);//直接指向了字符串常量池的不同字符串,地址当然不同
        System.out.println(str1 == str3);//直接指向了字符串常量池的同一字符串,地址相同
        System.out.println(str1.equals(str2));//内容不同
        System.out.println(str1.equals(str3));//内容相同

        System.out.println("=============");
        str2 = str1;

        System.out.println(str1 == str2);
        System.out.println(str1.equals(str2));
    }

打印结果是:

前后两次区别在于构造字符串的方式不同,第一次是使用new构造,第二次是直接使用常量串构造;

第一次不同对象指向字符串常量池的同一地址,==结果是false;而第二次相同的常量串构造,==结果是true,这刚好验证了我们上面介绍的内存分布情况。


比较

如下两种:

  1. int compareTo()
  2. int compareToIgnoreCase()

【compareTo】

前面介绍的equals()的返回值是boolean类型且只能判断是否相等,而compareTo()返回值是int类型,不同的比较结果返回不同特征的值。

compareTo()比较规则如下:

  1. 先按照字典序大小依次比较,如果出现不等的字符,直接返回两个字符的大小差值
  2. 如果前n个字符都相等(n为要比较的两个字符串的长度较小值),返回两个字符串长度差值
  3. 按照上面的比较规则,如果>就返回一个正数;如果<就返回一个负数;如果==就返回0

例如:

    public static void main(String[] args) {

        String s1 = new String("hello");
        String s2 = new String("Hello");
        String s3 = new String("zero");
        String s4 = new String("hello");

        System.out.println(s1.compareTo(s2));//第一个字符'h'与'H'比较,'h' > 'H',所以返回正值
        System.out.println(s1.compareTo(s3));//第一个字符'h'与'z'比较,'h' < 'z',所以返回负值
        System.out.println(s1.compareTo(s4));//所有字符依次比较,全部相等,返回0
    }


【compareToIgnoreCase】

compareTo()的区别是,compareToIgnoreCase()比较时忽略大小写,如:

    public static void main(String[] args) {
        String s1 = "HELLO";
        String s2 = "hello";

        System.out.println(s1.compareTo(s2));
        System.out.println(s1.compareToIgnoreCase(s2));
    }

注意:

  • compareTo()compareToIgnoreCase()比较时的主体都是调用方法的那个字符串而非传入的参数

查找

char charAt()

int indexOf()及其重载方法

int lastIndexOf()及其重载方法


【charAt】

charAt()方法传入一个int类型的值,这个值即下标值,它将返回指定下标位置的字符(char类型)

    public static void main(String[] args) {
        String s1 = "hello";
        System.out.println(s1.charAt(0));
        System.out.println(s1.charAt(1));
        System.out.println(s1.charAt(2));
        System.out.println(s1.charAt(3));
        System.out.println(s1.charAt(4));
    }


【indexOf及其重载方法】

  1. int indexOf(int ch):返回 ch 第一次出现的位置,没有则返回 -1

        public static void main(String[] args) {
            String s1 = new String("hello");
    
            System.out.println(s1.indexOf('h'));
            System.out.println(s1.indexOf('l'));
            System.out.println(s1.indexOf('i'));
        }
    

  2. int indexOf(int ch, int fromIndex):从fromIndex(下标值)位置开始找,返 ch 第一次出现的位置,没有找到则返回 -1

        public static void main(String[] args) {
            String s1 = "abcabcdabcde";
    
            System.out.println(s1.indexOf('c', 4));
            System.out.println(s1.indexOf('f', 0));
        }
    

  3. int indexOf(String str):返回 str 第一次出现的位置,没有找到则返回 -1

        public static void main(String[] args) {
            String s1 = new String("hello");
    
            System.out.println(s1.indexOf("ll"));
            System.out.println(s1.indexOf("lll"));
        }
    

  4. int indexOf(String str, int fromIndex):从fromIndex(下标值)的位置开始找,返回 str 第一次出现的位置,没有则返回 -1

        public static void main(String[] args) {
            String s1 = "abcabcdabcde";
            System.out.println(s1.indexOf("abc", 4));
            System.out.println(s1.indexOf("abcf", 2));
        }
    


【lastIndexOf】

与前面indexOf()的区别是:查找方向不同lastIndexOf()从后往前找

4种方式:

  • int lastIndexOf(int ch):从后往前找,返回 ch 第一次出现的位置,没有则返回 -1;
  • int lastIndexOf(int ch, int fromIndex):从fromIndex的位置开始从后往前找,返回 ch 第一次出现的位置,没有则返回 -1;
  • int lastIndexOf(String str):从后往前找,返回 str 第一次出现的位置,没有则返回 -1;
  • int lastIndexOf(String str, int fromIndex):从fromIndex的位置开始从后往前找,返回 str 第一次出现的位置,没有则返回 -1;
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "abcabcdabcde";

        System.out.println(s1.lastIndexOf('l'));
        System.out.println(s1.lastIndexOf('l', 2));
        System.out.println(s2.lastIndexOf("abc"));
        System.out.println(s2.lastIndexOf("abc", 6));
    }


转化

转化部分介绍:

  • 数值和字符串转化
  • 大小写转化
  • 字符串转数组
  • 格式化

【数值和字符串转化】

数值转化为字符串:

String String.valueOf()

字符串转化为数值:

包装类对应类型 包装类.valueOf(String str)

当然不是所有的转化都会成功

注意:需要使用类名调用valueOf()方法,这是因为valueOf()方法被static修饰

    public static void main(String[] args) {
        String s1 = String.valueOf(123);
        int i1 = Integer.valueOf("123");
        int i2 = Integer.parseInt("123");
        int i3 = Integer.valueOf("123");
        double d1 = Double.valueOf("12.2");
        System.out.println(s1);
        System.out.println(i1);
        System.out.println(i2);
        System.out.println(i3);
        System.out.println(d1);
    }

上图中穿插了一个parseInt()方法,其与valueOf()没有差别,观察valueOf()方法:

发现,valueOf()底层就调用了parseInt()方法


【大小写转化】

转大写:

String toUpperCase()

转小写

String toLowerCase()

public static void main(String[] args) {
    String s1 = "hello";
    String s2 = s1.toUpperCase();
    System.out.println(s2);
    System.out.println(s2.toLowerCase());
}


【字符串转数组】

char[] toCharArray()

    public static void main(String[] args) {
        String s1 = "hello";
        char[] chars = s1.toCharArray();

        for (char tmp : chars) {
            System.out.println(tmp);
        }
    }


【格式化】

String format()

需要了解占位符的知识,Java较少使用,但学过C语言的应该不陌生

    public static void main(String[] args) {
        String s1 = String.format("%d-%d-%d", 2024, 5, 31);
        System.out.println(s1);
    }


替换

String replace()

String replaceAll(String regex, String replacement):替换所有指定内容

String replaceFirst(String regex, String replacement):替换首个指定内容

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = s1.replace('l', 'o');
        String s3 = s1.replace("ll", "oo");
        String s4 = s1.replaceAll("l", "o");
        String s5 = s1.replaceFirst("ll", "oo");
        
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s4);
        System.out.println(s5);
    }


拆分

  1. String[] split(String regex):以regex为分割符拆分字符串,返回一个字符串数组

  2. String[] split(String regex, int limit):将字符串以指定的格式,拆分为limit


String[] split(String regex):以regex为分割符拆分字符】

    public static void main(String[] args) {
        String s1 = "hello world!";
        String[] strings1 = s1.split(" ");
        for (String tmp : strings1) {
            System.out.print(tmp + " ");
        }
        //换行
        System.out.println();
        //检验分割后返回的数组
        System.out.println(Arrays.deepToString(strings1));
    }

如果有多个分割符可以使用|,如:

public static void main(String[] args) {
    String s1 = "I am@OK!";
    String[] strings = s1.split(" |@");
    System.out.println(Arrays.deepToString(strings));
}


String[] split(String regex, int limit):将字符串以指定的格式,拆分为limit组】

    public static void main(String[] args) {
        String s1 = "i am a bit";
        String[] strings = s1.split(" ", 2);//仅被分为两组
        System.out.println(Arrays.deepToString(strings));
    }


有些特殊字符作为分割符可能无法正确切分,需要加上转义

  • 字符|*,+.都得加上转义字符,即\\
  • 如果想以\为分割符,就得写成\\\\;单独的\不能出现在字符串中,必须以\\表示\
  • 如果一个字符串中有多个分割符,可以使用|作为连字符,上面演示过了
    public static void main(String[] args) {
        String s1 = "2024.5.31";
        String[] strings1 = s1.split("\\.");
        System.out.println(Arrays.deepToString(strings1));

        String s2 = "2024+5+31";
        strings1 = s2.split("\\+");
        System.out.println(Arrays.deepToString(strings1));

        String s3 = "2024\\5\\31";
        strings1 = s3.split("\\\\");
        System.out.println(Arrays.deepToString(strings1));
    }


截取

String substring(int beginIndex):从beginIndex下标位置截取到结尾

String substring(int beginIndex, int endIndex):从beginIndex下标位置截取到endIndex - 1下标位置,也就是说[beginIndex, endIndex)


    public static void main(String[] args) {
        String s1 = "hello world!";
        String s2 = s1.substring(6);
        System.out.println(s2);

        String s3 = s1.substring(6, 11);
        System.out.println(s3);
    }


【补充】

String trim():去掉字符串中的左右空格,保留中间空格

    public static void main(String[] args) {
        String s1 = new String("  hello world   ");
        String s2 = s1.trim();
        System.out.println(s2);
    }


字符串的不可变性

String是一种不可变对象。字符串不可修改,我们从String类源码观察:

如图:

  • 第一个final表示String类不能被继承;

  • 第二个final表示value不能指向新的对象,但是其内容可以改变,我们可以验证这一点:

    public static void main(String[] args) {
        final String[] strings = new String[]{"hello", " world", "!"};
        //修改内容,不报错
        strings[1] = "bit";
        
        //修改strings指向的对象,报错!
        strings = new String[10];
    }


  • 真正决定String不可被修改的是蓝框里的private修饰符,valueprivate修饰,意味着value只能在当前的String类中使用,而String类没有提供valuegetset方法,所以我们没有办法拿到value值,自然就不能修改字符串了。反过来,只要给value提供getset方法,我们就可以修改字符串了。

基于字符串的不可变性,我们补充一点注意点:

上面我们介绍的所有的方法,都不是在原字符串上进行操作的,都是重新创建了一个新的字符串返回


【字符串的追加(拼接)操作】

使用+即可

    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = s1 + " world";
        System.out.println(s2);
    }

此操作容易被误认为修改了字符串,其实并不是。

这段代码实际上如此:

    public static void main(String[] args) {
        
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("hello");
        stringBuilder.append(" world");
        String s2 = stringBuilder.toString();
        System.out.println(s2);
    }

并不是在原字符串上修改的,而是使用了StringBuilder类,这是个什么类呢?我们接下来讲!


StringBuilder和StringBuffer

Java中字符串的不可变性使得字符串的操作要不断地new新的对象,使得便捷性与效率并不理想。

为了方便字符串的修改,Java提供了StringBuilderStringBuffer两个类String调用这两个类的构造方法或append方法即可实现转化,这两个类中有非常多的操作字符串的方法(包含String类方法并比String类更丰富),如上面展示过的append()追加方法、reverse()反转方法,调用这些方法时无需创建新对象,直接在它们的类对象上操作即可,若要转换成字符串,只需调用toString()方法并接收即可

我们演示一下:

    public static void main(String[] args) {
        //创建StringBuilder类对象
        StringBuilder stringBuilder = new StringBuilder("hello");
        //直接操作
        stringBuilder.reverse();
        //转换为字符串
        String str = stringBuilder.toString();
        //打印
        System.out.println(stringBuilder);
        System.out.println(str);
    }

以后我们想要对字符串执行追加操作时,要尽量使用StringBuilderStringBuffer两个类的追加方法,它们的运行效率更高!

StringBuilderStringBuffer类与String类最大的区别就是String类的内容无法修改,而StringBuilderStringBuffer的内容可以修改。


StringBuilderStringBuffer的区别】

  • StringBuffer采用同步处理,属于线程安全操作,用于保障多线程安全
  • StringBuilder未采用同步处理,属于线程不安全操作,用于单线程

那么,我们以后都使用StringBuffer?

不是的。虽然StringBuffer可以保证多线程安全,但是相比StringBuilder多了一把"锁",“开锁” 和 “上锁” 也是会有损耗的,其效率会较低。对于现在的我们,只需要记住,具体场景具体分析,合理选择。


以上就是对 String的所有介绍了
接下来就是异常了,异常结束后,JavaSE的语法部分就基本结束了,后续会将所有的知识整合在一起,发一篇SE语法全集


文章来源:https://blog.csdn.net/xiaokuer_/article/details/139379515
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云