【Java进阶最详解】String(一)入门了解和追溯探讨

本文介绍了Java中的String类,包括其常用方法如构造方法、判断、获取、转换和去空格功能。讨论了String的不可变性,以及Java对String的优化,如常量池、内存管理和版本迭代中的变化。
摘要由CSDN通过智能技术生成

在这里插入图片描述

String简介

引言:Java常用类将Java当中经常用到的类,String类是其中之一。String对象是 Java 中使用最频繁的对象之一,所以 Java 公司也在不断的对String对象的实现进行优化,以便提升String对象的性能

在java基本API中,String类出现的频率极高,很多大公司的算法题都是基于字符串的,所以今天对java中的String类做一个总结:

  • 包路径:java.lang.String

  • ?String类是什么

# String类

是一个特殊的对象,适用于描述字符串事物的。
一旦被初始化就不可以被改变,可以进行字符串的大小写转换,分割字符串等字符串的操作
  • ?为什么是final
# 安全性 :

这种类是非常底层的,和操作系统的接触是非常频繁的,如果可以被继承,那么一些人重写了一些方法,往操作系统内部写入一段具有恶意攻击性质的代码什么的,这不就成了核心病毒了么?

# 效率:

设计成final,JVM才不用对相关方法在虚函数表中查询,而直接定位到String类的相关方法上,提高了执行效率。
  • ?String的特性
1.String对象一旦被创建就是固定不变的了
对String对象的任何改变都不影响到原对象,
相关的任何change操作都会生成新的对象

  • ?String的继承关系
public final class String
extends Object
implements Serializable, Comparable<String>, CharSequence

常用方法和分类

Java API中有很多的字符串操作方法:

  • String类的构造方法

  • String类的判断功能

  • String类的获取功能

  • String类的转换功能

  • String类的去空格和分割功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLN4pIry-1598412444201)(61D7488BB44A4D58AEB377B4692DE657)]

String类的概述和构造方法

  • String(String original):把字符串数据封装成字符串对象

  • String(char[] value):把字符数组的数据封装成字符串对象

  • String(char[] value, int index, int count):把字符数组中的一部分数据封装成字符串对象

String类的判断功能

  • boolean equals(Object obj):比较字符串的内容是否相同

  • boolean equalsIgnoreCase(String str):比较字符串的内容是否相同,忽略大小写

  • boolean startsWith(String str):判断字符串对象是否以指定的str开头

  • boolean endsWith(String str):判断字符串对象是否以指定的str结尾

String类的获取功能

  • int length():获取字符串的长度,其实也就是字符个数

  • char charAt(int index):获取指定索引处的字符

  • int indexOf(String str):获取str在字符串对象中第一次出现的索引

  • String substring(int start):从start开始截取字符串

  • String substring(int start,int end):从start开始,到end结束截取字符串。包括start,不包括end

String类的转换功能

  • char[] toCharArray():把字符串转换为字符数组

  • String toLowerCase():把字符串转换为小写字符串

  • String toUpperCase():把字符串转换为大写字符串

String类的去空格和分割功能

  • String trim():去除字符串两端空格

  • String[] split(String str):按照指定符号分割字符串


String的初始化

String就是我们通常说的字符串,它也是Java中常用的类之一。既然它是类,那么可以使用它来实例化对象。接下来我们通过具体的代码的形式来演示如何实例化String类型的对象。

String str1 = "Java";             //方式一
String str2 = new String("Java"); //方式二

这两种方式都可以定义一个字符串类型的变量,并且给变量进行了赋值操作。不过它们还是有区别的:

方式一是把已经在堆内存中的变量和str1关联在了一起,在此之前堆内存中的变量是没有具体的名称的, 因此我们称它为匿名对象,匿名对象可以和变量str1关联,也可以和其它字符串类型的变量关联;
方式二是为str2在堆内存中重新申请一块内存空间,而且把内存空间和str2关联在了一起。

为了看清楚这两种方式之间的区别,我们再来一段代码:

String str3 = "Java"; 
String str4 = new String("Java");

对比上面的代码,我们可以说,str1和str3关联了同一段内存空间,而且str2和str4则是关联了不同的内存空间。真的是这样吗?有客官提问了,看官莫急,我们待会儿通过具体的代码来证明。

除了不同点外,这两种方式也有共同的特点,那就是它们都是字符串类型的变量,该变量只是一个名称,而真正的值位于内存中,我们可以通过操作变量名称来操作位于内存中的字符串。好了,不多说了,我们来点代码做说明:

public class StringEx {
    public static void main(String args[])
    {
        String str1 = "Java";
        String str2 = new String("Java");
        String str3 = "Java";
        String str4 = new String("Java");

        if(str1 == str3)
            System.out.println("Str1 and Str3 use the same memory address");
        else
            System.out.println("Str1 and Str3 don't use the same memory address");

        if(str2 == str4)
            System.out.println("Str2 and Str4 use the same memory address");
        else
            System.out.println("Str2 and Str4 don't use the same memory address");
    }
}

在上面的代码中我们使用了操作符“==”来判断字符串变量是否使用了相同的内存空间,下面是程序的运行结果,请大家参考:

Str1 and Str3 use the same memory address
Str2 and Str4 don't use the same memory address

从程序的运行结果中可以直观地看到两种字符串实例化方式的区别。这是对我们上面分析结果最好的验证。

此外,我们说一下操作符“==”,它用来判断两个变量的值是否相等,但是它只可用于Java中基本类型的变量,比如int或者double类型的变量,对于String类型的变量,它只能判断变量的内存地址是否相同,不能判断变量的值是否相等。


String追溯探讨

String与常量池

字符串常量池:

我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。

JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。

每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。

如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。

由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。

Java中的常量池,实际上分为两种形态:

  • 静态常量池
  • 运行时常量池

静态常量池:

即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间

运行时常量池:

则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

String a="hello";
String b="hello";

很明显,a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"对象,他们指向同一个对象。而且是在编译器就确定了。

所以a==b 为true

String c=new String("hello");

new关键字一定会产生一个对象chenssy(注意这个chenssy和上面的chenssy不同),同时这个对象是存储在堆中。所以上面应该产生了两个对象:保存在栈中的c和保存堆中chenssy。但是在Java中根本就不存在两个完全一模一样的字符串对象。故堆中的chenssy应该是引用字符串常量池中chenssy。所以c、chenssy、池chenssy的关系应该是:c—>chenssy—>池chenssy。整个关系如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8VrKL621-1598412444212)(03F5BE69822D4D8D9582DEFA94F9C89A)]

总结:

虽然a、b、c、chenssy是不同的对象.
但是从String的内部结构我们是可以理解上面的。String c = new String("chenssy");
虽然c的内容是创建在堆中
但是他的内部value还是指向JVM常量池的chenssy的value
它构造chenssy时所用的参数依然是chenssy字符串常量

探讨String的不可变性

从我们知道String对象的那一刻起,我想大家都知道了String对象是不可变的

那它不可变是怎么做到的呢?

Java 这么做能带来哪些好处?我们一起来简单的探讨一下,先来看看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;
    }

从这段源码中可以看出,String类用了 final 修饰符

我们知道当一个类被fianl修饰时,表明这个类不能被继承,所以String类不能被继承。这是String不可变的第一点

再往下看,用来存储字符串的char value[]数组被private 和final修饰,我们知道对于一个被final的基本数据类型的变量,则其数值一旦在初始化之后便不能更改。这是String不可变的第二点。

Java 公司为什么要将String设置成不可变的,主要从以下三方面考虑:

1、保证 String 对象的安全性。假设 String 对象是可变的,那么 String 对象将可能被恶意修改。
2、保证 hash 属性值不会频繁变更,确保了唯一性,使得类似 HashMap 容器才能实现相应的 key-value 缓存功能。
3、可以实现字符串常量池

探讨Java对String的优化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yEVA6MC8-1598412444213)(1603737787B44616965F8AEFDB22B0EF)]

在 Java6 以及之前的版本中

  • String对象是对 char 数组进行了封装实现的对象

主要有四个成员变量: char 数组、偏移量 offset、字符数量 count、哈希值 hash。

String对象是通过 offset 和 count 两个属性来定位 char[] 数组,获取字符串。这么做可以高效、快速地共享数组对象,同时节省内存空间,但这种方式很有可能会导致内存泄漏。

从 Java7 版本开始到 Java8 版本

  • String类中不再有 offset 和 count 两个变量了

这样的好处是String对象占用的内存稍微少了些,同时 String.substring 方法也不再共享 char[],从而解决了使用该方法可能导致的内存泄漏问题。

从 Java9 版本开始

  • 将 char[] 数组改为了 byte[] 数组

为什么需要这样做呢?我们知道 char 是两个字节,如果用来存一个字节的字符有点浪费,为了节约空间,Java 公司就改成了一个字节的byte来存储字符串。这样在存储一个字节的字符是就避免了浪费。

  • 在 Java9 维护了一个新的属性 coder

它是编码格式的标识,在计算字符串长度或者调用 indexOf() 函数时,需要根据这个字段,判断如何计算字符串长度。coder 属性默认有 0 和 1 两个值, 0 代表Latin-1(单字节编码),1 代表 UTF-16 编码。如果 String判断字符串只包含了 Latin-1,则 coder 属性值为 0 ,反之则为 1。

—end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值