17.1Java常用类之String类

JDK 常用包

java.lang:系统的基础类,比如String、Math、Integer、System和Thread提供常用API
java.io:流操作包。输入输出有关的类,比如文件的操作等
java.net:网络有关的类,比如URL,URLConnection等
java.util:系统辅助工具类,比如集合类Collection,List,Map等
java.sql:数据库操作的类,比如Connection,Statememt,ResultSet等

Object

Java中所有的类直接或者间接继承 Object
Java认为所有的对象都具备一些基本的共性内容,这些内容不断的向上抽取,最终就抽取到了一个最顶层的类 Object,该类中定义的就是所有对象都具备的功能。
具体方法:
boolean equals(Object obj): 用于比较两个对象是否相等(比较两个对象地址)
String toString(): 将对象变成字符串 默认返回的格式: 类名@哈希值 = getClass().getName() + ‘@’ + Integer.toHexString(hashCode())
为了对象对应的字符串内容有意义,可以通过重写建立该类对象自己特有的字符串表现形式。
Class getClass(): 获取任意对象运行时的所属字节码文件对象。
int hashCode(): 返回该对象的哈希码值 支持此方法是为了提高哈希表的性能。
通常equals, toString, hashCode在应用中都会被重写,从而来建立具体对象的特有的内容。

String 概述

String类表示字符串。
Java程序中的所有字符串字面值都是为该类的实例实现的。
用的比较多,在Java里面String属于一个对象,提供了一系列操作字符串的属性方法。
String不属于Java里的八种基本数据类型,String它是一个对象。
因为对象的默认值是null,所以String的默认值也是null,但它又是一种特殊的对象,有其它对象没有的一些特性。
字符串是常量,创建之后值是不可修改的。

String源代码:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * Object Serialization Specification, Section 6.2, "Stream Elements"</a>
     */
    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];

    /**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String() {
        this.value = "".value;
    }
    
    // ...许多操作字符串的属性方法
}

String 底层用一个字符数组来维护的字符串,被 final 关键字修饰,是一个常量,创建之后不能修改。
String类也被 final 修饰,意味着String类不能被继承,成员方法默认为 final 方法。

String 字符串操作

创建一个字符串的方式有多种
可以先声明后赋值,可以直接声明并赋值,也可以通过实例化的方式来声明
【例】

public static void main(String[] args) {
    // 先声明
    String str1;
    // 后赋值
    str1 = "HelloWorld";
    System.out.println(str1);

    // 直接声明并赋值
    String str2 = "你好,世界"; 
    System.out.println(str2);

    // 通过实例化方式
    String str3 = new String("来了老弟");
    System.out.println(str3);
}

==输出结果==
HelloWorld
你好,世界
来了老弟
String 常用API

首先要知道一个定义:索引下标从0开始,长度从1开始
这些 String 方法都是比较常用的,熟能生巧。
需要注意的是,如果字符串声明创建时为null,那么在调用String类里的方法时,会抛出空指向异常。在对字符串做任何操作处理时一定要加非null判断。

## String的常用操作方法
.substring(); // 截取字符串
.indexOf(); // 获取指定字符在字符串中第一次出现的下标位置(区分大小写) 
.lastIndexOf(); // 获取指定字符在字符串中最后一次出现的下标(区分大小写)
.split(); // 分割字符串(区分大小写)
.replace(); // 替换字符串(区分大小写)
.replaceAll(); // 替换字符串(区分大小写)
.length(); // 统计字符串长度
.toLowerCase(); // 将字符串单词大写转小写
.toUpperCase(); // 将字符串单词小写转大写
.contains(); // 字符串是否包含某个字符
.trim(); // 去掉字符串两头空格
.concat(); // 字符串追加
.charAt(); // 返回指定下标对应的字符
.isEmpty(); // 返回字符串是否为空字符串
.toCharArray(); // 返回字符串中每个字符元素的字符数组
.endsWith(); // 判断字符串是否以指定字符结尾(区分大小写)
.startsWith(); // 判断字符串是否以指定字符开始(区分大小写)
// 这些方法是需要入参的。具体需要什么参数,点进去看看,会返回什么。
String 对象内存分配

直接声明赋值的方式创建出来的字符串对象被放在方法区的常量池里面,也叫字符串常量池。
通过构造方法实例化出来的字符串对象在堆内存中。
字符串对象的内存分配和其他对象内存分配一样,需要消耗很多时间和空间。为了提高性能减少内存开销,JVM在实例化字符串时做了优化,就是开辟了字符串常量池。
每当创建一个字符串时,JVM会先检查字符串常量池里面是不是已经存在了这个字符串,如果存在了就直接返回常量池中的实例引用。不

存在就实例化该字符串,放到常量池中。
由于字符串的不可变性,可以十分确定常量池中不会出现两个一模一样的字符串。

StringBuffer&StringBuilder

在Java中除了String类来处理字符串,还有StringBuffer和StringBuilder也可以字符串。
StringBuffer 称为字符串缓冲区,它的工作原理是:预先申请一块内存,存放字符序列,如果字符序列满了,会重新改变缓存区的大小,以容纳更多的字符序列。
StringBuffer是可变对象,这个是与String最大的不同之处。
StringBuilder用法与StringBuffer一样。
StringBuilder和StringBuffer的区别在于,StringBuffer中所有的方法都是同步的,是线程安全的,但速度慢,StringBuilder的处理速度快,但线程不安全。
总的来说这两个都比String处理字符串效率要高,因为 StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuffer 类是可变字符串类,创建 StringBuffer 类的对象后可以随意修改字符串的内容。
每个 StringBuffer 类的对象都能够存储指定容量的字符串,如果字符串的长度超过了 StringBuffer 类对象的容量,则该对象的容量会自动扩容。
StringBuilder 类是在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

代码示例:

public class StringDemo {
    public static void main(String[] args) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("Hello,");
        stringBuffer.append("World");
        System.out.println(stringBuffer);

        StringBuffer stringBuffer2 = new StringBuffer();
        stringBuffer2.append("Hello").append("Hello");
        System.out.println(stringBuffer2);

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("你好,");
        stringBuilder.append("世界");
        System.out.println(stringBuilder);

        StringBuilder stringBuilder2 = new StringBuilder();
        stringBuilder2.append("你好").append("你好");
        System.out.println(stringBuilder2);
    }
}

使用 StringBuilder和StringBuffer 里的 append() 方法,进行字符串的拼接
同样 String 也有字符串的拼接,就是 +

public static void main(String[] args) {
    String str = "abc";
    System.out.println(str);
    str = str + "123";
    System.out.println(str);
}

这虽然是实现了字符串的拼接,但是效率是不如StringBuffer和StringBuilder的。
那它们拼接字符串的方式是什么样的呢?
上面String拼接字符串,先输出abc,再输出abc123,看起来好像是str这个对象被更改修改掉了,其实,不是这样的。
JVM对这几行代码处理是这样的,首先创建一个String对象str,并把abc赋值给str,输出abc,然又创建了一个新的对象也名为str,把原来的str的值和123拼起来赋值给新创建的那个str,原来的str对象就会被JVM的垃圾回收机制给回收。所以,看起来str被更改了,实际上并没有被更改。
上面也说了String对象一旦创建之后是不可更改的。
所以,Java中对String对象进行的操作就是不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。
而StringBuilder和StringBuffer的对象是变量,对变量进行操作就是直接对该对象进行更改,而不进行创建和回收的操作,所以速度要比String快很多。

线程安全问题

在线程安全问题上,StringBuilder是线程不安全的,StringBuffer是线程安全的。
StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法带有synchronized关键字,所以可以保证线程是安全的。
StringBuilder的方法则没有该关键字,所以不能保证线程安全,在多线程环境下会有问题。
所以如果要进行的操作是多线程环境下的,就使用StringBuffer。在单线程的情况下,不涉及到线程安全问题的情况下,还是建议使用速度比较快的StringBuilder。
就是个有所长。
StringBuilder和StringBuffer同样提供了许多与String里一样操作字符串的方法,但是用的较多的是对字符串的拼接,提高效率。一般对字符串的操作处理选择用String。
在实际开发过程中根据需求择选使用。

String StringBuffer和StringBuilder的区别

String 字符串常量,不可变 ,使用字符串拼接时是不同的2个空间
StringBuffer 字符串变量,可变,线程安全,字符串拼接直接在字符串后追加
StringBuilder 字符串变量 ,可变,线程不安全 ,字符串拼接直接在字符串后追加

1)StringBuilder 执行效率高于 StringBuffer 高于 String
2)String是一个常量,是不可变的,对于每一次+=赋值都是在创建销毁对象
3)StringBuffer和StringBuilder都是可变的,进行字符串拼接时采用append方法,在原来的基础上进行追加,性能比String要高。
4)StringBuffer 线程安全的,StringBuilder是线程不安全的。StringBuilder效率高于StringBuffer
5)对于大数据量的字符串的拼接工作,选择StringBuffer,StringBuilder

常量池

常量池分为:静态常量池和运行时常量池

静态常量池:是*.class文件中的常量池,class文件中的常量池不仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了类和接口的全限定名,字段名称和描述符,方法名称和描述符三种类型的常量。

运行时常量池:是JVM(虚拟机)在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。这个方法一般被称为手动放入常量池,String的intern()方法会在常量池中查找是否存在一份equals相等的字符串,如果有则返回该字符串的引用,没有则添加自己的字符串进入常量池。

常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,简单说就是实现对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
2)节省运行时间:比较字符串时,== 比equals()快。对于两个引用变量,只用 ==判断引用是否相等,也就可以判断实际值是否相等。
其实字符串常量池这个问题涉及到一个设计模式,《享元模式》,顾名思义 - - - > 共享元素模式

通过一段代码了解下 == 和 equals 区别

public class StringDemo {
    public static void main(String[] args) {
         String str1 = "aaaa";
         String str2 = "aaaa";
         System.out.println(str1 == str2); // true
         System.out.println(str1.equals(str2)); // true

         String str3 = new String("aaaa");
         System.out.println(str1 == str3); // false
        System.out.println(str1.equals(str3)); // true

        String str4 = new String("aaaa").intern();
        System.out.println(str1 == str4); // true

        String str5 = "aa" + "aa";
        System.out.println(str1 == str5); // true
        System.out.println(str1.equals(str5)); // true
    }
}
原理分析:

执行 String str1 = “aaaa”; 时会先去常量池里检查是否存在常量 aaaa,如果不存在,常量池创建这个对象,然后将创建这个对象的引用地址返回给字符串常量str1,str1常量就指向常量池中 aaaa 这个字符串对象。如果存在,不创建任何对象,直接池中”aaaa“这个对象地址返回,赋给str1。

执行 String str2 = “aaaa”; 时也是先去常量池检是否存在常量 aaaa,这时候已经存在了,就不创建了,直接返回 aaaa,对象的引用地址给str2。

也就是说这时候str1和str2指向了同一个对象地址。
== 是用来比较两个对象的引用地址是否相同的,因此 str1 == str2 结果为 true
equals是String类的一个方法,是用来比较两个字符串的字面值是否一样,不管你的引用地址在哪,只要两个值一样就为 true,所以 str1.equals(str2) 结果也为 true

执行 String str3 = new String(“aaaa”); 时会开辟两块内存空间,引用对象str3存放在栈内存中,这时候首先查看常量池中是否有相同值的字符串,如果有,则拷贝一份到堆中,然后返回堆中的地址指向str3;如果常量池中没有,则在堆中创建一份,然后返回堆中的地址指向str3。堆内存存放new出来的 aaaa 对象,并将堆中引用地址指向str3。

== 比较两个对象的引用地址是否相同的,因此 str1 == str3 为 false。因为这个new出来的字符串对象是不会自动入常量池的,像str1那样创建出来的会自动放入常量池。
equals用来比较两个字符串的字面值是否一样,因此 str1.equals(str3) 为 true。

如果想把str3创建出来的对象放入常量池则需要手动来处理,调用String的intern()方法。
执行 String str4 = new String(“aaaa”).intern(); 时,String str4 = new String(“aaaa”) 和 str3一样,只不过后边调用了intern()方法,手动把str4创建出来的对象放入常量池一份,这时候就会把常量池里的aaaa对象引用地址,赋值给str4,因此 str1 == str4 结果为 true。

执行 String str5 = “aa” + “aa”; 时,+号之前说过是前后都是字符串时,做字符串的拼接,既然这样,那+号前后就是一个字符串常量,加起来肯定也是一个字符串常量,所以在编辑时期就执行了字符串的拼接,也就是 str5 = aaaa;也就把 aaaa 放在了常量池里,所以 str1 == str5 结果也为 true 。
当然equals也为true,因为是比较的值。

通过以上分析可以得知
1)== 判断两个对象的引用地址是否相同
2)equals是判断两个字符串对象值是否相同
3)字符串直接声明并赋值,JVM开辟一块内存空间,自动将这个对象保存常量池
4)通过new出来的字符串对象开辟两块内存空间,对象不会自动保存到常量池里面,需要手动调用intern()方法才行。

一般在开发过程中不会使用实例化的方式来声明创建一个字符串。
但是会有面试题问到这个

String str = new String("abc");创建了几个对象?

首先不说创建了几个对象,但它一定会创建对象。
一般国内大公司的面试笔试题上差不多都会出现这个题目,而网上流传的及一些书籍上都说是2个对象,这种说法比较片面。
首先必须弄清楚创建对象的含义,创建是什么时候创建的?这段代码在运行期间会创建2个对象么?毫无疑问不可能,用javap -c反编译即可得到JVM执行的字节码内容:
在这里插入图片描述

很显然,new只调用了一次,也就是说只创建了一个对象。而这道题目让人混淆的地方就是这里,这段代码在运行期间确实只创建了一个对象,即在堆上创建了"abc"对象。而为什么大家都在说是2个对象呢,这里面要澄清一个概念,该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个String对象。因此,这个问题如果换成 String str = new String(“abc”)涉及到几个String对象?合理的解释是2个。

个人觉得在面试的时候如果遇到这个问题,可以向面试官询问清楚,是这段代码执行过程中创建了多少个对象还是涉及到多少个对象?然后再具体的来进行回答。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员良哥

我看你骨骼惊奇,花钱买点知识吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值