目錄
我們先來說說,java中八大資料類型,然後在說String。
String官方介紹
英文版
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html
目錄下
String核心部分源码分析 " >String核心部分源码分析
String类源码注释
String类定义
重要成员
构造方法
常用方法分析
hashCode方法
equals()方法
substring()方法
intern()方法
length()方法
isEmpty() 方法
charAt(int index) 方法
getBytes()方法
compareTo()方法
startsWith()方法
concat()方法
replace()方法
trim()方法
valueOf()方法
split() 方法
String在面试中常见问题 " >String在面试中常见问题
如何比较字符串相同?
String str=new String("abc");这行代码创建了几个对象?
String 和 StringBuilder、StringBuffer 的差異
String 和 JVM有什么关系?
如何判断两个字符串中含有几个相同字符
String有没有长度限制?是多少?为什么?
字符串对象能否用在switch表达式中?
说说String中intern方法
好了,关于Stirng类的分享就到此,欢迎找我探讨更多技术问题。
首頁 Java java教程 2w字 詳解 String,yyds

2w字 詳解 String,yyds

Aug 24, 2023 pm 03:56 PM
string


前言

#大家好,今天要跟大家分享java基礎知識之String。

String類的重要性就不必說了,可以說是我們後端開發用的最多的類,所以,很有必要好好來聊聊它。

本文主要內容如下:

2w字 詳解 String,yyds


String簡介

我們先來說說,java中八大資料類型,然後在說String。

八大基本資料型別

byte:8位,最大儲存資料量是255,存放的資料範圍是-128~127之間。

short:16位,最大資料儲存量是65536,資料範圍是-32768~32767之間。

int:32位,最大資料儲存容量是2的32次方減1,資料範圍是負的2的31次方到正的2的31次方減1。

long:64位,最大資料儲存容量是2的64次方減1,資料範圍為負的2的63次方到正的2的63次方減1。

float:32位,資料範圍在3.4e-45~1.4e38,直接賦值時必須在數字後面加上f或F。

double:64位,資料範圍在4.9e-324~1.8e308,賦值時可以加d或D也可以不加。

###boolean:只有true和false兩個取值。 ###

char:16位,儲存Unicode碼,用單引號賦值。

除了這八大資料類型以外(八大資料類型也有與之對應的封裝類型,我相信你是知道的),Java中還有一種比較特殊的類型:String,字面意義就是字串。

String官方介紹

英文版

2w字 詳解 String,yyds


https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html

##看不懂嗎?沒事,我們可以藉用翻譯工具,瀏覽器自備的,更希望的是你能看懂原版英文。

String 存在於咱們安裝的

JDK

目錄下

rt.ar

套件中,全路徑名為:

java.lang.String###。我們java程式碼中String用來表示字串,例如:###
String str = "中国梦,我的梦";
String name = "zhangsan";
登入後複製
###暫時先知道這些就可以了。 #########String使用###### #########定義類型######在日常開發中,使用String的地方太多了,尤其是用來定義變數、常數的類型,基本上只要你碼程式碼,總是能見到它。 ######例如:使用者資訊,用實體類別User表示。 ###
public class User{
    private Long id;
    private String userName;
    private String address;
    private String password;
    ....
}
登入後複製
###常用方法示範######String類別有20多個方法,下面給出一個使用範例(這裡示範大部分方法,剩下的可以自行去試試)。 ###
//案例代码,来源于网络
public class StringDemo {
    public static void main(String[] args) throws Exception {
        String str1 = "Hello World";
        String str2 = "Hello World";
        String str3 = "hello world";
        String str4 = " hello world ";
        //返回字符串的长度
        System.out.println("r1: " + str1.length());
        //比较两个字符串的大小compareTo(返回的是int),0相等,复数小于,正数大于
        System.out.println("r2 : " + str1.compareTo(str2));
        //比较两个字符串的大小compareTo(返回的是int),0相等,复数小于,正数大于
        System.out.println("r3 : " + str1.compareTo(str3));
        //字符串比较compareToIgnoreCase,忽略大小写。0相等,复数小于,正数大于
        System.out.println("r4 : " + str1.compareToIgnoreCase(str3));
        //字符串查找indexOf,返回的是找到的第一个的位置,没找到返回-1。从0开始
        System.out.println("r5 : " + str1.indexOf("o"));
        //查找字符串最后一次出现的位置lastIndexOf
        System.out.println("r6 : " + str1.lastIndexOf("o"));
        //删除字符串中的一个字符,字符串从0开始的 substring(a, b)
        //返回指定起始位置(含)到结束位置(不含)之间的字符串
        System.out.println("r7 : " + str1.substring(0, 5) + str1.substring(6));

        //字符串替换,替换所有
        System.out.println("r8 : " + str1.replace("o", "h"));
        //字符串替换,替换所有
        System.out.println("r9 : " + str1.replaceAll("o", "h"));
        //字符串替换,替换第一个
        System.out.println("r10 : " + str1.replaceFirst("o", "h"));
        //字符串反转
        System.out.println("r11 : " + new StringBuffer(str1).reverse());
        //字符串反转
        System.out.println("r11’: " + new StringBuilder(str1).reverse());
        //字符串分割
        String[] temp = str1.split("\\ ");
        for (String str : temp) {
            System.out.println("r12 : " + str);
        }
        //字符串转大写
        System.out.println("r13 : " + str1.toUpperCase());
        //字符串转小写
        System.out.println("r14 : " + str1.toLowerCase());
        //去掉首尾空格
        System.out.println("r15 : " + str4.trim());
        //是否包含,大小写区分
        System.out.println("r16 : " + str1.contains("World"));
        //返回指定位置字符
        System.out.println("r17 : " + str1.charAt(4));
        //测试此字符串是否以指定的后缀结束
        System.out.println("r18 : " + str1.endsWith("d"));
        //测试此字符串是否以指定的前缀开始
        System.out.println("r19 : " + str1.startsWith("H"));
        //测试此字符串从指定索引开始的子字符串是否以指定前缀开始
        System.out.println("r20 : " + str1.startsWith("ll", 2));
        //将指定字符串连接到此字符串的结尾。等价于用“+”
        System.out.println("r21 : " + str1.concat("haha"));
        //比较字符串的内容是否相同
        System.out.println("r22 : " + str1.equals(str2));
        //与equals方法类似,忽略大小写
        System.out.println("r23 : " + str1.equalsIgnoreCase(str2));
        //判断是否是空字符串
        System.out.println("r24:  " + str1.isEmpty());

    }
}
登入後複製

我们开发中差不多也就是这么使用了,但是如果你仅仅是使用很牛了,貌似遇到面试照样会挂。所以,学知识,不能停留在使用层面,需要更深层次的学习。

下面我们就来深层次的学习String,希望大家带着一颗平常的心学习,不要害怕什么,灯笼是张纸,捅破不值钱。

String核心部分源码分析

备注:JDK版本为1.8+,因为JDK9版本中和旧版本有细微差别。

String类源码注释

/**
 * The {@code String} class represents character strings. All
 * string literals in Java programs, such as {@code "abc"}, are
 * implemented as instances of this class.
 * 这个String类代表字符串。java编程中的所有字符串常量。
 * 比如说:"abc"就是这个String类的实例
 * <p>
 * Strings are constant; their values cannot be changed after they
 * are created. 
 * 字符串是常量,他们一旦被创建后,他们的值是不能被修改。(重点)
 * String buffers support mutable strings.
 * String缓存池支持可变的字符串,
 * Because String objects are immutable they can be shared. For example:
 * 因为String字符串不可变,但他们可以被共享。比如:
 * <blockquote><pre class="brush:php;toolbar:false">
 *     String str = "abc";
 * 

* is equivalent to: *

 *     char data[] = {&#39;a&#39;, &#39;b&#39;, &#39;c&#39;};
 *     String str = new String(data);
 * 

* Here are some more examples of how strings can be used: * String使用案例 * System.out.println("abc"); * String cde = "cde"; * System.out.println("abc" + cde); * String c = "abc".substring(2,3); * String d = cde.substring(1, 2); *

* The class {@code String} includes methods for examining * individual characters of the sequence, for comparing strings, for * searching strings, for extracting substrings, and for creating a * copy of a string with all characters translated to uppercase or to * lowercase. Case mapping is based on the Unicode Standard version * specified by the {@link java.lang.Character Character} class. * 这个String类包含了一些测评单个字符序列的方法,比如字符串比较,查找字符串, * 提取字符串,和拷贝一个字符串的大小写副本。 * 大小写映射的是基于Character类支持的Unicode的字符集标准版本。 *

* The Java language provides special support for the string * concatenation operator ( + ), and for conversion of * other objects to strings. * java语言提供了对字符串的特殊支持,如:可以通过"+"号来进行字符串的拼接操作, * 为其他类提供了与字符串转换的操作 * String concatenation is implemented * through the {@code StringBuilder}(or {@code StringBuffer}) * class and its {@code append} method. * 字符串的+号拼接操作是通过StringBuilder或者StringBuffer类的append()方法 * 来实现的 * String conversions are implemented through the method * {@code toString}, defined by {@code Object} and * inherited by all classes in Java. * 对象与字符串的转换操作是通过所有类的父类Object中定义的toString()方法来实现的 * For additional information on * string concatenation and conversion, see Gosling, Joy, and Steele, * The Java Language Specification. * *

Unless otherwise noted, passing a null argument to a constructor * or method in this class will cause a {@link NullPointerException} to be * thrown. * 除非有特殊说明,否则传一个null给String的构造方法或者put方法,会报空指针异常的 *

A {@code String} represents a string in the UTF-16 format * in which supplementary characters are represented by surrogate * pairs (see the section Unicode * Character Representations in the {@code Character} class for * more information). * 一个String 对象代表了一个UTF-16编码语法组成的字符串 * Index values refer to {@code char} code units, so a supplementary * character uses two positions in a {@code String}. *

The {@code String} class provides methods for dealing with * Unicode code points (i.e., characters), in addition to those for * dealing with Unicode code units (i.e., {@code char} values). * 索引值指向字符码单元,所以一个字符在一个字符串中使用两个位置, * String 类提供了一些方法区处理单个Unicode编码,除了那些处理Unicode代码单元。 * @since JDK1.0 */

登入後複製

以上便是String类注释的整个片段,后面剩下的就是作者、相关类、相关方法以及从JDK哪个版本开始有的。

String类定义

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
 ....   
 }
登入後複製

类图

2w字 詳解 String,yyds


String类被final修饰,表示String不可以被继承。下面我们来说说String实现三个接口有什么用处:

  • 实现Serializable,可以被序列化
  • 实现Comparable,可以用于比较大小(按顺序比较单个字符的ASCII码)
  • 实现CharSequence,表示是一个有序字符的序列,(因为String的本质是一个char类型数组)

简单介绍final

修饰类:类不可被继承,也就是说,String类不可被继承了

修饰方法:把方法锁定,以访任何继承类修改它的涵义

修饰遍历:初始化后不可更改

重要成员

 /** The value is used for character storage. */
// 来用存储String内容的
private final char value[];
// 存储字符串哈希值,默认值为0
private int hash; // Default to 0
// 实现序列化的标识
private static final long serialVersionUID = -6849794470754667710L;
登入後複製

char value[]被final修饰,说明value[]数组是不可变的。

构造方法

/**
 * 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.
 * 初始化新创建的String对象,时期表示空字符串序列。
 * 注意:这个构造方法的用法是没必要的,因为字符串是不可变的
 */
public String() {
        this.value = "".value;
}
登入後複製

无参构造方法中是将一个空字符串的value值赋给当前value。

 /**
  * Initializes a newly created {@code String} object so that it represents
  * the same sequence of characters as the argument; in other words, the
  * newly created string is a copy of the argument string. Unless an
  * explicit copy of {@code original} is needed, use of this constructor is
  * unnecessary since Strings are immutable.
  * 初始化创建的String对象,时期表示与参数相同的字符串序列。
  * 换句话说:新创建的字符串是参数自粗糙的副本。
  * 除非,如果需要original的显示副本,否则也是没有必要使用此构造方法的
  * 因为字符串是不可变的
  * @param  original
  *         A {@code String}
  */
 public String(String original) {
     this.value = original.value;
     this.hash = original.hash;
 }
//案例:  String str=new String("abc");
登入後複製

把original的value赋给当前的value,并把original的hash赋给当前的hash。

/**
 * Allocates a new {@code String} so that it represents the sequence of
 * characters currently contained in the character array argument. The
 * contents of the character array are copied; subsequent modification of
 * the character array does not affect the newly created string.
 * 分配一个新的{@code String},以便它表示字符数组参数中当前包含的字符。这个
 * 复制字符数组的内容;随后修改字符数组不影响新创建的字符串。
 * @param  value
 *         The initial value of the string
 */
public String(char value[]) {
    //注:将传过来的char数组copy到value数组里
    this.value = Arrays.copyOf(value, value.length);
}
//Arrays类中的copyOf方法
public static char[] copyOf(char[] original, int newLength) {
    //创建一个新的char数组
    char[] copy = new char[newLength];
    //把original数组中内容拷贝到新建的char数组中
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    //返回新建的char数组
    return copy;
}
登入後複製

使用Arrays类的copyOf方法,新建一个char数组,将original的内容放到新建的char数组中。

然后,把新建的char数组赋给当前的vlaue。

public String(StringBuffer buffer) {
   synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
   }
}
登入後複製

因为StringBuffer是线程安全类,所以,这里加了同步锁,保证线程安全。

public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
登入後複製

StringBuilder是非线程安全的,这里也就没有做线程安全处理,其他内容和前面一样。

注:很多时候我们不会这么去构造,因为StringBuilder跟StringBuffer有toString方法如果不考虑线程安全,优先选择StringBuilder

这里就讲这么多构造方法,其他很复杂,也基本不用,所以,了解这些就够了。如果对其他感兴趣的,可以自行去研究研究。

常用方法分析

前面的使用案例中,我们已经对String的大部分方法进行演示一波,这里我们就挑几个相对重要的方法进行深度解析。

hashCode方法

hashCode()方法是在Object类中定义的,String对其进行了重写。

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
            //hash算法,s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
            //使用{@codeint}算法,其中{@codes[i]}是<i> i</i>字符串的第个字符,
            //{@code n}是字符串,{@code^}表示指数运算。
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
}
登入後複製

hashCode的一个具体实现,由于java体系中每个对象都可以转换成String,因此他们应该都是通过这个hash来实现的

接着,我们看看equals()方法;

equals()方法

equals()方法也是Object类中定义的,String类对其进行了重写。

public boolean equals(Object anObject) {
    //首先会判断是否是同一个对象
     if (this == anObject) {
         return true;
     }
    //判断是否为String类型
     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) {
                 //不相等,直接返回false
                 if (v1[i] != v2[i])
                     return false;
                 i++;
             }
             return true;
         }
     }
     return false;
}
登入後複製

补充:==比较

==比较基本数据类型,比较的是值
==比较引用数据类型,比较的是地址值
登入後複製

substring()方法

substring方法在工作使用的也是相当的多,作用就是截取一段字符串。

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    //如果beginIndex==0,返回的是当前对象,
    //否则这里是new的一个新对象,其实String中的很多函数都是这样的操作
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
登入後複製

intern()方法

intern()方法是native修饰的方法,表示该方法为本地方法。

/*
 * When the intern method is invoked, if the pool already contains a
 * string equal to this {@code String} object as determined by
 * the {@link #equals(Object)} method, then the string from the pool is
 * returned. Otherwise, this {@code String} object is added to the
 * pool and a reference to this {@code String} object is returned.
 */
public native String intern();
登入後複製

方法注释会有写到,意思就是调用方法时,如果常量池有当前String的值,就返回这个值,没有就加进去,返回这个值的引用。

案例如下

public class StringDemo {
    public static void main(String[] args) throws Exception {
        String str1 = "a";
        String str2 = "b";
        String str3 = "ab";
        String str4 = str1 + str2;
        String str5 = new String("ab");

        System.out.println(str5 == str3);//堆内存比较字符串池
        //intern如果常量池有当前String的值,就返回这个值,没有就加进去,返回这个值的引用
        System.out.println(str5.intern() == str3);//引用的是同一个字符串池里的
        System.out.println(str5.intern() == str4);//变量相加给一个新值,所以str4引用的是个新的
        System.out.println(str4 == str3);//变量相加给一个新值,所以str4引用的是个新的

    }
}
登入後複製

运行结果

false
true
false
false
登入後複製

length()方法

获取字符串长度,实际上是获取字符数组长度 ,源码就非常简单了,没什么好说的。

public int length() {
    return value.length;
}
登入後複製

isEmpty() 方法

判断字符串是否为空,实际上是盼复字符数组长度是否为0 ,源码也是非常简单,没什么好说的。

public boolean isEmpty() {
    return value.length == 0;
}
登入後複製

charAt(int index) 方法

根据索引参数获取字符 。

public char charAt(int index) {
    //索引小于0或者索引大于字符数组长度,则抛出越界异常
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    //返回字符数组指定位置字符
    return value[index];
}
登入後複製

getBytes()方法

获取字符串的字节数组,按照系统默认字符编码将字符串解码为字节数组 。

public byte[] getBytes() {
    return StringCoding.encode(value, 0, value.length);
}
登入後複製

compareTo()方法

这个方法写的很巧妙,先从0开始判断字符大小。如果两个对象能比较字符的地方比较完了还相等,就直接返回自身长度减被比较对象长度,如果两个字符串长度相等,则返回的是0,巧妙地判断了三种情况。

public int compareTo(String anotherString) {
    //自身对象字符串长度len1
    int len1 = value.length;
    //被比较对象字符串长度len2
    int len2 = anotherString.value.length;
    //取两个字符串长度的最小值lim
    int lim = Math.min(len1, len2);
    char v1[] = value;
    char v2[] = anotherString.value;
 
    int k = 0;
    //从value的第一个字符开始到最小长度lim处为止,如果字符不相等,
    //返回自身(对象不相等处字符-被比较对象不相等字符)
    while (k < lim) {
        char c1 = v1[k];
        char c2 = v2[k];
        if (c1 != c2) {
            return c1 - c2;
        }
        k++;
    }
    //如果前面都相等,则返回(自身长度-被比较对象长度)
    return len1 - len2;
}
登入後複製

startsWith()方法

public boolean startsWith(String prefix, int toffset) {
    char ta[] = value;
    int to = toffset;
    char pa[] = prefix.value;
    int po = 0;
    int pc = prefix.value.length;
    // Note: toffset might be near -1>>>1.
    //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假
    if ((toffset < 0) || (toffset > value.length - pc)) {
        return false;
    }
    //从所比较对象的末尾开始比较
    while (--pc >= 0) {
        if (ta[to++] != pa[po++]) {
            return false;
        }
    }
    return true;
}
 
public boolean startsWith(String prefix) {
    return startsWith(prefix, 0);
}
 
public boolean endsWith(String suffix) {
    return startsWith(suffix, value.length - suffix.value.length);
}
登入後複製

起始比较和末尾比较都是比较经常用得到的方法,例如:在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较。

concat()方法

public String concat(String str) {
    int otherLen = str.length();
    //如果被添加的字符串为空,返回对象本身
    if (otherLen == 0) {
        return this;
    }
    int len = value.length;
    char buf[] = Arrays.copyOf(value, len + otherLen);
    str.getChars(buf, len);
    return new String(buf, true);
}
登入後複製

concat方法也是经常用的方法之一,它先判断被添加字符串是否为空来决定要不要创建新的对象。

replace()方法

public String replace(char oldChar, char newChar) {
    //新旧值先对比
    if (oldChar != newChar) {
        int len = value.length;
        int i = -1;
        char[] val = value; 
 
        //找到旧值最开始出现的位置
        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;
}
登入後複製

这个方法也有讨巧的地方,例如最开始先找出旧值出现的位置,这样节省了一部分对比的时间。replace(String oldStr,String newStr)方法通过正则表达式来判断。

trim()方法

public String trim() {
    int len = value.length;
    int st = 0;
    char[] val = value;    /* avoid getfield opcode */
 
    //找到字符串前段没有空格的位置
    while ((st < len) && (val[st] <= &#39; &#39;)) {
        st++;
    }
    //找到字符串末尾没有空格的位置
    while ((st < len) && (val[len - 1] <= &#39; &#39;)) {
        len--;
    }
    //如果前后都没有出现空格,返回字符串本身
    return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
登入後複製

trim方法就是将字符串中的空白字符串删掉。

valueOf()方法

public static String valueOf(boolean b) {
   //如果b为true就返回"true"否则返回"false"
   return b ? "true" : "false";
}
public static String valueOf(char c) {
    //创建data[]数组 并把c添加进去
    char data[] = {c};        
     //创建一个新的String对象并进行返回
    return new String(data, true); 
}
public static String valueOf(int i) {
    //调用Integer对象的toString()方法并进行返回
    return Integer.toString(i);  
}
//Integer类中的toString(i)方法
public static String toString(int i) {
    //是否为Integer最小数,是直接返回
    if (i == Integer.MIN_VALUE)
       return "-2147483648";
    //这个i有多少位
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    //创建一个char数组
    char[] buf = new char[size];
    //把i内容方法char数组中区
    getChars(i, size, buf);
    //返回一个String对象
    return new String(buf, true);
}
登入後複製

split() 方法

public String[] split(String regex) {
    return split(regex, 0);
}
//使用到了正则表达式
public String[] split(String regex, int limit) {
      //....
    //源码有点多了,反正就是里面使用到了正则表达式,进行切分
    }
登入後複製

split() 方法用于把一个字符串分割成字符串数组,返回一个字符串数组返回的数组中的字串不包括 regex自身。可选的“limit”是一个整数,第一个方法中默认是0,允许各位指定要返回的最大数组的元素个数。

常见方法源码分析就这么多了,下面我们再回顾到使用场景中来,尤其是面试中。

String在面试中常见问题

如何比较字符串相同?

在java中比较对象是否相同,通常有两种方法:

  • ==
  • equals方法

注意==用于基本数据类型的比较和用于引用类型的比较的区别。

==比较基本数据类型,比较的是值

==比较引用数据类型,比较的是地址值

另外,Stringequals方法进行了重写,所以比较字符串咱们还是要使用equals方法来比较。主要是Stringequals方法里包含了==的判断(请看前面源码分析部分)。

案例

public class StringDemo {
   public static void main(String[] args) {
     String st1 = "abc";
     String st2 = "abc";
     System.out.println(st1 == st2);
     System.out.println(st1.equals(st2)); 
   }
}
登入後複製

输出

true
true
登入後複製

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

看下面这段代码:

String str1 = "abc";  // 在常量池中
String str2 = new String("abc"); // 在堆上
登入後複製

关于这段代码,创建了几个对象,网上答案有多重,1个,2个还有3个的。下面我们就来聊聊到底是几个?

首先,我們需要明確的是;不管是str1還是str2,他們都是String類型的變量,不是對象,平時,可能我們會叫str2對象,那隻是為了便於理解,本質上來說str2、str1都不是對象。

其次,String str="abc";的時候,字串「abc」會被儲存在字串常數池中,只有1份,此時的賦值運算等於是建立0個或1個物件。如果常量池中已經存在了“abc”,那麼就不會再建立對象,直接將引用賦值給str1;如果常數池中沒有“abc”,那麼建立一個對象,並將引用賦值給str1。

那麼,透過new String("abc");的形式又是如何呢?

答案是1或2個。

當JVM遇到上述程式碼時,會先擷取常數池中是否存在“abc”,如果不存在“abc”這個字串,則會先在常數池中建立這個字元串。然後再執行new操作,會在堆記憶體中建立一個儲存「abc」的String對象,對象的參考賦值給str2。此過程建立了2個物件。

當然,如果檢索常數池時發現已經存在了對應的字串,那麼只會在堆內創建一個新的String對象,此過程只創建了1個對象。

最後,如果單獨問String str=new String("abc");建立了幾個對象,請記住:常數池中是否存在"abc",存在,建立一個對象;不存在創建兩個物件。

String 和 StringBuilder、StringBuffer 的差異

線程安全性

String 中的物件是不可變的,也可以理解為常數,線程安全。 AbstractStringBuilder 是 StringBuilder 與 StringBuffer 的公共父類,定義了一些字串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。 StringBuffer 對方法加了同步鎖或是對呼叫的方法加了同步鎖,所以是執行緒安全的。 StringBuilder 並沒有對方法進行加同步鎖,所以是非執行緒安全的。

效能

每次對 String 類型進行改變的時候,都會產生一個新的 String 對象,然後將 指標指向新的 String 物件。 StringBuffer 每次都會對 StringBuffer 物件本身進行操作,而不是產生新的物件並更改物件參考。相同情況下使用 StringBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的效能提升,但卻要冒多執行緒不安全的風險。

三者所使用的總結:

  • 操作少量的数据 ,推荐使用String
  • 单线程操作字符串缓冲区下操作大量数据,推荐使用 StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据 ,推荐使用 StringBuffer

String 和 JVM有什么关系?

String 常见的创建方式有两种,new String() 的方式和直接赋值的方式,直接赋值的方式会先去字符串常量池中查找是否已经有此值,如果有则把引用地址直接指向此值,否则会先在常量池中创建,然后再把引用指向此值;而 new String() 的方式一定会先在堆上创建一个字符串对象,然后再去常量池中查询此字符串的值是否已经存在,如果不存在会先在常量池中创建此字符串,然后把引用的值指向此字符串。

JVM中的常量池

2w字 詳解 String,yyds


字面量—文本字符串,也就是我们举例中的 public String s = " abc "; 中的 "abc"。

用 final 修饰的成员变量,包括静态变量、实例变量和局部变量。

请看下面这段代码:

String s1 = new String("Java");
String s2 = s1.intern();
String s3 = "Java";
System.out.println(s1 == s2); // false
System.out.println(s2 == s3); // true
登入後複製

它们在 JVM 存储的位置,如下图所示:

2w字 詳解 String,yyds


注意:JDK 1.7 之后把永生代换成的元空间,把字符串常量池从方法区移到了 Java 堆上。

除此之外编译器还会对 String 字符串做一些优化,例如以下代码:

String s1 = "Ja" + "va";
String s2 = "Java";
System.out.println(s1 == s2);
登入後複製

虽然 s1 拼接了多个字符串,但对比的结果却是 true,我们使用反编译工具,看到的结果如下:

Compiled from "StringExample.java"
public class com.lagou.interview.StringExample {
  public com.lagou.interview.StringExample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
    LineNumberTable:
      line 3: 0
  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String Java
       2: astore_1
       3: ldc           #2                  // String Java
       5: astore_2
       6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: aload_1
      10: aload_2
      11: if_acmpne     18
      14: iconst_1
      15: goto          19
      18: iconst_0
      19: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
      22: return
    LineNumberTable:
      line 5: 0
      line 6: 3
      line 7: 6
      line 8: 22
}
登入後複製

从编译代码 #2 可以看出,代码 "Ja"+"va" 被直接编译成了 "Java" ,因此 s1==s2 的结果才是 true,这就是编译器对字符串优化的结果。

如何判断两个字符串中含有几个相同字符

  • 将字符串转化成数组
  • HashMap 方法
  • 字符串直接进行比较
  • 正则表达式
  • HashSet 方法

String有没有长度限制?是多少?为什么?

下面先看看length方法源码:

private final char value[];
public int length() {
        return value.length;
}
登入後複製

length()方法返回的是int类型,那可以得知String类型的长度肯定不能超过Integer.MAX_VALUE的。

答:首先字符串的内容是由一个字符数组 char[] 来存储的,由于数组的长度及索引是整数,且String类中返回字符串长度的方法length() 的返回值也是int ,所以通过查看java源码中的类Integer我们可以看到Integer的最大范围是2^31 -1,由于数组是从0开始的,所以**数组的最大长度可以使【0~2^31】**通过计算是大概4GB。

但是通过翻阅java虚拟机手册对class文件格式的定义以及常量池中对String类型的结构体定义我们可以知道对于索引定义了u2,就是无符号占2个字节,2个字节可以表示的最大范围是2^16 -1 = 65535

但是由于JVM需要1个字节表示结束指令,所以这个范围就为65534了。超出这个范围在编译时期是会报错的,但是运行时拼接或者赋值的话范围是在整形的最大范围。

字符串对象能否用在switch表达式中?

JDK7开始的话,我们就可以在switch条件表达式中使用字符串了,也就是说7之前的版本是不可以的。

switch (str.toLowerCase()) {
      case "tian":
           value = 1;
           break;
      case "jiang":
           value = 2;
           break;
}
登入後複製

说说String中intern方法

JDK7之前的版本,调用这个方法的时候,会去常量池中查看是否已经存在这个常量了,如果已经存在,那么直接返回这个常量在常量池中的地址值,如果不存在,则在常量池中创建一个,并返回其地址值。

但是在JDK7以及之后的版本中,常量池从perm区搬到了heap区。intern检测到这个常量在常量池中不存在的时候,不会直接在常量池中创建该对象了,而是将堆中的这个对象的引用直接存到常量池中,减少内存开销。

下面的案例

public class InternTest {
  
  public static void main(String[] args) {
    String str1 = new String("hello") + new String("world");
    str1.intern();
    String str2 = "helloworld";
    System.out.println(str1 == str2);//true
    System.out.println(str1.intern() == str2);//true
  }
}
登入後複製

好了,关于Stirng类的分享就到此,欢迎找我探讨更多技术问题。

以上是2w字 詳解 String,yyds的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

使用java的String.valueOf()函數將基本資料型別轉換為字串 使用java的String.valueOf()函數將基本資料型別轉換為字串 Jul 24, 2023 pm 07:55 PM

使用Java的String.valueOf()函數將基本資料型別轉換為字串在Java開發中,當我們需要將基本資料型別轉換為字串時,常見的方法是使用String類別的valueOf()函數。這個函數可以接受基本資料類型的參數,並傳回對應的字串表示。在本文中,我們將探討如何使用String.valueOf()函數進行基本資料型別轉換,並提供一些程式碼範例來

怎麼把char數組轉string 怎麼把char數組轉string Jun 09, 2023 am 10:04 AM

char陣列轉string的方法:可以透過賦值來實現,使用{char a[]=" abc d\0efg ";string s=a;}語法,讓char陣列對string直接賦值,執行程式碼即可完成轉換。

使用java的String.replace()函數替換字串中的字元(字串) 使用java的String.replace()函數替換字串中的字元(字串) Jul 25, 2023 pm 05:16 PM

使用Java的String.replace()函數替換字串中的字元(字串)在Java中,字串是不可變的對象,這意味著一旦創建了一個字串對象,就無法修改它的值。但是,你可能會遇到需要替換字串中的某些字元或字串的情況。這時候,我們可以使用Java的String類別中的replace()方法來實作字串的替換。 String類別的replace()方法有兩種重

2w字 詳解 String,yyds 2w字 詳解 String,yyds Aug 24, 2023 pm 03:56 PM

大家好,今天跟大家分享java基礎之String。 String類別的重要性就不必說了,可以說是我們後端開發用的最多的類,所以,很有必要好好聊聊它。

使用java的String.length()函數取得字串的長度 使用java的String.length()函數取得字串的長度 Jul 25, 2023 am 09:09 AM

使用Java的String.length()函數取得字串的長度在Java程式設計中,字串是一種非常常見的資料類型,我們經常需要取得字串的長度,即字串中字元的個數。在Java中,我們可以使用String類別的length()函數來取得字串的長度。下面是一個簡單的範例程式碼:publicclassStringLengthExample{publ

Golang函數的byte、rune和string型轉換技巧 Golang函數的byte、rune和string型轉換技巧 May 17, 2023 am 08:21 AM

在Golang程式設計中,byte、rune和string類型是非常基礎、常見的資料型別。它們在處理字串、檔案流等資料操作時發揮著重要作用。而在進行這些資料操作時,我們通常需要對它們進行相互的轉換,這就需要掌握一些轉換技巧。本文將介紹Golang函數的byte、rune和string類型轉換技巧,旨在幫助讀者更好地理解這些資料類型,並能夠熟練地在程式設計實踐中應用

java的String類別如何使用 java的String類別如何使用 Apr 19, 2023 pm 01:19 PM

一、認識String1.JDK中的String首先我們看看JDK中的String類別源碼,它實作了很多接口,可以看到String類別被final修飾了,這就說明String類別不可以被繼承,String不存在子類,這樣所有使用JDK的人,用到的String類別都是同一個,如果String允許被繼承,每個人都可以對String進行擴展,每個人使用的String不是同一個版本,兩個不同的人使用相同的方法,表現出不同的結果,這就導致程式碼沒辦法進行開發了繼承和方法覆寫在帶來彈性的同時,也會帶來很多子類別行為不

Java String中的split方法如何使用 Java String中的split方法如何使用 May 02, 2023 am 09:37 AM

String中split方法使用String的split()方法用於按傳入的字元或字串對String進行拆分,並傳回拆分之後的陣列。 1.一般用法用一般的字符,例如@或,等符號做分隔符時:Stringaddress="上海@上海市@閔行區@吳中路";String[]splitAddr=address.split("@");System .out.println(splitAddr[0]+splitAddr[1]+splitAddr[2]+splitAddr[3

See all articles