阅读 284

HashMap 和ConcurrentHashMap总结

String x="abc"; 和String x=new String("abc") 区别

String x="abc" 内存会去查找永久代(常量池),如果常量池没有则开辟新的区域,将x指针指向此区域地址,如果有此常量,则直接指向此常量地址。

而String str = new String("abc");是在堆中从新new一块儿内存,把指针赋给栈,将新构造出来的String对象的引用赋给str。 因此 只要是new String(),则,栈中的地址都是指向最新的new出来的堆中的地址。

注: String Integer 等重写了 equals方法

 	String s3 = new String("hello");
        String s4 = "hello";
        System.out.println(s3 == s4);// false
        System.out.println(s3.equals(s4));// true

        String s5 = "hello";
        String s6 = "hello";
        System.out.println(s5 == s6);// true
        System.out.println(s5.equals(s6));// true
        
          String s1 = "hello";
        String s2 = "world";
        String s=s1+s2;
        String s3 = "helloworld";
        System.out.println(s3 == s1 + s2); //false 两个变量相加会先在常量池中开辟空间,再相加,再存放到此空间
        System.out.println(s == s3);  //false
        System.out.println(s3 == "hello" + "world");  //false  //两个常量相加会先相加得到新的常量,再去常量池比较是否存在
复制代码

为什么重写equals时必须重写hashCode方法?

Object的hashcode方法是本地方法,也就是用c语言或c ++实现的,该方法通常将对象的内存地址转换为整数之后返回。 重写的hashCode 实质内容和对象每个属性的hashCode()值相关

 public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }
复制代码

重写equals时必须重写hashCode方法只和hash容器有关,如果确保此对象不会被加入到hash容器,则不必重写hashCode(),hashCode() 在散列表中才有用,在其它情况下没用,当两个equals相等的对象没有重写hashCode()被加入到hashSet中,由于hashSet底层是hashMap set中的元素作为Map中的键,所以set加入第二个对象时,会通过对象hashcode值来判断对象加入的位置,如果没有重写的话是一个全新的hashCode值,则会加入到set中,此时则不是想要的结果。(没有重写hashCode则不能根据键的hashCode值来正确的确定在散列表的位置)

java 中只有值传递

public static void main(String[] args) {
   int num1 = 10;
   int num2 = 20;

   swap(num1, num2);

   System.out.println("num1 = " + num1);
   System.out.println("num2 = " + num2);
}

public static void swap(int a, int b) {
//调用时候都相当int a=10,b=20
   int temp = a;
   a = b;
   b = temp;

   System.out.println("a = " + a);
   System.out.println("b = " + b);
}
//输出
a = 20
b = 10
num1 = 10
num2 = 20
复制代码
 public static void main(String[] args) {
        int[] arr = { 1, 2, 3, 4, 5 };
        System.out.println(arr[0]);
        change(arr);
        System.out.println(arr[0]);
    }

    public static void change(int[] array) {
        // 将数组的第一个元素变为0
        array[0] = 0;
    }
  //输出
 	 1
	0
复制代码
public class Test {

  public static void main(String[] args) {
      // TODO Auto-generated method stub
      Student s1 = new Student("小张");
      Student s2 = new Student("小李");
      Test.swap(s1, s2);
      System.out.println("s1:" + s1.getName());
      System.out.println("s2:" + s2.getName());
  }

  public static void swap(Student x, Student y) {
      Student temp = x;
      x = y;
      y = temp;
      System.out.println("x:" + x.getName());
      System.out.println("y:" + y.getName());
  }
}
//输出
x:小李
y:小张
s1:小张
s2:小李
复制代码

hashMap 源码

1.7

默认数组容量 16

默认加载因子 0.75f

阈值 = 容量*加载因子

构造方法

调用this方法 赋值

  1. put过程

1.1 判断数组是否为空,为空 初始化空的数组

首先确定数组容量为>=传入的容量值 且 是2的幂次方,

注:因为后续计算数组下标公式是hashCode & (length-1),在与运算中为了让下标在0-length-1范围内,lenth-1 是一个必须高位全为0,低位全为1的数,推算 length必须是只有1位是1其他位是0,也就是2的幂次方。

计算阈值

new 数组

1.2 hash(key) 方法获得一个hash值 ,内部通过异或和右移让原始hashCode高位参与运算,提高hashCode的散列性

1.3 indexfor 方法得到存放的数组下标

1.4 遍历从arr[i]开始的链表 如果当前节点的hash值和刚才计算出的hash值相等且(key的内存地址相等或内容相等),则新的value覆盖旧的value,且返回旧的value

1.5 modCount++ 改变次数+1,和异常有关,在遍历会执行expectCount=modeCount,在hashMap.remove()时modCount++,在判断modeCount==expectCount会抛出异常,是一种fast-fail机制,因为hashMap认为在遍历的过程中增减操作时可能存在风险,不允许这么做。

1.6 如果没有相等的则执行addEntry(hash,key,value,i)方法

判断 size > 容量*加载因子 && table[i] !=null

数组扩容

新建一个两倍容量的数组,将数据转移到新的数组,计算阈值

转移方法 链表上的每个节点重新计算出的i可能和原来的i相同或是原来的两倍,所有扩容的目的是数组变长,链表变短

注: 为什么说hashMap是线程不安全的,因为多线程下转移方法中指针指向会出现问题,形成循环链表,耗尽资源

创建节点

头插法并且向下移动,size+1

ConcurrentHashMap

1.7

注 hashTable 也是线程安全的,内部在每一个操作上都加了synchronized 关键字

构造器 (默认参数16 0.75f 16)

DEFAULT_CONCURRENCY_LEVEL :segment 数量

DEFAULT_INITIAL_CAPACITY : hashEntry 数量

算出来每个segment中有一个hashEntry

segment 数量最多为16个

确定segment数量 真正的segment数量是 <传入的level && 是2的幂次方 (1,2,4,8,16)

确定hashEntry数量 每个hashEntry 也必须是2的幂次方 且 > capacity/level

new Segment[]

在索引为0的位置放入segment对象

put的时候 用cas方式只允许一个线程去修改数组

文章分类
后端
文章标签