一些java中集合类及其子类的总结,本文章一直将在更新中

hashmap的:

首先hashmap是基于map的hash实现,map是接口的第一个实现类,在map中定义了数据对,也就是map所存储的数据都是<key,value>类型的,key和value一一对应,在hashmap中引用hash算法来实现和key和value的一一对应,hashmap中在jdk1.7中的底层实现原理是使用数组和链表的实现方式,其中每数组的每一个元素代表一个桶,当然这个桶其实就是hash计算中的key的值虽对应的位置,之所以说它是桶是因为在该位置存储着一个链表这个链表存储着key对应的value值,hash计算并不能保证所有的key都能取到一个与其他元素不一样的位置,所以必然存在一定的hash冲突,当这个桶中已经存在一个数之后再计算对应到这里的value将会以头插法的方法插入到链表的头部,在jdk1.7之后他的底层实现原理是使用数组+红黑树+链表的实现方式,之所以这样设计是因为这是考虑性能问题,在之前的实现方式中由于是使用单链表来实现解决hash冲突的这就会有一个问题当你get方法获取元素的时候你就会进行再进行一次遍历如果冲突很多的话这将对性能造成极大的影响,而使用红黑树之后利用红黑树的自调整特性使得遍历值的过程不那么久了效率大大提升,(注:这里当链表的值大于8后才使用红黑树不然还是仍然使用链表只不过这里是使用的链表的插入方法是尾插法,具体为什么这样做笔者也不知道,笔者猜想可能和加入了红黑树之后链表转红黑树的时候比较容易吧)。

ps:hashmap的hash函数设计是基于使用与操作来时实现的,而hashtable是使用和length进行模运算然后取余的方法实现的,这样设计的原因是hashmap的长度一般是2的n次方,只能进行与操作才能减少hash冲突不然若果使用模运算然后取余的方法将会产生大量的冲突,而另一个原因就是这样做可以提高效率,在计算机中进行与炒作只需要进行简单的操作就可得出结果如果使用模运算然后取余的方法将会进行大量的运算这些运算都不利于性能的提升,而hashtable的长度是一个素数,一个奇数,这在使用模运算然后取余的方法后将会很少产生hash冲突,hashtable的初始长度为11,以后每次kongrong、扩容为原来的2*length+1,也是素数,保证他的hash运算的特性。

put方法:

看源码:

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

可以看到底层调用的是putVal()方法

再来看看putVal()方法:

 /**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

 该方法中有Node<K,V>[]tab; Node<K,V> p;

tab就是数组,而p是每个桶

如果tab刚开始是null或者大小为0,则进行扩容操作resize(),返回值为Node<K,V>[],直接赋值给tab,初始化tab。

初始化之后通过位与运算(求余)找到put的index,如果该位置没有元素也就是tab[index]==null,那么tab[i] =newNode(hash, key, value, null);即put成功

当然我们知道hash冲突是有的,所以当tab[index]!=null时,也就发生了hash冲突

第一个if其实考虑的是重复键,第二个if我们可以看到绿色的注释说的是在map中已经存在key了,所以这两步是对于已有key情况下的节点put的一个处理。

如果不是重复的,那么就看p是否是树节点,因为jdk1.8中采用的是红黑树,所以要考虑树节点,如果是树节点就进行树节点的put,e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key,value);对于树节点的插入我们这里就不多做解释了

如果上述情况都不是,那就是hash冲突并且使用链表处理了;。

get方法:

get()方法的源码:

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
 
    /**
     * Implements Map.get and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @return the node, or null if none
     */
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

get方法比较简单

主要是getNode(int hash,Object key)

直接判断hashmap中的桶是否为空,并且看tab[index]是否为空,如果为空则返回null

否则检查tab[index]处Node的属性,看key是否相等,相等返回改Node,不是则遍历该桶中的节点。

利用first.next遍历,如果是树节点则getTreeNode(hash,key),是链表节点的话遍历链表寻找。

array总结:

array类似于c语言中的数组,

array有几种操作方法分别是:

函数 复杂度 描述
array.push(element1[, …[, elementN]]) O(1) 将一个或多个元素添加到数组的末尾
array.pop() O(1) 移除数组末尾的元素
array.shift() O(n) 移除数组开头的元素
array.unshift(element1[, …[, elementN]]) O(n) 将一个或多个元素添加到数组的开头
array.slice([beginning[, end]]) O(n) 返回浅拷贝原数组从 beginning 到 end(不包括 end)部分组成的新数组
array.splice(start[, deleteCount[, item1[,…]]]) O(n) 改变 (插入或删除) 数组

 特别注意在数组的末尾添加元素或者删除元素时间复杂度都是o(1)的而在数组头部插入和删除都是哦(n)的,因为在末尾进行这些操作不需要移动数组,而在开头进行这些操作则需要移动元素所以时间复杂多就是o(n).

arrayList

常用的方法:add(),remove().

在遍历ArrayList的时候一般使用iterator进行遍历而不使用for循环来遍历可大大提高效率。

大小默认是十,当数据大于十后将进行自动扩容,扩容后大小变为length*1.5;

LinkList
常用方法:

add(E),

addlast(E),

add(index,E):该方法添加元素时先判断元素是看尽链表头还是尾,靠近那你就从哪一端开始插入

get(index),

remove(E);

Stack:栈

继承自Vector栈先进后出的结构,默认大小为10,超出十个将自动扩容,栈的应用十分广泛,在jvm中用来保存线程的一些变量和方法的出口或者一些动态链接等,在编译器中也有很好的作用,比如做计算,做语法分析比较优先级等

常用方法:pop(),push()

Queue:队列

与Stack的区别在于, Stack的删除与添加都在队尾进行, 而Queue删除在队头, 添加在队尾.

常用方法:put(),take()。

当元素被取出后, 并没有对数组后面的元素位移, 而是更新takeIndex来指向下一个元素.

takeIndex是一个环形的增长, 当移动到队列尾部时, 会指向0, 再次循环.

LinkedHashmap:在hashmap中维持一个链表来记录的插入顺序,每一次进行元素的增减的时候都会同时对链表进行操作。

removeEldestEntry 删除最老的元素

各个集合的一些具体数据结构的区别和比较

poll()方法和remove()方法区别?

poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。

LinkedHashMap和PriorityQueue的区别

PriorityQueue 是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。

WeakHashMap与HashMap的区别是什么?

WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。

ArrayList和LinkedList的区别?

最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。

ArrayList和Array有什么区别?

  1. Array可以容纳基本类型和对象,而ArrayList只能容纳对象。

  2. Array是指定大小的,而ArrayList大小是固定的

ArrayList和HashMap默认大小?

在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的代码片段

private static final int DEFAULT_CAPACITY = 10;

//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

 

Comparator和Comparable的区别?

Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。

如何实现集合排序?

你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合,如 list,然后通过 Collections.sort() 来排序。

如何打印数组内容

你可以使用 Arrays.toString() 和 Arrays.deepToString() 方法来打印数组。由于数组没有实现 toString() 方法,所以如果将数组传递给 System.out.println() 方法,将无法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。

LinkedList的是单向链表还是双向?

双向循环列表,具体实现自行查阅源码.

TreeMap是实现原理

采用红黑树实现,具体实现自行查阅源码.

遍历ArrayList时如何正确移除一个元素

该问题的关键在于面试者使用的是 ArrayList 的 remove() 还是 Iterator 的 remove()方法。这有一段示例代码,是使用正确的方式来实现在遍历的过程中移除元素,而不会出现 ConcurrentModificationException 异常的示例代码。

什么是ArrayMap?它和HashMap有什么区别?

ArrayMap是Android SDK中提供的,非Android开发者可以略过. 
ArrayMap是用两个数组来模拟map,更少的内存占用空间,更高的效率.

猜你喜欢

转载自blog.csdn.net/qq_30675777/article/details/81512535
今日推荐