天天看点

《JavaSE-第十二章》之String

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

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

​​刷题求职神器​​

共勉:talk is cheap, show me the code

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

刷题求职神器

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

《JavaSE-第十二章》之String

传送门:​​牛客网​​

文章目录

  • ​​1.String概述​​
  • ​​2.String类常用的构造方法​​
  • ​​3.字符串方法​​
  • ​​4.什么是池?​​
  • ​​4.1字符串常量池​​
  • ​​4.2再谈String对象创建​​
  • ​​4.3intern方法​​
  • ​​5.字符串的不可变性​​
  • ​​6字符串修改​​
  • ​​7.StringBuilder和StringBuffffe​​
  • ​​8.面试题​​

1.String概述

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

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

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

通过源码可知,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);
    }      

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

private final char value[];

 private int hash; // Default to 0      

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

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

《JavaSE-第十二章》之String

2.newString详解

《JavaSE-第十二章》之String

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

《JavaSE-第十二章》之String

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

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

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

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
    }      

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

示例

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

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
    }      

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
}      

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
}      

2.通过new创建String类对象

@Test
    public void testString6(){
      String s3 = new String("hello");
      String s4 =new String ("hello");
      System.out.println(s3==s4);//false
    }      
《JavaSE-第十二章》之String

当直接使用字符串常量进行赋值时,在加载字节码文件时,“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
    }      

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

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

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

《JavaSE-第十二章》之String

通过汇编得知每次进行字符串拼接时都会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);
    }      

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

2.反转一个字符串

@Test
    public  void testStringBuilder(){
        StringBuilder s= new StringBuilder("hello world");
        System.out.println(s.reverse().toString());
    }      

注意: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对象中(三个)