Java基础之字符串&equals、==、包装类、常量池

在java中有三中对字符串的操作方式。

注:文章只注明思路原理。不注明方法,看API就行了。文章就涉及到啥写啥了,哈哈,瞅着可能乱一点。但是这么写就很舒服

1、String                  常量 效率较低  指的是多进行字符串拼接操作时的效率 final

2、StringBuffer        变量 效率其次  多线程安全

3、StringBuilder      变量 效率最快  多线程不安全

先看看String这个类。这个类被final修饰,所以不能被继承。

对于String方法的改变操作方法都是new了一个新的对象。原对象保存不变。

 先说一下String中的equals。下面是源代码

 

可以看到。976行方法入参是个Object,如果是这样的  str.equals(str);那他执行的时候977行就是this==anObject 直接返回,因为是同一个对象。接下来 980行,判断是不是String及其子类的引用。没有子类,这里判断得就是这个引用是不是String的。然后对比字符串中的char[] 每个索引的元素的相同就返回true。

注:要区分一下哦。引用和对象不是一个东西。引用是存放在栈中的,内存地址固定。而对象是存在于堆中的。

注:instanceOf是判断这个对象是不是右边类的实例。

java存放数据的地方有如下这么几个地方(JVM的东西java基础搞定了在写笔记)

1.寄存器。也就是最快的地方,属于处理器的内部。比内存还快。

2.栈。仅次于寄存器。入栈就是开拓一个新的内存地址。出栈便是释放内存地址。有个类似指针的东西来控制访问、存的是基本数据类型的值   对象的引用。 真正的对象,也就是Object的子类对象存放的位置是堆内存中(以及常量池)。线程私有。

3.堆。存放的是对象。也就是最占地方的。线程共享。

4.常量池。也就是不可变的字符串。这个是线程共享的。

5.程序计数器PC。存放的是下一条指令的地址。遍历的是字节码文件。

6.静态区。在程序初始化时会最先初始化静态变量。有特定位置存放静态数据。而不是堆栈中。

说一下==和equals的区别以及hashcode吧。

一、 ==

1、首先 == 这个操作符在基本数据类型中可以理解成值是否相等,对于 int a = 10;  double b = 10.0  a==b  true

==会将a和b转成相同的数据类型进行比较。

byte boolean  char  short  int float long double       boolean取值为false,true默认是false

1  、 1、           2、     2、 4、 4、  8、    8   byte字节  取值范围就是 2的字节数*8(一个字节=8B)次幂,再看符号位。

对于char无论是中文还是英文一个字符就是占2个字节。

对于整型。+ 操作符会转成int操作。  byte b = (byte) 128 ; 这个会溢出的。取补码,也就是二进制取反+1;  输出-128

int a = 1000;
long b = 1000;
double c = 1000.0;
double d = a;
int e = (int) c;    //高向低转需要强转。

2、对于Object的子类,==表示的是是否为同一个对象。即存放在堆内存中的地址是否相等。

二、equals

首先equals是只有对象才能用的。基本数据类型不能用(不要混淆基本数据类型和包装类),因为这个是Object的方法。

下面是Object最初的equals方法。子类需要重写来定义相等的情况

public boolean equals(Object obj) {
    return (this == obj);
}

可以看出。原始的equals相当于==

说到equals,把包装类顺便也说了吧。0.0   包装类与基本数据类型最大的区别就是它允许存NULL值

首先对于包装类型。对于数值型都有一个父类就是Number.class。  加上Character  Boolean 构成

public static boolean parseBoolean(String s) {
    return ((s != null) && s.equalsIgnoreCase("true"));
}
public Short(String s) throws NumberFormatException {
    this.value = parseShort(s, 10);
}

有个共同的特征。除了Character的构造是char,其余的构造都能传String,然后在parse转换。

而对于转成基本数据类型。下面是Short的源码、

int a = 10;
Integer aa = Integer.valueOf(a);  //包装类与基本数据类型的转换。 相当于自动装箱。

Integer b = new Integer(10);

int bb = b;    // 相当于Integer.intValue()  自动拆箱。   Integer内部有个int类型的value变量。这个方法把这个value返回了

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
public int intValue() {
    return value;
}

对于包装类中的equals。

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
       return value == ((Integer)obj).intValue();//value变量是类内部的int型 再把入参强转+拆箱。再用基本数据类型==比较
    }
    return false;
}

所以包装类 == 是比较是否为同一个对象(或者在常量池范围内)。equals就是看值相不相等。而包装类与基本数据类型的比较就是先拆箱再比较。对于包装类的常量池,每个类不一样,统共就那几个。Integer a = 127; Integer b = 127 true; 换成128就是false

Byte--Long -128~127  Character 0~127  Float Double没有。 Boolean true false

而上文的double,int案例就不存在了。因为equals会判断是否是integer类的实例。

个人认为:基本数据类型是存在栈中的,比较快。而包装类为基本数据类型提供了基于对象的操作。

三、hashCode   参考:Java hashCode() 和 equals()的若干问题解答 - 如果天空不死 - 博客园

1.没涉及到Hash存储结构。此时Hashcode没啥实际意义。没涉及到Hash的情况equals等就ok了。

2.涉及到HashMap等存储结构。Hashcode的值相等时还得equals相等才能算作等。光Hashcode值相等还有哈希冲突的情况。

泛型作为key时,必须Hashcode和equals都重写、hashcode要是不等,就相当于存到俩个位置了。Hash相等再判断equals

看下HashMap中node的equals源码吧。

public final boolean equals(Object o) {
    if (o == this)
        return true;
    if (o instanceof Map.Entry) {
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        if (Objects.equals(key, e.getKey()) &&     //注意这个是Objects不是Object
            Objects.equals(value, e.getValue()))
            return true;
    }
    return false;
}
public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));   //此时比较equals就是泛型重写的equals了。

接着回到String

emmm。这个时候得提一下对象的创建方式

对象有如下这么几个创建方式

1.new 这个很容易,只要构造方法不是private。外部就能随便new(不要抬杠说protected..)

2.反射(下篇文章会介绍) Class.newInstance。或者用Class的构造来创建

3.clone对于实现Cloneable接口的类可以使用这个方法来创建对象。

4.反序列化。在反序列化的时候可以用ObjectInputStream来读取对象。转换成指定的类。

String s = "abc";   String s = new String("abc"); 这是String的创建方式。比较特殊

String s = "abc"; 这个是将s这个引用存放到栈内存中,并在常量池中查找"abc"。查到就返回,没查到就new一个放进常量池。这种如果常量池中存在就不必创建一个新对象了。常量池用来存储运行中创建的字符。String有个intern方法调常量池的引用

String s = new String("abc");这个就是在堆中创建这么一个对象。堆中不要求唯一,创建就开辟一块新地址。

对于这个例子。String s = "abc"; String s1 = "a"+"bc";    s==s1  true   //本处拼接没有用原生引用。是常量池中引用拼接

对于这个例子。String s = "a"; String s1 = "bc"; String s2 = "abc";  s2==(s+s1); 返回的是false;因为s+s1是原生引用拼接。jvm在编译阶段会进行优化,生成一个StringBuffer对象,对每个+连起来的做append操作,最后toString成一个新的对象,放堆里。下面讲一下反编译过程。

下图为StringBuffer的toString()方法。不用管别的行,看673行就知道new了一个新的对象扔堆里了。

tips:堆里面的对象没有被引用,慢慢就被GC回收了。(jvm文章会详细说明)

对于如上这个操作,我们可以用javap反编译来解释。

先放一下源文件代码给大家瞅一眼

在class文件处。shift+鼠标右键打开终端。我把system的部分解释去掉了。

这俩图已经表示的很明显了。

简单总结一下。对于String的equals比较各个位置的字符是否都相等。 == 在没有new或者栈引用拼接时候就相等。

这个是String中重写的Hashcode方法

接下来说一下String中的getBytes方法。因为这个面试时候有被问过。 对于编码就不做过多介绍了。在写代码时候涉及到编码时一定要指明编码格式。否则放到另一个平台就会乱码。windows下GBK ,unix默认是iso

可以看到。一个中文字符GBK占2,utf-8占3,ISO占1

关于String还有最后一个问题。使用final是为什么。

首先final是   private final char value[];  来实现的、引用不可变。

String s = "abc";  s= "def"; 这个是换栈中s位置存放的常量池中的引用。而自身引用是不变的。变得是引用中存放的东西。

String设计成final的为了保证线程安全。并且使用常量池,使操作的效率变高。

上图是对比。对于StringBuffer,不想把之前的值改变。但是事与愿违。

关于String介绍的差不多了。接下来说说StringBuffer和StringBuilder

对于三者的区别。

1.执行速度  StringBuilder>StringBuffer>String 

2.StringBuffer是线程安全的,StringBuilder不是。而String是常量。

多线程用StringBuffer,总操作字符用StringBuffer或者StringBuider。字符操作少用String。(我代码里还没用过StringBuilder。。)

StringBuffer的操作保证线程安全,方法都带了个synchronized

他俩都继承于AbstractStringBuilder 2倍+2扩容。一个线程安全一个不安全,一个快一个稍慢。

void expandCapacity(int minimumCapacity) {    //AbstractStringBuilder 的扩容方法
    int newCapacity = value.length * 2 + 2;
    if (newCapacity - minimumCapacity < 0)
        newCapacity = minimumCapacity;
    if (newCapacity < 0) {
        if (minimumCapacity < 0) // overflow
            throw new OutOfMemoryError();
        newCapacity = Integer.MAX_VALUE;
    }
    value = Arrays.copyOf(value, newCapacity);
}

方法都差不多。看看API就好了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值