String类的深入理解(未完待续)

  String不是基本数据类型,String和8种包装类型是不可变类。String和8种基本数据类型采用值传递。

0.不可变类的设计原则

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];//数组是引用传递
    /** The offset is the first index of the storage that is used. */
    private final int offset;
    /** The count is the number of characters in the String. */
    private final int count;
    /** Cache the hash code for the string */
    private int hash; // Default to 0
    ....
    public String(char value[]) {
         this.value = Arrays.copyOf(value, value.length); // deep copy操作
     }
    ...
     public char[] toCharArray() {
     // Cannot use Arrays.copyOf because of class initialization order issues
        char result[] = new char[value.length];
        System.arraycopy(value, 0, result, 0, value.length);
        return result;
    }
    ...
}

如上代码所示,可以观察到以下设计细节:

  1. String类被final修饰,不可继承
  2. string内部所有成员都设置为私有变量
  3. 不存在value的setter
  4. 并将value和offset设置为final。
  5. 当传入可变数组value[]时,进行copy而不是直接将value[]复制给内部变量.
  6. 获取value时不是直接返回对象引用,而是返回对象的copy.

这都符合上面总结的不变类型的特性,也保证了String类型是不可变的类。

例如:

package cn.qlq.test;

public class ArrayTest {
    public static void main(String[] args) {
        String str = "x1x1";
        str.replace("1", "2");
        System.out.println(str);// x1x1

        str = str.replace("1", "2");
        System.out.println(str);// x2x2
    }
}

1.创建过程与字符串拼接过程

1.创建过程研究

例如:

package cn.qlq.test;

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

  String s1 = new String("abc"); 是在堆中创建一个String对象,并检查常量池中是否有字面量为"abc"的常量,没有的话在常量区创建"abc"并将堆中的对象指向该常量,有的话堆中的对象直接指向"1";

  String s2 = new String("abc"); 又在堆中创建一个String对象,并将s2指向该对象,其字面量"abc"在前面已经创建,所以不会再创建常量区中创建字符串;

  

  String s3 = "abc";   检查常量池中有没有字面量为"abc"的字符串,如果没有则创建并将s3指向该常量;有的话直接指向该该常量;

  String s4 = "abc"  的时候常量池已经有abc,所以不会再创建对象,也就是s3与s4指向同一个对象。

所以我们可以用下面图解解释,String s = new String("xxx")在检查常量池的时候会涉及到堆中创建对象;String s = "x"直接检查常量池,不会涉及堆。

如下图解:

 

一道经典的面试题:new String("abc")创建几个对象?

  简单的回答是一个或者两个,如果是常量区有值为"abc"的值,则只在堆中创建一个对象;如果常量区没有则会在常量区创建"abc",此处的常量区是方法区的运行时常量池(也称为动态常量区)。

  我们需要明白只要是new都会在堆中创建对象。直接String s = "xxx"不会涉及堆,只在常量区检查是否有该常量。

反编译查看编译后的信息:  

package cn.qlq.test;

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

编译并且查看反编译信息:

C:\Users\liqiang\Desktop>javap -c -verbose ArrayTest.class
Classfile /C:/Users/liqiang/Desktop/ArrayTest.class
  Last modified 2018-9-2; size 383 bytes
  MD5 checksum 0ab23a2d60142821a621d4d345b50622
  Compiled from "ArrayTest.java"
public class cn.qlq.test.ArrayTest
  SourceFile: "ArrayTest.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         //  java/lang/Object."<init>":()V
   #2 = String             #16            //  abc
   #3 = Class              #17            //  java/lang/String
   #4 = Methodref          #3.#18         //  java/lang/String."<init>":(Ljava/lang/String;)V
   #5 = Class              #19            //  cn/qlq/test/ArrayTest
   #6 = Class              #20            //  java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               ArrayTest.java
  #15 = NameAndType        #7:#8          //  "<init>":()V
  #16 = Utf8               abc
  #17 = Utf8               java/lang/String
  #18 = NameAndType        #7:#21         //  "<init>":(Ljava/lang/String;)V
  #19 = Utf8               cn/qlq/test/ArrayTest
  #20 = Utf8               java/lang/Object
  #21 = Utf8               (Ljava/lang/String;)V
{
  public cn.qlq.test.ArrayTest();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         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[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: ldc           #2                  // String abc
         2: astore_1
         3: ldc           #2                  // String abc
         5: astore_2
         6: new           #3                  // class java/lang/String
         9: dup
        10: ldc           #2                  // String abc
        12: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        15: astore_3
        16: new           #3                  // class java/lang/String
        19: dup
        20: ldc           #2                  // String abc
        22: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        25: astore        4
        27: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 16
        line 9: 27
}

上面的Constant pool:是所设计的常量信息,包括类名字、方法名字、字符串常量池信息信息。

下面就是编译之后的方法:

第一个构造方法研究:

  public cn.qlq.test.ArrayTest();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

编译器给我们生成的无参构造方法,访问类型是public,

aload_0                     将第一个引用类型本地变量推送至栈顶(将this引用推送至栈顶,即压入栈。)

invokespecial #1                  // Method java/lang/Object."<init>":()V      调用Object的<init>(构造方法)

return  函数结束(返回类型是void)

第二个main方法研究:

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: ldc           #2                  // String abc
         2: astore_1
         3: ldc           #2                  // String abc
         5: astore_2
         6: new           #3                  // class java/lang/String
         9: dup
        10: ldc           #2                  // String abc
        12: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        15: astore_3
        16: new           #3                  // class java/lang/String
        19: dup
        20: ldc           #2                  // String abc
        22: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        25: astore        4
        27: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 16
        line 9: 27

访问标志符号是static、public类型

ldc:  该系列命令负责把数值常量或String常量值从常量池中推送至栈顶。该命令后面需要给一个表示常量在常量池中位置(编号)的参数(#2代表上面标记为#2的常量)

astore_1                  将栈顶引用型数值存入指定第二个本地变量,超过3的格式变为  astore 4  此种格式

new  代表创建对象

invokespecial   代表调用方法

return   代表函数结束,返回类型是void

  然后对着命令自己去查去吧。。。。。。。。

2.拼接过程研究

2.Intern()方法详解

3.equals()和hashCode方法详解

hashCode()源码查看:

public int hashCode() {
        int h = hash;//默认为0
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    } 

看出来String是遍历每个char,h乘以31加上对应char的ASCII码。

验证:

        String s1 = "a";
        String s2 = "b";
        System.out.println(s1.hashCode());//97
        System.out.println(s2.hashCode());//98

equals(obj)源码查看:  是将形参转变为String,然后遍历里面的char[],两个char[]进行依次对比。也就是比较字符串的值是否相等。

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        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) {
                    if (v1[i] != v2[i])
                            return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

我们利用两个hashCode相等的字符串作为key存入map,查看:

package cn.qlq.test;

import java.util.HashMap;

public class ArrayTest {
    public static void main(String[] args) {
        String s1 = "Aa";
        String s2 = "BB";
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());

        HashMap map = new HashMap();
        map.put(s1, "xxx");
        map.put(s2, "xxxdddd");
        System.out.println(map);
    }

}

2112
2112
{BB=xxxdddd, Aa=xxx}

"Aa" 与"BB"的hashCode相等,那么是如何存入map的?--验证hashmap的实现原理基于数据+链表

     

  先存入Aa,并放在第五个数组位置,当存BB的时候发现hashCode一样,会将BB存到第五个位置,并将第五个位置元素的next(也是一个Entry)存为Aa。也就是数组加链表实现原理

4..String引用传值图解(由于是不可变类,所以给形参赋值的时候相当于新建对象,不会影响实参)

更进一步的理解:"引用传值也是按值传递,只不过传的是对象的地址"。

比如下面一段代码:

package cn.qlq.test;

import java.util.Arrays;

public class ArrayTest {
    public static void main(String[] args) {
        String s = "hello";
        test(s);
        System.out.println(s);
    }

    public static void test(String s1) {
        s1 = "world";
    }
}

结果:

hello

解释:调用test方法的时候采用引用传递(将s的地址传下去),执行s1="world"是新创一个"world"并赋值给s1,也就是s1此时已经指向其他对象,不再与s指向相同对象。

图解:

  

原文地址:https://www.cnblogs.com/qlqwjy/p/9576320.html

时间: 2024-10-28 20:13:43

String类的深入理解(未完待续)的相关文章

关键概念理解---未完待续

默认情况下,每个主分片都有一个副本,但可以在现有索引上动态更改副本数. 永远不会在与其主分片相同的节点上启动副本分片. 分片分布计算公式:shard_num = hash(_routing) % num_primary_shards _routing是文档的_id 写入到Elasticsearch的文档,在默认的情况下并不马上可以进行搜索.这是因为在Elasticsearch的设计中,有一个叫做refresh的操作.它可以帮在Lucene里的离散的Segments进行合并,并使新进入的文档变为搜

C++语言体系设计哲学的一些随想(未完待续)

对于静态类型语言,其本质目标在于恰当地操作数据,得到期望的值.具体而言,需要: (1)定义数据类型 你定义的数据是什么,是整形还是浮点还是字符.该类型的数据可以包含的值的范围是什么. (2)定义操作的含义 操作是严格数据类型相关的.操作表明了对了一个具有特定类型的数据,执行操作后产生什么样结果. =========================================== C++就是一个典型的静态类型语言.在C++中,无论是"数据类型"还是"操作",都分为

练武场之“封装、继承”攻略(未完待续)

如题: 使用继承编写Person.Student类 编写Person类: 属性有:姓名.年龄.出生日期 方法有:showInfo(); 编写Student类: 属性有:姓名.年龄.出生日期.学校 方法有:showInfo(); study(); 使用继承优化Student类. Ready Go! 第一步 Person类 1 package a; 2 //父类 3 public class Person { 4 // 为了让成员变量隐藏(封装),便于对值制定规则:否则变量会被直接赋值. 5 pri

[译]App Framework 2.1 (1)之 Quickstart (未完待续)

最近有移动App项目,选择了 Hybrid 的框架Cordova  和  App Framework 框架开发. 本来应该从配置循序渐进开始写的,但由于上班时间太忙,这段时间抽不出空来,只能根据心情和兴趣,想到哪写到哪,前面的部分以后慢慢补上. App Framework 前生是是叫 jqMobi 注意大家不要和 jQuery Mobile 混淆了,它们是两个不同的框架,一开始我还真混淆了0.01秒. 这里我先翻译一下Quickstart 部分,一是自己工作上用的上,二是也想顺便练练英文,最关键

听风讲MVC丶 —— 一言不合就撸码 (未完待续&#183;&#183;&#183;&#183;&#183;&#183;)

     希望你看了此小随 可以实现自己的MVC框架     也祝所有的程序员身体健康一切安好                                                                                                                                                ——久伴深海丶默 1.什么是前端控制器(font controller).Java Web中的前端控制器是应用的门面,

我的Java问题集(1)(重点、难点、疑点)未完待续...

学习Java这么久了,总算也了点笔记,一个一个字码的哟! 1.Java中的int等类型变量既然已经有默认值为0,为什么还会出现没有初始化,编译出错呢? 一个变量作为类成员使用的时候,如果没有被初始化,java会为其分配默认值: Boolean false Char '\u0000'(null) byte (byte)0 short (short)0 int 0 long 0L float 0.0f double 0.0d 如果在一个方法中定义一个变量,java不会给其分配默认值,就必须我们来给他

AutoMapper介绍(未完待续、部分没实现)

实体间转换工具.其实也可以用Json来实现同名属性.异名属性(用JsonProperty指明)的自动转换 最新版本6.11 需要使用vs2013以上.vs2012下载新版 nuget会遇到问题.只能旧版. 1 using AutoMapper; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namesp

脚本编辑$RANDOM 和 case语句 (笔记) 未完待续

$RANDOM 个人理解是一个用来生成随机数的一个变量 ,由此编写一个脚本 [[email protected] script]# cat random.sh #!/bin/bash # declare -i MAX=0 declare -i MIN=0 for I in `seq 1 10`; do MYRANDOM=$RANDOM [ $I -eq 1 ] && MIN=$RANDOM if [ $I -le 9 ]; then echo -n "$MYRANDOM,&quo

whatweb.rb 未完待续

#!/usr/bin/env ruby #表示ruby的执行环境 =begin # ruby中用=begin来表示注释的开始 .$$$ $. .$$$ $. $$$$ $$. .$$$ $$$ .$$$$$$. .$$$$$$$$$$. $$$$ $$. .$$$$$$$. .$$$$$$. $ $$ $$$ $ $$ $$$ $ $$$$$$. $$$$$ $$$$$$ $ $$ $$$ $ $$ $$ $ $$$$$$. $ `$ $$$ $ `$ $$$ $ `$ $$$ $$' $ `$