• 《JavaSE-第十二章》之String


    博客主页:KC老衲爱尼姑的博客主页

    博主的github,平常所写代码皆在于此

    刷题求职神器

    共勉:talk is cheap, show me the code

    作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


    刷题求职神器

    在下给诸位推荐一款巨好用的刷题求职神器,如果还有小伙伴没有注册该网站,可以点击下方链接直接注册,注册完后就可以立即刷题了。

    在这里插入图片描述

    传送门:牛客网


    1.String概述

    String是Java中的引用类型,位于java.lang下,该类所定义的变量可用于指向字符串对象,然后来操作该字符串。

    String既然是一个类,那么可以从该类的属性以及构造方法出发,去认识该类。

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
    
    • 1
    • 2

    通过源码可知,String实现了三个接口,首先java.io.Serializable是一个空接口,作用就是标识该类,说明此类可以被序列化,Comparable接口是用于比较大小的接口,最后一个CharSequence接口,该接口是char值的可读序列, 该接口为其实现类提供统一的,只读访问许多不同类型的char序列。

    2.String类常用的构造方法

    String类提供了许多的构造方法,但是最常用有以下几种。

    	@Test
        public  void testString(){
            //使用常量串构造
            String  s  = "hello";
            System.out.println(s);
            //newString对象
            String s2 = new String("world");
            System.out.println(s2);
            //使用字符数组进行构造
            char [] arr ={'a','b','c'};
            String s3 = new String(arr);
            System.out.println(s3);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    通过源码可以看到String的底层是一个被private以及final修饰的字符数组

      private final char value[];
    
     private int hash; // Default to 0
    
    • 1
    • 2
    • 3

    通过调试也能证明底层确实是一个数组,只是它的组成部分还有hash。

    1.直接使用常量串构造详解

    在这里插入图片描述

    2.newString详解

    在这里插入图片描述

    3.使用字符数组进行构造详解

    在这里插入图片描述

    当传入字符数组时 ,底层会拷贝一份字符数组并将拷贝后数组的引用给字符串对象的value。

    传入字符数组时String的构造方法

        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
    
    • 1
    • 2
    • 3

    3.字符串方法

    String对象的比较

    字符串的对象的比较分为以下4中

    1.== 比较是否引用同一个对象

    	@Test
        public void testString2(){
            String s1 = new String("hmr");
            String s2 = new String("hmr");
            String s3 = new String("yzq");
            String s4 = s1;
            System.out.println(s1==s3);//fasle
            System.out.println(s1 == s2);//false
            System.out.println(s4==s1);//true
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.boolean equals(Object anObject) 方法:按照字典序比较

    String 重写了Object中的equals方法,因为Object中的equals方法默认按照==来比较,String类重写后会按照字典序来比较

    String重写后equals

     public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    示例

        @Test
        public void testString3(){
            String s1 = new String("hmr");
            String s2 = new String("hmr");
            String s3 = new String("yzq");
            String s4 = s1;
            System.out.println(s1.equals(s3));//false
            System.out.println(s1 .equals(s2));//true
            System.out.println(s4.equals(s1));//true
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.int compareTo(String s) 方法 按照字典序进行比较

    与equals不同的是,equals返回的是boolean类型,而compareTo返回的是int类型。具体比较方式:

    1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值

    2. 如果前k个字符相等(k为两个字符长度最小值),返回值两个字符串长度差值

    示例

        @Test
        public void testString4(){
            String s1 = new String("hmr");
            String s2 = new String("hmr");
            String s3 = new String("yzq");
            String s4 = s1;
            System.out.println(s1.compareTo(s2));//0
            System.out.println(s2 .compareTo(s3));//-17
            System.out.println(s4.compareTo(s1));//0
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较。

    示例

    	@Test
        public void testString5(){
            String s1 = new String("HMR");
            String s2 = new String("hmr");
            String s3 = new String("YZQ");
            String s5 = new String("yzq");
            String s4 = s1;
            System.out.println(s1.compareToIgnoreCase(s2));//0
            System.out.println(s2 .compareToIgnoreCase(s3));//-17
            System.out.println(s4.compareToIgnoreCase(s1));//0
            System.out.println(s5.compareToIgnoreCase(s3));//0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.什么是池?

    由于我们经常对这些字符串常量(常用资源)进行操作,而每次使用时都会开辟相应的内存,为了是程序运行的速度加快,就以空间来换时间,即事先将要频繁使用的资源放入空间中,当我们需要操作时直接从空间来拿使用就行了,这个空间就是池。这就好比张三家里没有冰箱,那么想吃冰棒得去小卖部,张三每天都出去觉得太麻烦了,于是自己买了个冰箱,在向冰箱里屯了许多冰棒,以后向吃就可以随时吃,就节约了大量的时间。

    4.1字符串常量池

    字符串常量池在JVM中是StringTable类,实际是一个固定大小的HashTable(数组+链表(val为字符串对象))。不同版本的jdk下的字符串常量池的位置和大小的是不同的再次讨论的是jdk8下的字符串常量池,jdk8中该池位于堆内存中,池的大小可以设置,其最小值是1009。

    4.2再谈String对象创建

    1.直接使用字符串常量进行赋值

        @Test
        public void testString6(){
           String s1 = "hello";
           String s2 = "hello";
            System.out.println(s1==s2);//true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.通过new创建String类对象

        @Test
        public void testString6(){
          String s3 = new String("hello");
          String s4 =new String ("hello");
          System.out.println(s3==s4);//false
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    当直接使用字符串常量进行赋值时,在加载字节码文件时,“hello”在字符串中已经创建好并保存在字符串常量池中,当代码走到String s1 = “hello”;创建对象时,会优先在字符串常量池中查找是否有该字符串,当找到了该字符串则将该字符串的引用赋值给s1,如果没有则创建新的字符串对象并入池。通过new创建的字符串类对象,首先会在堆内存开辟一个String对象,然后向字符串常量池中查找该字符是否存在,如若存在则将字符数组的引用赋值给字符串对象的value,反之则直接创建新的字符串对象。

    通过上述,可以得出new的对象是唯一,并且使用常量串创建的String类型对象的效率更高,更节约空间。

    4.3intern方法

    intern 是一个native方法(Native方法指:底层使用C++实现的,看不到其实现的源代码),该方法的作用是手动将创建的String对象添加到常量池中。

    代码示例

        @Test
        public void testString7(){
            char [] arr = new char[]{'a','b','c'};
            String s1 = new String(arr);
            String s2 = "abc";
            System.out.println(s1==s2);//false
        }
    
        @Test
        public void testString7(){
            char [] arr = new char[]{'a','b','c'};
            String s1 = new String(arr);
            s1.intern();
            String s2 = "abc";
            System.out.println(s1==s2);//true
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    当s1调用ntern()方法后,会将s1对象放入到字符串常量池,故s1==s2。

    5.字符串的不可变性

    String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:

    1. String类在设计时就是不可变的,
    2. String类中的字符实际是在内部的value字符数组中,通过源码可知String是被final修饰,不能被继承,同时把value被final以及private修饰表明value本身的值是不能修改的,也是就是不能引用其它数组,但是对于一个数组是可以通过下标访问修改其数组对应的值,而此时在String类外压根拿不到value故字符串不可变。
    3. 所以涉及到可能修饰字符串内容的操作都是创建一个新的对象,改变的新的对象,源码如下。
     public String replace(char oldChar, char newChar) {
            if (oldChar != newChar) {
                int len = value.length;
                int i = -1;
                char[] val = value; /* avoid getfield opcode */
    
                while (++i < len) {
                    if (val[i] == oldChar) {
                        break;
                    }
                }
                if (i < len) {
                    char buf[] = new char[len];
                    for (int j = 0; j < i; j++) {
                        buf[j] = val[j];
                    }
                    while (i < len) {
                        char c = val[i];
                        buf[i] = (c == oldChar) ? newChar : c;
                        i++;
                    }
                    return new String(buf, true);
                }
            }
            return this;
        }
    
    • 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

    6字符串修改

    错误的使用String进行拼接

    public class Test {
        public static void main(String[] args) {
            String s = "";
            for (int i = 0;i<10_000;i++) {
                s+= i;
            }
            System.out.println(s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    使用String进行字符串拼接效率极其低下,之所以速度慢,可以通过查看Test的汇编代码来究其本质
    在这里插入图片描述

    通过汇编得知每次进行字符串拼接时都会new一个StringBuilder对象,这也意味着会程序的运行速度是非常低下的,因此尽量不使用String直接拼接字符串,可以使用StringBuilder或者StringBuffer。

    7.StringBuilder和StringBuffffe

    由于String类型的字符串不可更改,为了可以高效的进行字符串修改,Java提供了StringBuffer和StringBuilder类,这两个类大同小异,这里就介绍几个常用的API更多的方法,更多需求自行查看 StringBuildre在线文档

    1.使用StringBuilder进行拼接字符串

        @Test
        public void testString9(){
            long start = System.currentTimeMillis();
            StringBuffer s = new StringBuffer("");
            for (int i = 0;i<10_0000;i++) {
               s.append(i);
            }
            long end = System.currentTimeMillis();
            System.out.println(end-start);//18
        }
    	
        @Test
        public void testString8(){
            long start = System.currentTimeMillis();
            String s = "";
            for (int i = 0;i<10_0000;i++) {
                s+= i;
            }
            long end = System.currentTimeMillis();//32139
            System.out.println(end-start);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    从上述代码的运行时间上来看,StringBulder比String速度快了n倍。

    2.反转一个字符串

        @Test
        public  void testStringBuilder(){
            StringBuilder s= new StringBuilder("hello world");
            System.out.println(s.reverse().toString());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意:String和StringBuilder类不能直接转换。如果需要转换可以采用一下方式:

    1. String转StringBuilder:利用StringBuilder的构造方法或者append()方法。
    2. StringBuilder变成Sring:调用toString()方法。

    8.面试题

    1,String、StringBuffffer、StringBuilder的区别

    • String的内容不可修改,StringBuffffer与StringBuilder的内容可以修改。

    • StringBuffffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操

      作。

    1. 以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】

    1.String str = “hello”;

    只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象(1个)

    2.String str = new String(“hello”)

    会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟

    的String对象赋值。(2个)

    3.String str = new String(new char[]{‘h’, ‘e’, ‘l’, ‘l’, ‘o’})

    先在堆上创建一个String对象,然后利用copyof将重新开辟数组空间,将参数字符串数组中内容拷贝到

    String对象中(三个)

    最后的话

    各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!。如果你想变强那么点我点我 牛客网

    在这里插入图片描述

  • 相关阅读:
    为什么调用父类构造函数,super 必须是构造函数内的第一条语句?
    设计模式之状态模式
    Android 13.0 无源码app修改它的icon图标
    Visual Studio App Center 中的 Bug 跟踪服务
    网络IP地址子网划分学习
    Redis实现Session持久化
    设计模式-Factory
    【定语从句练习题】 which 修饰句子
    【Web3 系列开发教程——创建你的第一个 NFT(2)】NFT 历史回溯
    01 Python进阶:正则表达式
  • 原文地址:https://blog.csdn.net/qq_60308100/article/details/126485531