Java基础系列之字符串和数组

5.1 字符串创建与存储的机制是什么?

  1. 对于 String s1 = new String(“abc”) 与 String s2 = new String(“abc”)语句,存在两个引用对象s1和s2,两个内容相同的字符串对象“abc”,它们在内存中的地址是不同的。只要用到new总会生成新的对象。
  2. 对于String s1 = "abc"与String s2 = "abc"语句,在JVM中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,s1,s2引用的是同一个常量池中的对象。String的实现采用了 享元(Flyweight)的设计模式,当创建一个字符串常量时,例如String s = “abc”,会首先在字符串常量池中查找是否已经有相同的字符串被定义,判断依据是String类equals(Object obj)方法的返回值。由于String是不可变类,一旦创建好了就不能被修改,因此String对象可以被共享而且不会导致程序混乱。

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。

String s = "abc"; // 把“abc”放到常量区中,在编译时产生
String s = "ab" + "c"; // 把"ab" + "c"转换为字符串常量“abc”放到常量区
String s = new String("abc"); // 在运行时把“abc”放到堆里面

String s1 = "abc"; // 在常量区里面存放了一个"abc"字符串对象
String s2 = "abc"; // s2引用常量区中的对象,因此不会创建新的对象
String s3 = new String("abc"); // 在堆中创建新的对象
String s4 = new String("abc"); // 在堆中又创建了一个新的对象

String s = new String(“abc”);详细解释:
语句的执行可以分为两个阶段。第一个阶段是创建对象的过程,即new String(“abc”);第二个过程是赋值的过程,即String s =。第一个过程中new String(“abc”);会调用String的构造函数:

	public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

在这里插入图片描述
new String(“abc”);创建了几个对象?

一个或两个。 如果常量池中有“abc”,那么只创建一个对象;如果常量池中没有字符串“abc”,那么就会创建两个对象。

5.2 “==”、equals和hashCode有什么区别?

  1. “==”运算符用来比较两个变量的值是否相等。通常比较两个基本数据类型或两个引用变量是否相等。
  2. 对于赋值语句String s1 = new String() ,变量s占用一块内存空间,而new String()则存储在另外一块存储空间里,此时,变量s所对应内存中存储的数值就是对象占用的那块内存的首地址。如果要比较两个变量是否指向同一对象,即要看这两个变量所对应内存中的数值是否相等,这时就可以用“==”运算符进行比较。
  3. equals是Object类提供的方法之一。 每个Java类都继承自Object类,所以每个对象都具有equals方法。Object类中定义的equals(Object)方法是直接用“ == ” 运算符比较两个对象,所以在没有覆盖equals(Object)方法的情况下,equals(Object) 与 “ == ”运算符一样,比较的是引用。
  4. 相比“==”运算符,equals(Object)方法可以被覆盖,所以可以通过覆盖的方法让它不是比较引用而是比较数据内容。

Object类equals方法:

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

String类中重写Object类的equals方法:

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;
    }

如果一个类没有自己定义equals()方法,那么它将继承Object类的equals()方法。也就是 “ == ” 运算符,此时使用 “ ==” 运算符和equals()方法会得到相同结果。如果编写的类希望 比较实例的内容是否相等,那么必须覆盖equals()方法。

hashCode()方法:

hashCode()方法是从Object类中继承过来的,他也可以用来鉴定两个对象是否相等。Object类中的hashCode()方法返回对象在内存中地址转成的一个int值,所以如果没有重写hashCode()方法,任何对象的hashCode()方法都是不相等的。

equals()方法和hashCode()方法的区别:

一般来讲,equals()方法是给用户调用的,如果需要判断两个对象是否相等,可以重写equals()方法,然后在代码中调用就可以了。对于hashCode()方法,用户一般不会调用它,例如在hashmap中,由于Key是不可重复的,它在判断key是否重复时就判断了hashCode()方法,而且也用到了equals()方法。此处的“不可以重复”指的是equals()和hashCode()只要有一个不等就可以了。

覆盖约定:

一般在覆盖equal()方法的同时 也要覆盖hashCode()方法,否则,就会违反Object.hashCode约定,导致该类无法与所有基于散列值(hash)的集合类(HashMap、HashSet和Hashtable)结合在一起正常运行。

hashCode()方法的返回值和equals()方法的关系:

如果x.equals(y)为true,那么这两个对象的hashCode()方法的返回值必须是相同结果; 如果x.equals(y)为false,那么这两个对象的hashCode()方法的返回值可能相等,可能不相等。
反之,如果hashCode()方法的返回值不相等,一定能推出equals()方法的返回值也不相等,而hashCode()方法的返回值相等,equals()方法的返回值可能相等,可能不相等。

为什么不同的对象hashcode可能相同:

因为当输入数据量太大,哈希值却是固定32长度的,这意味着哈希值是一个有限集合,无法建立一对一关系,所以hashcode相等是有可能会发生的。

Object类中的hashCode()方法:

	public native int hashCode();

String类中重写Object类的hashCode()方法:

    public int hashCode() {
        int h = hash;
        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;
    }

Set里的元素是不能重复的,那么用什么方法来区分是否重复呢?

用equals()方法来区分是否重复。

5.3 String、StringBuffer、StringBuilder和StringTokenizer有什么区别?

Java语言有四个类可以对字符或字符串进行操作。Character(单个字符)、String、StringBuffer和StringTokenizer。
String类是不可变类,String对象一旦被创建,其值将不能被改变,适合在被 共享的场合使用;StringBuffer是可变类,当一个字符串经常被修改时,最好使用StringBuffer实现。使用String会生成许多无用的对象被垃圾回收器回收,从而影响性能。

String初始化,可以利用构造函数和赋值语句;而StringBuffer初始化只能使用构造函数。

String字符串的修改原理机制:
当用String类型对字符串进行修改时,其实现方法是首先创建一个StringBilder,其次调用StringBilder的append()方法,最后调用StringBuilder的toString()方法返回结果。

	String s = "Hello";
	s += "World";
	//以上代码等价于以下代码
	StringBilder sb = new StringBilder();
	sb.append("World");
package org.base;

public class Test {
	public static void testString(){
		String s = "Hello";
		String s1 = "world";
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			s += s1;
		}
		long end = System.currentTimeMillis();
		System.out.println("testString:" + (end - start));
	}
	
	public static void testStringBuilder(){
		StringBuilder s = new StringBuilder("Hello");
		String s1 = "world";
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			s.append(s1);
		}
		long end = System.currentTimeMillis();
		System.out.println("testStringBuilder:" + (end - start));
	}
	
	public static void main(String[] args) {
		testString();
		testStringBuilder();
	}
}

运行结果:testString:249
     testStringBuilder:1

StringBuilder和StringBuffer都是字符串缓冲区。StringBuillder是线程不安全的,如果只是在单线程中使用字符串缓冲区,那么StringBuilder效率高。当有多个线程访问时使用StringBuffer。因为StringBuffer必要时可以对这些方法进行同步。

StringTokenizer:分隔字符串的工具类。

package org.base;
import java.util.StringTokenizer;

public class Test {
	public static void main(String[] args) {
		StringTokenizer st = new StringTokenizer("Welcome to our country!");
		while (st.hasMoreTokens()) {
			System.out.println(st.nextToken());
		}
	}
}

运行结果:Welcome
     to
     our
     country!

5.4 Java中数组是不是对象?

数组是指具有相同类型的数据的集合,具有固定长度,并且在内存中占据连续的空间。在C/C++语言中,数组名是一个指针,指向数组的首元素,既没有属性也没有方法,在Java语言中,数组有自己的属性(length属性),也有一些方法可以被调用(clone方法)。这个角度来讲,数组是对象。

通过instanceof判断数据的类型:

package org.base;

public class Test {
	public static void main(String[] args) {
		int[] a = {1,2};
		int[][] b = new int[2][4];
		String[] s = {"a","b"};
		if(a instanceof int[])
			System.out.println("the type for a is int[]");
		if(b instanceof int[][])
			System.out.println("the type for a is int[][]");
		if(s instanceof String[])
			System.out.println("the type for a is String[]");
	}
}

运行结果:the type for a is int[]
     the type for a is int[][]
     the type for a is String[]

5.5 数组的初始化方式有哪几种?

Java中数据被创建后会根据数组存放的数据类型初始化对应的初始值(int类型会初始化0,对象会初始化null)。

一维数组:

int[] a = new int[5];
int[] b = {1,2,3,4,5};
int[] c;
c = new int[5];

二维数组:

int[][] a;
int b[][];
int[] c[];

原生类指未被实例化的类,数组一般指实例化、被分配空间的类。

5.6 length属性与length()方法有什么区别?

length()方法是针对字符串而言的,String提供了length()方法来计算字符串的长度。

除了length属性和length()方法外,Java中还提供了一个计算对象大小的方法–size()方法,该方法是针对泛型集合而言的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值