首页 > 编程语言 > 详细

Java基础汇总(二)

时间:2021-05-19 01:06:46      阅读:21      评论:0      收藏:0      [点我收藏+]

Java集合

1.ArrayList和LinkedList

  • 是否线程安全:两者都不是线程安全的
  • 底层数据结构:Object数组,双向链表
  • 是否支持快速随机访问
  • 插入删除元素速度
  • 空间占用:ArrayList尾部会预留一定的容量

RandomAccess接口

public interface RandomAccess {
}

RandomAccess是一个标志性接口,没有任何定义。标志实现这个接口的类具有随机访问的功能。

binartSearch()方法中,会判断传入的list是否RandomAccess实现

2.ArrayList,Vectors,CopyOnWriteArrayList

CopyOnWriteArrayList

  • 实现了List接口
  • 内部持有一个ReentrantLock lock = new ReentrantLock();
  • 底层是用volatile transient声明的数组 array
  • 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array

增删改都需要获得锁,并且锁只有一把,而读操作不需要获得锁,支持并发。

CopyOnWriteArrayList为什么并发安全且性能比Vector好?

我知道Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。

3.HashSet,CopyOnWriteArraySet

HashSet

  • HashSet实际上就是HashMap,只是将value设置成null
  • HashSet中的元素不重复的
  • HashSet中的元素是无序的

CopyOnWriteArraySet

HashSet对应的并发类为什么叫CopyOnWriteArraySet,而不是叫CopyOnWriteHashSet呢?
public class CopyOnWriteArraySet<Eextends AbstractSet<E>
        implements java.io.Serializable 
{
    private static final long serialVersionUID = 5457747651344034263L;

    private final CopyOnWriteArrayList<E> al;

    /**
     * Creates an empty set.
     */

    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
    ...
}

CopyOnWriteArraySet的底层存储结构竟然是CopyOnWriteArrayList,那么我们就可以知道它的名字的由来了,并且知道它支持并发的原理跟CopyOnWriteArrayList是一样的。

4.ConsurrentHashMap,HashMap,Hashtable

HashTable

  • 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低。
  • 初始size为11,扩容:newsize = olesize*2+1
  • 计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length

ConcurrentHashMap

  • 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶 数组进?了分割分段(Segment),每?把锁只锁容器其中?部分数据,多线程访问容器?不同数 据段的数据,就不会存在锁竞争,提?并发访问率。
  • 到了 JDK1.8 的时候已经摒弃了Segment的 概念,?是直接? Node 数组+链表+红?树的数据结构来实现,并发控制使? synchronized 和 CAS 来操作,每次对一个桶上锁。
  • synchronized只锁定当前链表或红??叉树的?节点,这样只要hash不冲突,就不会产?并发,效率? 提升N倍

5.HashMap

参数

  • DEFAULT_INITIAL_CAPACITY = 1<<4 // 缺省容量
  • MAXIMUM_CAPACITY = 1 << 30 // 最大容量
  • DEFAULT_LOAD_FACTOR = 0.75 // 缺省装载因子
  • TREEIFY_THRESHOLD = 8 // 将链表变成红黑树的阈值
  • UNTREEIFY_THRESHOLD = 6 // 将红黑树变回链表的阈值
  • MIN_TREEIFY_CAPACITY = 64 // 会转换成红黑树的最小容量,就是说如果map的容量低于64, 就算一个桶里面数量大于8也不会转换成红黑树
  • size // 容量
  • modCount // modify的次数
  • threshold // 扩容阈值,超过这个阈值,将扩容
  • loadFactor // 装在因子

Node

  • hash
  • key
  • value
  • next

注意点

  • 在put的时候,会对key进行一个hash,但并不是直接将key的hashCode进行hash,而需要进行一步处理
  // 注意在hash过程中,会进处理。(h = key.hashCode()) ^ (h >>> 16); 相当于在散列过程中同时考虑高16位的信息
  static final int hash(Object key) {
      int h;
      return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  }
  • jdk 1.8 与 1.7 的区别 : Entry-> Node, 添加红黑树, 头插改成尾插

头插改成尾插的原因是:为了防止resize过程中形成循环链表

  • 设置成技术分享图片的目的
  1. 在hash的过程中,需要对size取模,而如果size的长度是技术分享图片,则可通过技术分享图片的计算方法得到。

  2. 创建Map过程中,初始的size不一定是技术分享图片, 如果不是,则会进行处理:

  // 这是java11中的写法,与java8中有所不同
  static final int tableSizeFor(int cap) {
     int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
     return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  }
  1. resize过程中需要rehash, 因为扩容成原来的两倍,所以原本桶中的Node有两种rehash可能:留在原桶;rehash到index+oldCap的位置,而判断的方法只需要对于原来的hash值在进行一次hash,&oldCap即可:举个例子,如果oldCap=8 (oX1000),则扩容后是16,原本在桶7位置的只需要判断是放在7还是放在15,所以只需要将hash &= oldCap 即可。
  • key为null时

key如果为null,会默认将hash为0,hash后放在桶0中。

注意:在ConcurrentHashMap中是不能插入key为null的,ConcurrentHashMap不能put null 是因为 无法分辨是key没找到的null还是有key值为null,这在多线程里面是模糊不清的,所以压根就不让put null。

6.如何选用集合

主要根据集合的特点来选?,?如我们需要根据键值获取到元素值时就选?Map接?下的集合,需要排序时选择TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选?ConcurrentHashMap.当我们只 需要存放元素值时,就选择实现Collection接?的集合,需要保证元素唯?时选择实现Set接?的集合 ?如TreeSet或HashSet,不需要就选择实现List接?的?如ArrayList或LinkedList,然后再根据实现 这些接?的集合的特点来选?。

Java基础汇总(二)

原文:https://www.cnblogs.com/yinjiacheng/p/14783048.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!