【JAVA SE】java中的String类

【JAVA SE】java中的String类,第1张

目录

String类

什么是String类?

String类的定义方式

String的内部

如何求字符串长度呢?

String类对象的比较

字符串查找

总结charAt

字符串转化

数字与字符串之间的转化

 总结:

字符串常量池

intern方法

一道面试题:

String的不可变性

字符串修改

StringBuffer和StringBuilder

经典面试题


String类 什么是String类?

我们无论是在做oj题还是在学习编程语言时都会遇到字符串,在c语言中我们用字符指针或者字符数组来定义字符串,而在java这门编程语言中专门为它定义了一种类型,我们叫做string类,利用双引号引起来的就是字符串。既然是String类那肯定通过这个字符串调用很多方法,没错在java标准库中,有很多方法,我们可以调用。

String类的定义方式

string定义字符串有几种常见的定义方式:

String str1 = "hello";

第一种方式我们可以直接字符串"hello"赋给str。

String str2 = new String("world");

由于String类型是引用类型,在这里我们可以通过new关键字定义字符串

char[] chars = {'c','s','d','n'};
String str3 = new String(chars);//将字符数组组装成字符串

我们还可以讲一个字符数组组装成一个字符串。

String的内部

在java中String是一种很特殊的类型,我们来看一下它的内部是怎么构造的。看一下源码是怎么实现的???

根据源码的实现可知:String是由value这个字符数组和hash值组成。

 我们也可以根据调试看String类型的构造:

 从这里能看出String类型是由字符数组和hash值组成,也能看出在Java中没有像c语言那样以'\0'结尾,在Java中是根据求字符串长度来知道字符串的结尾。

我们也可根据String在内存中的图来理解String类型:

这个str1指向value,value又指向这个字符数组。

如何求字符串长度呢?

 在Java中求字符串长度格式: 字符串对象的引用.length(); 

 当然我们也可以这样求,利用双引号引起来的就是一个对象,在Java中一切皆对象。

String类对象的比较
  • 根据==比较
    public static void main(String[] args) {
        int a =1;
        int b =1;
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        String str4 = new String("hello");
        System.out.println(a==b);
        System.out.println(str1==str2);
        System.out.println(str1==str3);
        System.out.println(str3==str4);
    }

这段代码都会打印出什么呢???

是不跟你心里的答案不太一样为啥会打印这样的结果??

首先第一个 是两个int类型进行比较,因为==两边都是基本数据类型也就是int类型,两个值相等答案就是true毋庸置疑。

我们先说第3个,发现str1和str3的内容都是hello,结果却是false,为啥呢??答案就是因为String是引用类型,当通过关键字new实例化的时候他就会产生一个新的对象,所以str1和str3存放的地址不同所以结果为false,可知==是比较左右两个对象的地址。

我们再说第4个,这个有了第3个基础上就更好回答了,因为两个对象都要new,都要产生新的对象,索引两个引用存放地址是不同的,所以结果为false。

我们来说第2个,这个是因为有个叫字符串常量池捣的鬼,第一次str1放的是hello,hello要存放在字符串常量池当中,当我们再一次对象里放的是hello时,它会检查字符串常量池里面有没有hello,如果有他就会将这个对象的引用指向str1,所以这就是String的特殊性,在字符串常量池中只能存在一份。所以str1和str2引用的是同一个对象,地址相同,答案就是true。

总结:

如果==两边是基本数据类型,比较的是是否相同。

如果==两边是引用类型,比较的是地址是否相同。


  • 根据equals比较字符串
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        String str4 = new String("hello");
        String str5 = "world";
        System.out.println(str1.equals(str2));
        System.out.println(str1.equals(str3));
        System.out.println(str3.equals(str4));
        System.out.println(str3.equals(str5));
    }

我们可以发现只要引用所指向内容一样,答案就是true。引用所指向的内容不一样,答案就是false。

在这里还要注意一下,equals是依据类型进行比较,比如是比较两个String类型的字符串,那他就会重写String类型的equals方法。如果是object那就会重写object的equals方法根据地址比较。

总结: 

equals比较字符串的时候比较的是字符串的内容,字符串内容相同,返回结果就是true,反之为false。

  • 根据compareTo比较两个字符串

我们之前学过一个接口是comparable接口,这个接口就要重写compareTo方法,我们知道compareTo是比较两个字符串的大小,依据字典序比较。

    public static void main(String[] args) {
        String str1 = "hellb";
        String str2 = "hella";
        String str3 = "world";
        String str4 = "hellb";
        System.out.println(str1.compareTo(str2));
        System.out.println(str1.compareTo(str3));
        System.out.println(str1.compareTo(str4));
    }

 我们可以知道根据compareTo比较,比较的是两个字符串的大小,从第一个字符一个一个按照字典序进行比较,如果字符串1大于字符串二返回大于0的数组,这个数字就是两个字符间的间距,如果小于0,返回的是两个字符间的负间距,也就是返回小于0的数字,当这两个字符串相等的时候返回0.

此外还有一个compareToIgnoreCase:

这个compareToIgnoreCase是忽略大小写进行比较两个字符串

比如:

    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "HELLO";
        System.out.println(str1.compareToIgnoreCase(str2));
    }


总结:

compareTo比较的是两个字符串的大小,从第一个字符按照字典序进行一一比较。

而compareToIgnoreCase是忽略两个字符串的大小写进行比较。


字符串查找

在java中标准库为了开发更方便,实现了很多有关字符串方法,我们接下来讲一讲字符串查找。

  • charAt(int index)

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

charAt(下标),是拿到某个下标的字符。

 当你设置的下标超出范围就会报出异常;

总结charAt

charAt(int index)方法是拿到下标index的字符。当设置的下标超出范围就会报异常。


  • indexof 
int indexOf(int ch)返回ch第一次出现的位置,没有返回-1
int indexOf(int ch, int
fromIndex)
从fromIndex位置开始找ch第一次出现的位置,没有返回-1
int indexOf(String str)返回str第一次出现的位置,没有返回-1
int indexOf(String str, int
fromIndex)
从fromIndex位置开始找str第一次出现的位置,没有返回-1
  • 第一个:indexOf(int ch) 返回第一次ch出现的位置,如果没有找到返回-1

  • 第二个 :indexOf(int ch,int fromIndex)从fromIndex位置开始寻找字符ch第一次出现的位置

从fromIndex的位置开始寻找字符ch,如果找到了返回下标,如果没有找到返回-1, 

  • 第三个:indexOf(String str) 找到了str返回str的第一个字符的下标,没有找到返回-1;


  • lastIndexOf
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 str = "Hello,programmer";
        System.out.println(str.lastIndexOf('p'));//从字符串最后位置开始找字符p
        System.out.println(str.lastIndexOf("pro"));//从字符串最后位置开始找字符串pro
        System.out.println(str.lastIndexOf('p',14));//从14的位置往前找字符p
        System.out.println(str.lastIndexOf("pro",4));//从4位置往前找字符串pro
        System.out.println(str.lastIndexOf("pr",10000));//超出范围就会从最后位置寻找
    }


字符串转化 数字与字符串之间的转化
  • 数字转字符串 ->利用Stirng.valueOf(int  x)

  •  字符串转整数   1.Integer.valueOf   2.Integer.parseint

  •  大小写转换     大写转小写 -> toLowerCase()    小写转大写->toUpperCase()

  •  字符串转数组->toCharArray()

  • 数组转字符串 ->toStirng
  •  字符串替换->replace

  • 字符串拆分 ->split

  • 分为多组拆分

  • 分为多次拆分 
  • split如果有特殊字符需要转义

  •  字符串截取 subString

  • 去除字符串中的左右空格->trim

  •  总结:

根据上面分析字符串String类的方法可以发现,只要我们对字符串进行 *** 作都不是在字符串本身 *** 作而是又创建了一个新的对象。


字符串常量池

我们先思考一下这段代码:

    public static void main(String[] args) {
        String str1 = "hello,world";
        String str2 = new String("hello,world");
        String str3 = "hello,world";
        System.out.println(str1==str2);
        System.out.println(str1==str3);
        System.out.println(str2==str3);
    }

 让我们来分析一下为啥会出现这样的结果呢???

我们回忆一下==是判断什么来着???

没错,如果==左右两边是基础数据类型判断的就是值的大小,如果==左右两边是引用类型的他就判断的是地址是否相等。

1.str1==str2显然这里由于str2要new一个对象,所以会重写创建一块内存空间,地址会不一样,答案就为false。同理str2与str3判断相等也是与1类似的。

2.str1==str3,为啥这里答案为false呢??不是说String是引用类型么,都会new一个对象么??

答案为啥会是这样的呢??这就与我们的字符串常量池相关了。

什么是字符串常量池呢??

字符串常量池StringTable以前存放在方法区中,而现在java版本更新把字符串常量池放在了堆中,字符串常量池底层是由哈希表来实现的,用来存储字符串常量(也就是被双引号引起来的字符串)的地址,目的是为了避免频繁的创建和销毁对象而影响系统性能,它也实现了对象的共享,节省内存空间。

1. 在JVM中字符串常量池只有一份,是全局共享的
2. 刚开始字符串常量池是空的,随着程序不断运行,字符串常量池中元素会越来越多
3. 当类加载时,字节码文件中的常量池也被加载到JVM中,称为运行时常量池,同时会将其中的字符串常量保存在字符串常量池中
4. 字符创常量池中的内容:一部分来自运行时常量池,一部分来自程序动态添加


也就是说在字符串常量池中只保存相同字符串常量的一份地址,这份地址是全局共享的,每一次要放入字符串常量池之前都要检查一下,这个字符串常量的地址常量池里是否存在,如果字符串常量池中存在这份地址,就不要再将这个地址放入常量池,只需将这个地址存到这个对象变量即可。

画图理解一下:


intern方法
public static void main(String[] args) {
      String str2 = new String("hello,world");
      str2 = str2.intern();
      String str1 = "hello,world";
      String str3 = "hello,world";
      System.out.println(str1==str2);
      System.out.println(str1==str3);
      System.out.println(str2==str3);
}

 这时当我们使用了intern方法,它会检查字符串常量是否在常量池当中,如果没有就把它放在字符串常量池中,如果有就返回它的引用。


一道面试题:

请解释String类中两种对象实例化的区别:(每一个题都不在上一个基础上)

1. String str = "hello"

将hello放入常量池当中,这个str引用,引用的就是这个放在常量池当中hello的这个对象,只会在堆中开辟一块空间。

2. String str = new String("hello");

如果面试官问这个题,我们应该这么回答,这行代码创建一个或者两个对象,一是当检查字符串常量池中有没有hello,如果发现常量池没有hello,他就会在字符串常量池中创建,然后在通过new创建一个对象,这个String对象的value数组就存放着常量池中的hello,而str就引用这个新在堆内存创建的对象。

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

 这里首先new了一个字符数组,然后根据:

 这里会拷贝一个char类型的字符数组然后String对象中的value数组就指向这个拷贝的char类型的字符数组。这里就会创建三个对象


String的不可变性

为啥String不可变呢???

我们可以看一下String的源码:

 从这里可以看到:String包含两个成员,一个是char 类型的value数组,一个是hash值。

那为啥String会不可变呢,第一这个String类是被final所修饰的,证明这个类是不能被继承的,第二这个String的成员的char类型的value数组是被final+private所修饰,他也没有提供get或set方法我们并不能访问到这个数组所以不能被修改,注意不是因为她被final所修饰他就不可变,这个成员final修饰只是代表这个value数组的指向不可改变。还有一点就是因为对String类型的 *** 作总是要返回一个新的对象而不是在本身改变。


  • String这个类被final所修饰,不能被继承。
  • 这个String成员的value数组被private+final所修饰,并且没有提供get或者set方法,并不能够访问到,所以不能修改
  • 对String类型的 *** 作总是返回一个新的对象,而不是在本身 *** 作。

字符串修改

我们如果要对字符串类型进行修改,怎么修改呢???那就只能使用  + 号来进行拼接

    public static void main(String[] args) {
        String str = "";
        for(int i =0;i<10;++i) {
            str+="a";
        }
        System.out.println(str);
    }

这样写虽然能得出拼接出来的结果,但是你并不知道它究竟会做什么???

看一下汇编代码:

 根据汇编代码,有个new,我们还记得对String类型 *** 作的时候,需要返回给一个全新的对象,熟不知这里在拼接的时候会创建大量的临时对象,这样需要频繁的创建和销毁。


从这个汇编代码我们还可以知道,这个String类型会被优化成Stringbuilder,也就是先创建一个StirngBuilder这个对象,然后调用两次append的方法进行拼接,在调用toString方法把拼接的之后的调用toString方法返回到这个对象中。


StringBuffer和StringBuilder

对于String类型,当我们进行拼接的时候会产生大量的临时对象,这就会造成效率非常的低下。

所以,如果要进行修改的话,我们就可以shiyongStringBuffer或者Stringbuilder,它们是可变的,通过调用它们的append方法进行拼接,并且他们不会创建临时对象,而是把拼接好的返回当前对象。

我们怎么使用StringBuffer和StringBulider呢???

    public static void main(String[] args) {
        //使用StringBuffer
        StringBuffer stringBuffer = new StringBuffer();
        for(int i =0;i<10;++i) {
            stringBuffer.append("a");
        }
        System.out.println(stringBuffer);
        //使用StringBuilder
        StringBuilder stringBuilder = new StringBuilder();
        for(int i =0;i<10;++i) {
            stringBuilder.append("a");
        }
        System.out.println(stringBuilder);
    }

  • 为什么说StringBuffer或者StringBuilder拼接不会产生临时对象呢???

原因是StringBuffer或者StringBuilder拼接是利用append。我们看一看append的原码

他返回的是this,this代表当前对象的引用,所以就返回到当前stringbuilder对象里,不会产生大量临时对象。

 注意:String和StringBuilder类不能直接转换。如果要想互相转换,可以采用如下原则:
String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
StringBuilder变为String: 调用toString()方法

StringBuffer StringBuilder的区别??

 两个对比可知,他两差了一个关键字synchronized,这个关键字代表线程安全。


经典面试题

面试官:String StringBuffer StringBuilder的区别???

  • 首先String与这两个的区别:string的拼接会被优化成Stringbuilder的append的拼接,String不可变,而StringBuffer StringBuilder可变。同时String拼接会产生大量的临时对象,而StringBuffer StringBuilder不会产生,而是将拼接好的返回到当前对象。
  • StringBuffer StringBuilder有String没有的方法,同时StringBuffer StringBuilder大部分功能类似
  • StringBuffer是线程安全的 StringBuilder不是线程安全的

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/719608.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存