java字符串String详解,字符串常量池

系列文章目录

本文主要详细介绍java当中String类的使用和底层原理,字符串常量池的相关知识。

一、什么是String类

String是一个固定长度的字符串,对String进行操作,是创建新的对象(String的操作都是改变赋值地址而不是改变值操作)。

而和它相似的还有一个字符串StringBuffer,对StringBuffer进行操作,这是在原来的对象之上进行改变(StringBuffer的操作都是改变值操作)。

String是一种不高效的字符串使用方式,在要求执行效率的系统当中会更多的使用StringBuffer来代替,这样可以降低系统的繁复性。

二、String的常用方法

int length();

语法:字符串变量名.length();  返回值为 int 类型。得到一个字符串的字符个数(中、英、空格、转义字符皆为字符,计入长度)

public void getLength(){
        String str="abcde";
        System.out.println(str.length());//5
    }

char charAt(int index)

返回 char指定索引处的值。

 public void getChar(){
        String str="abcde";
        System.out.println(str.charAt(2));//c
    }

int compareTo(String anotherString)

按字典顺序比较两个字符串。
如果String对象按字典顺序排列在参数字符串之前,结果为负整数。 结果是一个正整数,如果String对象按字典顺序跟随参数字符串。 如果字符串相等,结果为零; compareTo返回0 ,当equals(Object)方法将返回true 。

public static void testCompare(){
        String str1="abcde";
        String str2="bcd";
        System.out.println(str1.compareTo(str2));//-1
        String str3="abc";
        String str4="abc";
        System.out.println(str3.compareTo(str4));//0

        System.out.println(str2.compareTo(str1));//1
    }

public String concat(String str)

将指定的字符串连接到该字符串的末尾。
如果参数字符串的长度为0 ,则返回此String对象。 否则,返回一个String对象,表示一个字符序列。

public static void testConcat(){
        String str="abc";
        System.out.println(str.concat("def"));//abcdef
    }

public boolean contains(CharSequence s)

当且仅当此字符串包含指定的char值序列时才返回true

 public static void testContains(){
        String str1="abcd";
        System.out.println(str1.contains("ab"));//true
    }

boolean endsWith(String suffix)

测试此字符串是否以指定的后缀结尾。

equals(Object anObject)

将此字符串与指定对象进行比较。(很重要,后期源码讲解重点)

byte[] getBytes()

使用平台的默认字符集将此 String编码为字节序列,将结果存储到新的字节数组中。

public int hashCode()

返回此字符串的哈希码。 String对象的哈希代码计算为
s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1]
使用int算术,其中s[i]是字符串的第i个字符, n是字符串的长度, ^表示取幂。 (空字符串的哈希值为零)

public int indexOf(int ch)

返回指定字符第一次出现的字符串内的索引。 如果与值的字符ch在此表示的字符序列发生String第一事件发生之对象,则索引(在Unicode代码单元)被返回。

public static void testIndex() {
        String str1 = "abcd";
        System.out.println(str1.indexOf("ab"));//0
        System.out.println(str1.indexOf(98));//输出1,98就是'b'
        System.out.println(str1.indexOf('b'));//1
    }

public String[] split(String regex)

将此字符串拆分为给定的regular expression的匹配。
该方法的工作原理是通过使用给定表达式和限制参数为零调用双参数split方法。 因此,尾随的空字符串不会包含在结果数组中。

 public static void testSplit(){
        String str="ab,fff,4,8";
        String[] split = str.split(",");
        for (String s : split) {
            System.out.print(s+" ");//ab fff 4 8 
        }
    }

public String substring(int beginIndex int endIndex)

返回一个字符串,该字符串是此字符串的子字符串。 子串开始于指定beginIndex并延伸到字符索引endIndex - 1 。 因此,子串的长度为endIndex-beginIndex 。

public static void testSubString(){
        String str="abcdefg";
        System.out.println(str.substring(1, 4));//bcd
    }

目前,关于字符串的API先总结这么多,剩下的关于toString()方法,valueOf()方法,这些也很重要,读者可以自己尝试看看。这些都在之后的源码分析中看到。

三、String的内部源码

构造器

终于开始分析String类的源码了,首先从构造器开始,String的构造器那么多,都做了哪些事呢?还有String.valueOf()这个方法跟构造器有什么关联呢?截取一部分

 public String() {
        this.value = "".value;
    }

   
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

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

  
    public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

会发现,最核心的是value,不管传进去是字符串还是什么,都会赋值给value,那么value到底是什么呢?

 private final char value[];

原来value是个数组,还是个char类型的,还是final修饰的,怪不得不可变,一旦对他赋值了,后期就不能改变了,但问题是我传的是个字符串,跟字符数组有什么关系呢?

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

这里传入的是个字符串,原来要得到它的value值,也就是传入的字符串字面量就是个对象。那么这个字面量对象什么时候生成的?我怎么没印象呢?后期会讲字符串常量池。

先来讲讲常用的方法吧,我大概能猜到了,都是对这个数组进行操作,不管是查找也好,分割也好,都得遍历,仔细想想列举的那几个方法,不都是对字符串进行操作吗?拼接什么的。

public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

每一次操作都要生成一个字符串,原因就是value数组final修饰,必须生成一个新的char[]数组。

equals()

其他的方法都差不多,接下来讲讲equals()方法,首先equals()方法在哪个类中的?String类重写了它。

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;
    }

仔细看都能看懂,这个方法值得学习,内部先判断是不是String类的对象,否则免谈,长度不同也免谈,剩下的就是一个一个字符比较了。

hashcode()方法

得到一个数字,关于它,我更想在集合中讲,还是看看它的源码吧。

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

计算过程怎么不好理解呢,不用管它怎么计算的,只需要知道为什么要这么算,目的就是得到两个不同内容的字符串最后返回的数值尽可能不同(不同的内容得到的数字会一样吗?读者可以去考虑一下这个问题,也可以自己设计一个hash方法),这一块更想在集合中讲,尤其是关于hashMap,喜欢的可以关注我呀,后期更新。

四、String的字面量和字符串常量池

从这里开始,字符串就算入门了,接下里讲的很重要,先从几个代码入手。

public static void main(String[] args) {
        System.out.println(getStr() == getStr2());
    }

    public static String getStr(){
        String str="abc";
        return str;
    }

    public static String getStr2(){
        String str2="abc";
        return str2;
    }

字符串常量池

猜猜这个结果,是什么,答案是true。有的同学会问了,老师说比较字符串用equals()比较的是内容,==比较地址,这两个字符串怎么可能一样呢?首先不在同一个方法里,其次连名字都不一样,不管怎么看都不相同啊!问到这里我就有很多话说了,比较字符串永远不要去看在哪个方法创建,要看字符串常量池,那么什么是堆呢,什么是常量池。
简单的说,堆就是创建对象的地址,包括数组等等,字符串也是一个对象哦。那么常量池呢?里面也是地址,跟堆很像,但是存放字面量。记住三个字,跟常量池挂钩的是字面量(其实可以手动创建,后面会讲)。也就是在两个不同的方法里创建的两个字符对象,其实都是同一个,准确的说,只创建了一个,第二字直接返回了那个地址。字符串常量池,当str="abc"时,首先会去池子里面找,找到这个"abc"直接返回,找不到怎么办?创建一个,再返回。那么第二次str2="abc"怎么办呢?去找,找到了!直接返回。==比较的是地址,地址都一样当然相等了。

堆呢?是什么情况?我改一下代码

public static void main(String[] args) {
        System.out.println(getStr() == getStr2());
    }

    public static String getStr(){
        String str=new String("abc");
        return str;
    }

    public static String getStr2(){
        String str2=new String("abc");
        return str2;
    }

这样就返回false了哦!原因是两次new 都是在堆中生成的对象,地址都不一样,什么时候生成在池子里,记住三个字,字面量
那么一个new ,一个字面量,结果会相等吗?当然不相等了!

public static void main(String[] args) {
        System.out.println(getStr() == getStr2());
    }

    public static String getStr(){
        String str="abc";
        return str;
    }

    public static String getStr2(){
        String str2=new String("abc");
        return str2;
    }

返回false哦!

说到这里有的小伙伴悟了,有的小伙伴懵了。来几副图,好好理解一下。
String str1=“abc”; String str2=“abc”;str1 ==str2,结果是true;
在这里插入图片描述

那么String str1=“abc”;String str2=new String(“abc”);str1 == str2返回的是什么?
在这里插入图片描述
那两次都是new呢?当然是false了。
在这里插入图片描述
到这里有的小伙伴悟了。没懂的私聊我。接下来讲讲更难的。

五、String的Intern方法

当调用intern方法时,如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自池的字符串。 否则,此String对象将添加到池中,并返回对此String对象的引用。

 public static void main(String[] args) {
        String str1="abc";
        String str2=new String("abc");
        System.out.println(str1 == str2);//false
        System.out.println("===================");
        System.out.println(str1== str2.intern());
    }

输出是什么呢?

false
===================
true

有的小伙伴说了,我懂了,intern()方法就是返回常量池中的对象,没错,但他也可以创建的哦!关于这个创建稍后再说,补一个知识点,new String(“abc”);创建了几个对象?回答一个的正常,回答两个的还不错,其实得看情况。可能是一个,也可能是两个。一个的情况是:常量池中存在这个字面量,只需堆中创建;两个的情况是常量池中没有这个字面量,得在池子中创建对象,同时堆中也得创建。
小伙伴问了,这不是多此一举吗?new的字符串对象肯定不相同。没错啊,确实不是同一个,但是,没准这个字符串字面量以后经常用呢?
好了,既然了解到new会产生两个对象,就应该明白我为什么不演示intern()在常量池中创建对象的示例了,我怎么才能不在池子中创建这个字面量呢?直接字面量赋值,肯定会有,new String(“abc”)也会有,那咋办?
还记得String的构造方法吗?

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

new String(“abc”);中的"abc"是不是字面量?这也就是为什么用new String("")会可能产生两个对象,我只要不提供字面量就行了呗。

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

这样不就好了吗?
来试试intern的另一个作用,创建对象,在常量池中。

public static void main(String[] args) {
        char []chs={'a','b','c'};
        String str1=new String(chs);
    }

此时只有一个对象哦!在堆中。那我调用intern()方法,生成一个对象,在池子里。这个对象的地址是哪个呢(分jdk版本)?二选一:1.自己生成一个2.引用堆中的引用,也就是str1的地址。那测试一下。

 public static void main(String[] args) {
        char []chs={'a','b','c'};
        String str1=new String(chs);

        str1.intern();//在常量池中生成了一个对象,是str1引用的地址,还是单独生成的?
        String str2="abc";
        if(str2 == str1) System.out.println("常量池中生成的对象是str1的地址");
        else{
            System.out.println("单独生成了一个对象,跟堆中的地址没关系");
        }
    }
常量池中生成的对象是str1的地址

答案明确了,intern()的两个作用都清晰了吧。(读者自行用jdk1.6测试该代码,作者是用jdk1.8)

六、字符串拼接的问题

String str1="abc"+"def";

像这种情况会编译优化,等于str1=“abcdef”;
一旦有了引用就没那么智能了

String str1="abc";
String str2=str1+"def";

这种情况其实内部是生成了StringBuilder。

StringBuilder sb=new StringBuilder();
StringBuilder def = sb.append(str1).append("def");
str2=def.toString();

提问一下,字符串常量池中存在"abcdef"这个对象吗?

public String toString() {
        // Create a copy, don't share the array
return new String(value, 0, count);
    }

没有哦!
再来个题目

String res=new String("abc")+new String("def");

这里生成了几个对象?
首先new 我可以肯定是两个,总共就有四个了(不懂的看前面关于new的创建),
字符串拼接,生成一个StringBuilder对象,就五个了,调用toString()方法时,生成了一个对象(不懂的看前几行)。

总结

后期想让我添加的再私聊我,哪里不懂的也可以私聊我,有错误信息也可以私聊我改正。
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值