【JAVA SE】java中的String类

目录

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不是线程安全的
  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值