• 常用集合和方法


    常用集合

    set

    Set不按特定方式进行排序,并且没有重复的对象,它的有些实现类能对集合中的对象按照特定的顺序排序。主要有两个实现类:HashSet和TreeSet

    HashSet按照哈希算法来存取集合中的对象,存取速度比较快。

    TreeSet实现了SortSet接口,具有排序功能。

    List

    List主要特征是以线性方式存储,集合中允许存放重复对象主要实现类包括ArrayList和LinkList

    ArrayList代表长度可变的数组,允许对元素进行快速的随机访问,但是向ArrayList中插入与删除元素较慢。

    LinkList在实现中采用链表的结构,插入和删除元素较快,随机访问则相对较慢。

    LinkList单独具有addFirst(),addLast(),getFirst(),getLast(),removeFirst(),removeLast()等方法这些方法使得LinkList可以作为堆栈,队列和双向队列使用

    Queue

    Queue队列中的对象按照先进先出的规则来排列。在队列末尾添加元素,在队列的头部删除元素。可以有重复对象。

    双向队列Deque则允许在队列的尾部和头部添加和删除元素。

    因为LinkList可以作为双向队列使用,所以Queue和Deque使用比较少

    Map

    HashMap不保证映射的顺序,特别是它不保证该顺序恒久不变。(也就是无序)

    HashMap底层基于散列表实现,由数组加链表组成,数组是HashMap的主体,链表用来解决Hash冲突

    采用key的hashCode()方法结合数组长度通过无符号右移>>> 异或^ 计算出数组索引,通过链表法解决哈希冲突(如果key值equals()则替换旧的value),

    平方去中法 取余数 伪随机数法

    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

    // aka 16 默认初始容量是2的n次幂,可以减少哈希碰撞3&(8-1)=3 2&(8-1)=2 | 3&(9-1)=0 2&(9-1)=0

    DEFAULT_LOAD_FACTOR=0.75 代表数组满的程度,如果小 则数组利用率不高,太大 则更容易产生Hash碰撞(每个桶的链表会变多)

    JDK1.8之后链表长度超过8,并且数组长度大于64会进化成红黑树(红黑树查询O(logn) 链表O(n))

    TreeMapTreeMap基于红黑树(Red-Black tree)的 NavigableMap 实现,有序。

    HashMap是使用hashCode和equals方法去重的。而TreeMap依靠Comparable或Comparator来实现key去重

    并发类容器(集合)

      在java集合框架中,Set List Queue Map的实现类都没有采取同步机制。在单线程环境中,这种实现方式会提高操纵集合的效率,java虚拟机不必会因为管理用不锁而产生额外的开销。

    在多线程环境中,可能会有多个线程同时操纵一个集合,比如一个线程在为集合排序,而另外一个线程正不断的向集合中添加新的元素。

    为了避免并发问题,可以直接采用java.util.concurrent并发包提供线程安全的集合。列如

      ConcurrentHashMap  、ConcurrentSkipListMapConcurrentSkipListSetConcurrentLinkedQueue

    这些集合的底层实现采用了复杂的算法,保证多线程访问集合时,既能保证线程之间的同步,又具有高效的并发性能。

      Hashtable:

        Hashtable,用作键的对象必须实现 hashCode 方法和 equals 方法。HashTable 由于所有方法都加了 synchronized 关键字所以是线程安全的。

        如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。 

    历史集合类 :Vector Hashtable Stack Enumeration 在实现中都采用了同步机制,并发性能较差,因此不提倡使用它们。

      ConcurrentHashMap:

    JDK7:ConcurrentHashMap采用了分段锁的,把容器默认分成16段,put值的时候 只是锁定16断中的一个部分,就是把锁给细化了

    JDK8:

    疑问:HashMap有线程安全的ConcurrentHashMap 但是TreeMap为什么没有ConcurrentTreeMap

         因为CAS操作用在红黑树实现起来太复杂 

       所以用ConcurrentSkipListMap用CAS实现排序(跳表代替Tree) 

       跳表:在链表的基础上一层一层的加一些个关键元素的链表,加了个索引。跳表的查找效率比链表本身要高,同时它的CAS实现难度比TreeMap容易很多

    常用方法(Object类)

    equals

      equals()方法判断两个对象是否“相等”。如果不重写这个方法,equals两个对象 除非是同一个引用,否则一直不相等(返回false)。

      Eclipse默认给我们重写的equals()方法,是对象的所有成员变量是否都相等,如果该对象和比较对象的成员变量都相等 则两个对象互相equals() 也就是相等。

      我们都知道Set集合存放的对象都是不可重复的,Set集合就是根据对象的equals方法判断对象是不是重复

      注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

    hashCode

      返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。

      如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。

      如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

        ——摘自API,大家也就理解了 为什么eclipse中 equals和HasdCode方法给我们的快捷键中,必须一起重写。

    toString

       Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和

            此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于

       public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }

    以上是JDK默认toString方法的实现和API解释,可见Object类默认的toString方法和equals方法都和对象的哈希码有关

    重写toString方法可以让我们更直观的理解对象的内容属性。

    getClass

    反射可以拿到一个对象的成员变量的值,但方法的参数是出现在方法被调用的时候,
    你拿到了class,method,参数类型等这些都没用,这些都是静态的,固定的。

    也就是说反射拿不到参数的值

    String StringBuffer和StringBuilder

    StringBuffer代表可变的字符序列,往StringBuffer字符串加东西,直接相加。

    String代表不可变字符序列  当我们执行两个字符串相加时 需要分配另外一块内存 然后 执行两次copy。最后把字符串的指针执行新的那块内存。

    StringBuilder和StringBuffer类似 区别是StringBuilder线程非安全。

    HashMap为什么是线程不安全的

    1.首先我们解释下HashMap的扩容:当HashMap的元素个数超过:数组长度乘以负载因子DEFAULT_LOAD_FACTOR(16*0.75)=12时,就会把数组长度扩大为16X2=32

    将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。

    这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

    这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为<原下标+原容量>的位置(JDK1.7重新计算Hash值 1.8巧妙的采用了位运算)

    2.重新调整HashMap大小存在什么问题吗?

    当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。

    在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。

    如果条件竞争发生了,那么就死循环了。(多线程的环境下不使用HashMap)

    推荐简述:https://www.jianshu.com/p/ecd75ea6ee5a HashMap多线程下死循环的问题 

    关于死循环的问题,在Java8中个人认为是不存在了,在Java8之前的版本中之所以出现死循环是因为在resize的过程中对链表进行了倒序处理;在Java8中不再倒序处理,自然也不会出现死循环。

  • 相关阅读:
    2016年蓝桥杯B组C/C++决赛题解
    2016年蓝桥杯B组C/C++决赛题目
    线段树区间更新 费马小定理|魔豆传奇
    2015年蓝桥杯B组C/C++决赛题解
    欧拉线性筛 与 欧拉函数 + 几道例题
    2015年蓝桥杯B组C/C++决赛题目
    2017年蓝桥杯B组C/C++决赛题解
    2017年蓝桥杯B组C/C++决赛题目
    2019 蓝桥杯国赛 B 组模拟赛 题解
    2018年蓝桥杯B组C/C++决赛题解
  • 原文地址:https://www.cnblogs.com/ssskkk/p/9367524.html
Copyright © 2020-2023  润新知