• 关于

    双向链表

    的搜索结果

回答

说一下我的见解:不一定对 仅供参考 首先 线性表分为顺序表和链式表 其中后者又可分为动态链表和静态链表 这两种链表又可进一步分为:单向无循环 双向无循环 单向有循环 双向有循环 应该说一般的排序算法在单链表都是可以的 插入排序 冒泡排序 选择排序 快速排序 堆排序 归并排序 基数排序 希尔排序 只是在不同的线性表中不同的算法会有效率上的不同 静态链表是比较适合需要做排序的 因为它既具有顺序表的顺序存取功能 又具有链式表易于移动元素的功能 Best Wishes!

小哇 2019-12-02 01:18:30 0 浏览量 回答数 0

回答

本期答案: 基本:使用双向链表记录 map entry,读写时把访问的 entry 从链表中间删除插入 head, 如果 map 满了从链表 tail 淘汰数据。/ 进阶:提到 map 和双向链表的并发访问保护机制。/ 骨灰:提到 LRU 的几类优化,提到 Redis 的优化。

答题小助手 2019-12-24 11:27:41 0 浏览量 回答数 0

问题

c++读取文件中的链表存取问题

a123456678 2019-12-01 20:11:22 992 浏览量 回答数 1

阿里云试用中心,为您提供0门槛上云实践机会!

0元试用32+款产品,最高免费12个月!拨打95187-1,咨询专业上云建议!

问题

把二元查找树转变成排序的双向链表(树)

问问小秘 2020-01-06 15:30:40 3 浏览量 回答数 1

回答

虽然是很久以前的问题了,但是也许以后会帮助到其他的同学。 双向链表相比于单向链表,所谓的O(1)是指删除、插入操作。 单向链表要删除某一节点时,必须要先通过遍历的方式找到前驱节点(通过待删除节点序号或按值查找)。若仅仅知道待删除节点,是不能知道前驱节点的,故单链表的增删操作复杂度为O(n)。 双链表(双向链表)知道要删除某一节点p时,获取其前驱节点q的方式为 q = p->prior,不必再进行遍历。故时间复杂度为O(1)。而若只知道待删除节点的序号,则依然要按序查找,时间复杂度仍为O(n)。 单、双链表的插入操作,若给定前驱节点,则时间复杂度均为O(1)。否则只能按序或按值查找前驱节点,时间复杂度为O(n)。至于查找,二者的时间复杂度均为O(n)。 对于最基本的CRUD操作,双链表优势在于删除给定节点。但其劣势在于浪费存储空间(若从工程角度考量,则其维护性和可读性都更低)。双链表本身的结构优势在于,可以O(1)地找到前驱节点,若算法需要对待操作节点的前驱节点做处理,则双链表相比单链表有更加便捷的优势。

游客nqewexvef7oyc 2019-12-02 02:09:27 0 浏览量 回答数 0

问题

[@talishboy][¥20]Java 中的 LinkedList 是单向链表还是双向链表?

李博 bluemind 2019-12-01 19:26:49 603 浏览量 回答数 1

回答

"LinkedHashMap使用双向链表来维护key-value对的次序(其实只需要考虑key的次序),该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。 LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能;但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。"

星尘linger 2020-04-12 09:29:55 0 浏览量 回答数 0

回答

先从 MySQL 的基本存储结构说起: MySQL的基本存储结构是页 (记录都存在页里边) : 各个数据页可以组成一个双向链表 每个数据页中的记录又可以组成一个单向链表: 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。 所以说,如果我们写 这样没有进行任何优化的sql语句,默认会这样做: 定位到记录所在的页:需要遍历双向链表,找到所在的页 从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了 很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。 索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对): 要找到id为8的记录简要步骤: 很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 “目录” 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn)) 其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。

pandacats 2019-12-23 10:28:46 0 浏览量 回答数 0

回答

逆序没什么特别的好处,给你学编程的时候练练手玩的,在实际的项目中会用到标准库,那是双向链表,没有逆序创建一说。要说逆序的好处:当要加入新的数据时,不需要遍历链表,可以直接在头结点之后插入即可,减少时间复杂度

a123456678 2019-12-02 02:01:41 0 浏览量 回答数 0

回答

输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。 要求不能创建任何新的结点,只调整指针的指向。    10   / /   6  14  / / / / 4  8 12 16  转换成双向链表 4=6=8=10=12=14=16。    首先我们定义的二元查找树 节点的数据结构如下:  struct BSTreeNode {   int m_nValue; // value of node   BSTreeNode *m_pLeft; // left child of node   BSTreeNode *m_pRight; // right child of node };

问问小秘 2020-01-06 15:31:01 0 浏览量 回答数 0

问题

LRU 缓存淘汰算法详解 5月21日【今日算法】

游客ih62co2qqq5ww 2020-05-21 14:02:03 16 浏览量 回答数 1

回答

数据结构 ziplist - 压缩列表:元素较少时,使用一块连续内存存储所有元素quicklist - 快速列表:元素较多时,将多个ziplist用双向指针链表串联起来,普通链表需要的附加指针空间太大,浪费空间和加重内存的碎片化 pop最后一个元素后,list自动删除,内存回收异步队列

1658458755422780 2020-03-31 15:26:13 0 浏览量 回答数 0

回答

例如 LRU 常见中用双向链表+哈希。

a123456678 2019-12-02 02:40:29 0 浏览量 回答数 0

问题

[C] 内核中的双向链表在上层应用是否会有问题?(list.h)

a123456678 2019-12-01 19:43:21 915 浏览量 回答数 1

问题

[C] 内核中的双向链表在上层应用是否会有问题?(list.h)

a123456678 2019-12-01 20:02:51 912 浏览量 回答数 1

回答

ArrayList使用了数组的实现,封装了对内部数组的操作 linkedList使用了循环双向链表数据结构

问问小秘 2020-01-03 13:45:37 0 浏览量 回答数 0

回答

ArrayList,LinkedList和Vector都继承自List接口。ArrayList和Vector的底层是动态数组,LinkedList的底层是双向链表.

游客u5e7zhl7hrshu 2020-01-09 15:22:49 0 浏览量 回答数 0

问题

双向链表为什么时间复杂度是O(1)

a123456678 2019-12-01 19:50:01 1501 浏览量 回答数 2

回答

  907数据结构与算法满分是150分。   考试科目代码及名称:907数据结构与算法   一、考试基本要求   本考试大纲适用于报考深圳大学计算机与软件学院学术型硕士的研究生入学考试。《数据结构与算法》是为招收计算机科学与技术学术型硕士和软件工程学术型硕士而设置的具有选拔功能的水平考试。它的主要目的是测试考生对数据结构与算法各项内容的掌握程度。要求考生熟悉常见的数据结构和算法,能根据实际应用选择合理的逻辑结构、存储结构及其相应的算法,并掌握算法的时间分析和空间分析技术。要求考生能够编写符合软件工程规范、结构清楚、正确易读的算法(程序)。   二、考试内容和考试要求 1、 基本概念 逻辑结构、存储结构、算法及三者之间的关系;算法的特征及设计目标;了解算法时间、空间需求的大O表示法 。 2、向量、链表、栈、队 向量(顺序表)、链表(静态链表、单链表、双向链表、循环链表)及相关算法;栈、队,了解其应用,理解递归;串及C语言中串的表示;串的模式匹配算法;了解多维数组的行优先和列优先的顺序存储;了解特殊矩阵(如上、下三角矩阵)的一维数组存储 ,了解广义表的表示。 3、树和二叉树 树(森林)、二叉树及其性质;两者的对应关系;二叉树的llink-rlink和完全二叉树的顺序存储法;二叉树遍历;赫夫曼(Huffman)树的构造及应用 。 4、图 图(网)的概念及其邻接矩阵和邻接表存储法;图的遍历、最小生成树、最短路径、拓扑排序、关键路径等算法 。 5、查找与排序 顺序查找、二分查找;二叉排序树、平衡二叉排序树及插入、删除时的平衡方法;B-树、B+树;哈希(Hash)表;了解查找成功及失败的平均查找长度;排序的概念及相关术语;“插入”、“希尔”、“起泡”、“快速”、“选择”、“堆”、“归并”、“基数”等排序算法;了解上述排序算法的时间复杂度、空间复杂度、稳定性;了解上述部分排序算法的适用场合 。   三、考试基本题型 主要题型包括:填空题、选择题、判断题、应用题、算法设计题等。试卷满分为150分。   

小哇 2019-12-02 01:23:55 0 浏览量 回答数 0

回答

名称 HashMap LinkedHashMap TreeMap 共同点 线程不安全 线程不安全 线程不安全 不同点 数据无序 数据有序 数据有序还可以对数据进行排序 数据结构 数组+链表+红黑树(在JDK1.8中如果链表长度大于8的时候才转换为红黑树,平常不是) 双向链表+HashMap 红黑树 1.HashMap是一个最常用的Map,它根据键的hashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为null,不允许多条记录的值为null。HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要同步,可以用Collections.synchronizedMap(HashMap map)方法使HashMap具有同步的能力。 2.LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的。在遍历的时候会比HashMap慢。有HashMap的全部特性。 3.TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。当用Iteraor遍历TreeMap时,得到的记录是排过序的。TreeMap的键和值都不能为空。

景凌凯 2020-01-09 13:45:34 0 浏览量 回答数 0

回答

1、ArrayList、LinkedList和Vector都是实现了List接口。2、ArrayList和Vector底层数组,下标访问,查找效率高,可以动态增加,访问性能高,但是新增修改删除排序不够灵活。3、LinkedList底层双向链表,查询一般、插入和删除效率高。4、Vector是同步访问线程安全的。ArrayList没有

徐雷frank 2019-12-02 01:42:28 0 浏览量 回答数 0

回答

1.具体区别如下: 我们在开发的过程中使用HashMap比较多,在Map中在Map 中插入、删除和定位元素,HashMap 是最好的选择。 但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。 如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列. HashMap是一个最常用的Map,它根据键的hashCode值存储数据,根据键可以直 接获取它的值,具有很快的访问速度。HashMap最多只允许一条记录的键为null, 允许多条记录的值为null。HashMap不支持线程同步,即任一时刻可以有多个线程 同时写HashMap,可能会导致数据的不一致性。如果需要同步,可以用Collections的 synchronizedMap方法使HashMap具有同步的能力。 Hashtable与HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的 同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtable在写入时会比较慢。 LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的 记录肯定是先插入的。 在遍历的时候会比HashMap慢TreeMap能够把它保存的记录根据键排序,默认是按升序排序, 也可以指定排序的比较器。当用Iterator遍历TreeMap时,得到的记录是排过序的。 2.LinkedHashMap简述: LinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和 输入时的相同,那么就选用LinkedHashMap。 LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现 提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保 证该顺序恒久不变。 LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双 重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。 注意:此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结 构上修改了该映射,则它必须保持外部同步。 根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。 默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素 移至链表尾部,不断访问可以形成按访问顺序排序的链表。 可以重写removeEldestEntry方法返 回true值指定插入元素时移除最老的元素。 3.LinkedHashMap的实现: 对于LinkedHashMap而言,它继承与HashMap、底层使用哈希表与双向链表来保存所有元 素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。 下面我们来分析LinkedHashMap的源代码: 类结构:public class LinkedHashMap extends HashMap implements Map 1) 成员变量: LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry, 该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而 在哈希表的基础上又构成了双向链接列表。看源代码: //true表示按照访问顺序迭代,false时表示按照插入顺序 private final boolean accessOrder; /** 双向链表的表头元素。 */ private transient Entry header; /** LinkedHashMap的Entry元素。 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。 */ private static class Entry extends HashMap.Entry { Entry<K,V> before, after; …… } HashMap.Entry: static class Entry implements Map.Entry { final K key; V value; Entry<K,V> next; final int hash; Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } } 2) 初始化: 通过源代码可以看出,在LinkedHashMap的构造方法中,实际调用了父类HashMap的 相关构造方法来构造一个底层存放的table数组。如: public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } HashMap中的相关构造方法: public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); } 我们已经知道LinkedHashMap的Entry元素继承HashMap的Entry,提供了双向链表 的功能。在上述HashMap的构造器中,最后会调用init()方法,进行相关的初始化,这个 方法在HashMap的实现中并无意义,只是提供给子类实现相关的初始化调用。 LinkedHashMap重写了init()方法,在调用父类的构造方法完成构造后,进一步实现 了对其元素Entry的初始化操作。 void init() { header = new Entry<K,V>(-1, null, null, null); header.before = header.after = header; } 3) 存储: LinkedHashMap并未重写父类HashMap的put方法,而是重写了父类HashMap的put方法 调用的子方法void recordAccess(HashMap m) ,void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己 特有的双向链接列表的实现。 HashMap.put: public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } 重写方法: void recordAccess(HashMap m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } void addEntry(int hash, K key, V value, int bucketIndex) { // 调用create方法,将新元素以双向链表的的形式加入到映射中。 createEntry(hash, key, value, bucketIndex); // 删除最近最少使用元素的策略定义 Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { if (size >= threshold) resize(2 * table.length); } } void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry<K,V> e = new Entry<K,V>(hash, key, value, old); table[bucketIndex] = e; // 调用元素的addBrefore方法,将元素加入到哈希、双向链接列表。 e.addBefore(header); size++; } private void addBefore(Entry existingEntry) { after = existingEntry; before = existingEntry.before; before.after = this; after.before = this; } 4) 读取: LinkedHashMap重写了父类HashMap的get方法,实际在调用父类getEntry()方法取得 查找的元素后,再判断当排序模式accessOrder为true时,记录访问顺序,将最新访问的 元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常 量级的,故并不会带来性能的损失。 HashMap.containsValue: public boolean containsValue(Object value) { if (value == null) return containsNullValue(); Entry[] tab = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (value.equals(e.value)) return true; return false; } /*查找Map中是否包含给定的value,还是考虑到,LinkedHashMap拥有的双链表, 在这里Override是为了提高迭代的效率。 */ public boolean containsValue(Object value) { // Overridden to take advantage of faster iterator if (value==null) { for (Entry e = header.after; e != header; e = e.after) if (e.value==null) return true; } else { for (Entry e = header.after; e != header; e = e.after) if (value.equals(e.value)) return true; } return false; } /*该transfer()是HashMap中的实现:遍历整个表的各个桶位,然后对桶进行遍历得到 每一个Entry,重新hash到newTable中, //放在这里是为了和下面LinkedHashMap重写该法的比较, void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } */ /** *transfer()方法是其父类HashMap调用resize()的时候调用的方法,它的作用是表扩容后, 把旧表中的key重新hash到新的表中。 *这里从写了父类HashMap中的该方法,是因为考虑到,LinkedHashMap拥有的双链表, 在这里Override是为了提高迭代的效率。 */ void transfer(HashMap.Entry[] newTable) { int newCapacity = newTable.length; for (Entry e = header.after; e != header; e = e.after) { int index = indexFor(e.hash, newCapacity); e.next = newTable[index]; newTable[index] = e; } } public V get(Object key) { // 调用父类HashMap的getEntry()方法,取得要查找的元素。 Entry<K,V> e = (Entry<K,V>)getEntry(key); if (e == null) return null; // 记录访问顺序。 e.recordAccess(this); return e.value; } void recordAccess(HashMap m) { LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m; // 如果定义了LinkedHashMap的迭代顺序为访问顺序, // 则删除以前位置上的元素,并将最新访问的元素添加到链表表头。 if (lm.accessOrder) { lm.modCount++; remove(); addBefore(lm.header); } } /** * Removes this entry from the linked list. */ private void remove() { before.after = after; after.before = before; } /*clear链表,设置header为初始状态/ public void clear() { super.clear(); header.before = header.after = header; } 5) 排序模式: LinkedHashMap定义了排序模式accessOrder,该属性为boolean型变量,对于访问 顺序,为true;对于插入顺序,则为false。 private final boolean accessOrder; 一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。看LinkedHashMap 的构造方法,如: public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } 这些构造方法都会默认指定排序模式为插入顺序。如果你想构造一个LinkedHashMap, 并打算按从近期访问最少到近期访问最多的顺序(即访问顺序)来保存元素,那么请使用 下面的构造方法构造LinkedHashMap: public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } 该哈希映射的迭代顺序就是最后访问其条目的顺序,这种映射很适合构建LRU缓存。 LinkedHashMap提供了removeEldestEntry(Map.Entry eldest)方法。该方法可以提供 在每次添加新条目时移除最旧条目的实现程序,默认返回false,这样,此映射的行为将类 似于正常映射,即永远不能移除最旧的元素。 当有新元素加入Map的时候会调用Entry的addEntry方法,会调用removeEldestEntry方法, 这里就是实现LRU元素过期机制的地方,默认的情况下removeEldestEntry方法只返回false 表示元素永远不过期。 /** * This override alters behavior of superclass put method. It causes newly * allocated entry to get inserted at the end of the linked list and * removes the eldest entry if appropriate. */ void addEntry(int hash, K key, V value, int bucketIndex) { createEntry(hash, key, value, bucketIndex); // Remove eldest entry if instructed, else grow capacity if appropriate Entry<K,V> eldest = header.after; if (removeEldestEntry(eldest)) { removeEntryForKey(eldest.key); } else { if (size >= threshold) resize(2 * table.length); } } /** * This override differs from addEntry in that it doesn't resize the * table or remove the eldest entry. */ void createEntry(int hash, K key, V value, int bucketIndex) { HashMap.Entry<K,V> old = table[bucketIndex]; Entry e = new Entry(hash, key, value, old); table[bucketIndex] = e; e.addBefore(header); size++; } protected boolean removeEldestEntry(Map.Entry eldest) { return false; } 此方法通常不以任何方式修改映射,相反允许映射在其返回值的指引下进行自我修改。 如果用此映射构建LRU缓存,则非常方便,它允许映射通过删除旧条目来减少内存损耗。 例如:重写此方法,维持此映射只保存100个条目的稳定状态,在每次添加新条目时 删除最旧的条目。 private static final int MAX_ENTRIES = 100; protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_ENTRIES; } 其实LinkedHashMap几乎和HashMap一样,不同的是它定义了一个Entry header, 这个header不是放在Table里,它是额外独立出来的。LinkedHashMap通过继承hashMap 中的Entry,并添加两个属性Entry before,after,和header结合起来组成一个双 向链表,来实现按插入顺序或访问顺序排序

wangccsy 2019-12-02 01:48:36 0 浏览量 回答数 0

回答

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

游客pklijor6gytpx 2019-12-02 03:06:59 0 浏览量 回答数 0

回答

ArrayList和Vector底层是数据结构实现,LinkedList是双向链表,ArrayList和Vector查询快,删除慢,LinkedList与之相反,ArrayList是线程不安全的,性能优于Vector,Vector是线程安全的,能解决线程安全问题

有头发的程序猿 2019-12-02 03:22:07 0 浏览量 回答数 0

回答

Vector和ArrayList的底层都是使用数组的方式实现,实现方式相同,插入,删除慢,查询快,不过Vector加了synchronized关键字,所以是线程安全的,ArrayList则是线程不安全的, LinkedList的实现方式的双向链表,插入,删除快,查询慢

有头发的程序猿 2020-01-09 10:42:19 0 浏览量 回答数 0

回答

自动释放池是一个 AutoreleasePoolPage 组成的一个page是4096字节大小,每个 AutoreleasePoolPage 以双向链表连接起来形成一个自动释放池 pop 时是传入边界对象,然后对page 中的对象发送release 的消息 AutoreleasePool的释放有如下两种情况: 一种是Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。 手动调用AutoreleasePool的释放方法(drain方法)来销毁AutoreleasePool或者@autoreleasepool{}执行完释放

游客bnlxddh3fwntw 2020-04-13 15:30:22 0 浏览量 回答数 0

回答

第一部分 基本概念第1章 数据结构基础1.1 问题求解分析1.2 数据结构1.3 数据结构的分类1.4 数据的四种基本存储方法1.5 数据结构三方面的关系习题第2章 算法及算法分析基础2.1 算法的基本概念2.2 算法的描述2.3 算法分析方法2.4 程序语言的基本语句与基本结构2.5 数组与结构2.6 抽象数据类型的表示与定义习题第二部分 简单数据结构第3章 线性表3.1 线性表的定义3.2 线性表的运算3.3 线性表的顺序存储结构及实现3.3.1 线性表的顺序存储结构3.3.2 顺序表的实现3.4 线性表的链式存储结构及实现3.4.1 单链表3.4.2 循环链袁3.4.3 双向链表3.4.4 静态链表3.4.5 顺序表和链表的比较3.5 线性表的应用习题第4章 栈和队列4.1 栈4.1.1 问题的提出4.1.2 定义及其操作4.1.3 栈的存储结构及实现4.1.4 栈的应用举例:表达式求值4.2 队列4.2.1 问题的提出4.2.2 队列的定义及操作4.2.3 队列的存储结构及实现4.2.4 队列的应用举例习题第5章 矩阵和广义表5.1 矩阵的存储5.2 特殊矩阵5.3 稀疏矩阵5.4 广义表习题第三部分 复杂数据结构第6章 二叉树和树6.1 二叉树的定义和性质6.1.1 二叉树的定义及相关术语6.1.2 特殊二叉树6.1.3 二叉树的性质6.2 二叉树的存储结构6.2.1 二叉树的顺序存储表示6.2.2 二叉树的链式存储表示6.3 二叉树的遍历6.3.1 问题的提出6.3.2 二叉树的遍历算法6.3.3 二叉树遍历的非递归实现6.3.4 遍历算法的应用6.4 二叉树的线索化6.4.1 线索二叉树的定义6.4.2 线索二叉树的结构6.4.3 二叉树的线索化算法6.4.4 线索二叉树基本操作的实现6.5 二叉树的应用——哈夫曼树……第7章 图第8章 散列结构第9章 集合结构第四部分 算法与数据结构应用

琴瑟 2019-12-02 01:22:41 0 浏览量 回答数 0

问题

图解九大数据结构 6月13日 【今日算法】

游客ih62co2qqq5ww 2020-06-17 13:17:00 29 浏览量 回答数 1

问题

图解!24张图彻底弄懂九大常见数据结构! 7月22日 【今日算法】

游客ih62co2qqq5ww 2020-07-27 13:19:32 6 浏览量 回答数 1

回答

遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么? 遍历方式有以下几种: for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。 foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。 最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。如果没有实现该接口,表示不支持 Random Access,如LinkedList。 推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或 foreach 遍历。 说一下 ArrayList 的优缺点 ArrayList的优点如下: ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。ArrayList 在顺序添加一个元素的时候非常方便。 ArrayList 的缺点如下: 删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。插入元素的时候,也需要做一次元素复制操作,缺点同上。 ArrayList 比较适合顺序添加、随机访问的场景。 如何实现数组和 List 之间的转换? 数组转 List:使用 Arrays. asList(array) 进行转换。List 转数组:使用 List 自带的 toArray() 方法。 代码示例: ArrayList 和 LinkedList 的区别是什么? 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。 补充:数据结构基础之双向链表 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。 ArrayList 和 Vector 的区别是什么? 这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。性能:ArrayList 在性能方面要优于 Vector。扩容:ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在 Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。 Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。 Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。 插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性? ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。 Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差。 LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快。 多线程场景下如何使用 ArrayList? ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样: 为什么 ArrayList 的 elementData 加上 transient 修饰? ArrayList 中的数组定义如下: private transient Object[] elementData; 再看一下 ArrayList 的定义: public class ArrayList extends AbstractList implements List<E>, RandomAccess, Cloneable, java.io.Serializable 可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现: 每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。 List 和 Set 的区别 List , Set 都是继承自Collection 接口 List 特点:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。 Set 特点:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。 另外 List 支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。 Set和List对比 Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。 List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变 Set接口 说一下 HashSet 的实现原理? HashSet 是基于 HashMap 实现的,HashSet的值存放于HashMap的key上,HashMap的value统一为PRESENT,因此 HashSet 的实现比较简单,相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不允许重复的值。 HashSet如何检查重复?HashSet是如何保证数据不可重复的? 向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合equles 方法比较。 HashSet 中的add ()方法会使用HashMap 的put()方法。 HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复( HashMap 比较key是否相等是先比较hashcode 再比较equals )。 以下是HashSet 部分源码: hashCode()与equals()的相关规定: 如果两个对象相等,则hashcode一定也是相同的 两个对象相等,对两个equals方法返回true 两个对象有相同的hashcode值,它们也不一定是相等的 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖 hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。 ** ==与equals的区别** ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同 ==是指对内存地址进行比较 equals()是对字符串的内容进行比较3.==指引用是否相同 equals()指的是值是否相同 HashSet与HashMap的区别 Queue BlockingQueue是什么? Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。 在 Queue 中 poll()和 remove()有什么区别? 相同点:都是返回第一个元素,并在队列中删除返回的对象。 不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。 代码示例: Queue queue = new LinkedList (); queue. offer("string"); // add System. out. println(queue. poll()); System. out. println(queue. remove()); System. out. println(queue. size()); Map接口 说一下 HashMap 的实现原理? HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap的数据结构: 在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。 HashMap 基于 Hash 算法实现的 当我们往Hashmap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。 需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn) HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现 在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。 JDK1.8之前 JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 JDK1.8之后 相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 JDK1.7 VS JDK1.8 比较 JDK1.8主要解决或优化了一下问题: resize 扩容优化引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。 HashMap的put方法的具体流程? 当我们put的时候,首先计算 key的hash值,这里调用了 hash方法,hash方法实际是让key.hashCode()与key.hashCode()>>>16进行异或操作,高16bit补0,一个数和0异或不变,所以 hash 函数大概的作用就是:高16bit不变,低16bit和高16bit做了一个异或,目的是减少碰撞。按照函数注释,因为bucket数组大小是2的幂,计算下标index = (table.length - 1) & hash,如果不做 hash 处理,相当于散列生效的只有几个低 bit 位,为了减少散列的碰撞,设计者综合考虑了速度、作用、质量之后,使用高16bit和低16bit异或来简单处理减少碰撞,而且JDK8中用了复杂度 O(logn)的树结构来提升碰撞下的性能。 putVal方法执行流程图 ①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容; ②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③; ③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals; ④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤; ⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可; ⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。 HashMap的扩容操作是怎么实现的? ①.在jdk1.8中,resize方法是在hashmap中的键值对大于阀值时或者初始化时,就调用resize方法进行扩容; ②.每次扩展的时候,都是扩展2倍; ③.扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。 在putVal()中,我们看到在这个函数里面使用到了2次resize()方法,resize()方法表示的在进行第一次初始化时会对其进行扩容,或者当该数组的实际大小大于其临界值值(第一次为12),这个时候在扩容的同时也会伴随的桶上面的元素进行重新分发,这也是JDK1.8版本的一个优化的地方,在1.7中,扩容之后需要重新去计算其Hash值,根据Hash值对其进行分发,但在1.8版本中,则是根据在同一个桶的位置中进行判断(e.hash & oldCap)是否为0,重新进行hash分配后,该元素的位置要么停留在原始位置,要么移动到原始位置+增加的数组大小这个位置上 HashMap是怎么解决哈希冲突的? 答:在解决这个问题之前,我们首先需要知道什么是哈希冲突,而在了解哈希冲突之前我们还要知道什么是哈希才行; 什么是哈希? Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。 所有散列函数都有如下一个基本特性**:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同**。 什么是哈希冲突? 当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。 HashMap的数据结构 在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做链地址法的方式可以解决哈希冲突: 这样我们就可以将拥有相同哈希值的对象组织成一个链表放在hash值所对应的bucket下,但相比于hashCode返回的int类型,我们HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要远小于int类型的范围,所以我们如果只是单纯的用hashCode取余来获取对应的bucket这将会大大增加哈希碰撞的概率,并且最坏情况下还会将HashMap变成一个单链表,所以我们还需要对hashCode作一定的优化 hash()函数 上面提到的问题,主要是因为如果使用hashCode取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让hashCode取值出的高位也参与运算,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动,在JDK 1.8中的hash()函数如下: static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与自己右移16位进行异或运算(高低位异或) } 这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动); JDK1.8新增红黑树 通过上面的链地址法(使用散列表)和扰动函数我们成功让我们的数据分布更平均,哈希碰撞减少,但是当我们的HashMap中存在大量数据时,加入我们某个bucket下对应的链表有n个元素,那么遍历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使得遍历复杂度降低至O(logn); 总结 简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的: 使用链地址法(使用散列表)来链接拥有相同hash值的数据;使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;引入红黑树进一步降低遍历的时间复杂度,使得遍历更快; **能否使用任何类作为 Map 的 key? **可以使用任何类作为 Map 的 key,然而在使用之前,需要考虑以下几点: 如果类重写了 equals() 方法,也应该重写 hashCode() 方法。 类的所有实例需要遵循与 equals() 和 hashCode() 相关的规则。 如果一个类没有使用 equals(),不应该在 hashCode() 中使用它。 用户自定义 Key 类最佳实践是使之为不可变的,这样 hashCode() 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode() 和 equals() 在未来不会改变,这样就会解决与可变相关的问题了。 为什么HashMap中String、Integer这样的包装类适合作为K? 答:String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况; 如果使用Object作为HashMap的Key,应该怎么办呢? 答:重写hashCode()和equals()方法 重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; 重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性; HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标 答:hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过hashCode()计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置; 那怎么解决呢? HashMap自己实现了自己的hash()方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均; 在保证数组长度为2的幂次方的时候,使用hash()运算之后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题; HashMap 的长度为什么是2的幂次方 为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。 这个算法应该如何设计呢? 我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是2的幂次方。 那为什么是两次扰动呢? 答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的; HashMap 与 HashTable 有什么区别? 线程安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它; 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛NullPointerException。 **初始容量大小和每次扩充容量大小的不同 **: ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用则用 ConcurrentHashMap 替代。 如何决定使用 HashMap 还是 TreeMap? 对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。 HashMap 和 ConcurrentHashMap 的区别 ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。) HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。 ConcurrentHashMap 和 Hashtable 的区别? ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; 实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 两者的对比图: HashTable: JDK1.7的ConcurrentHashMap: JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点): 答:ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。 ConcurrentHashMap 锁的方式是稍微细粒度的。 ConcurrentHashMap 底层具体实现知道吗?实现原理是什么? JDK1.7 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构如下: 一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;Segment 是一种可重入的锁 ReentrantLock,每个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 锁。 JDK1.8 在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。 结构如下: 如果该节点是TreeBin类型的节点,说明是红黑树结构,则通过putTreeVal方法往红黑树中插入节点;如果binCount不为0,说明put操作对数据产生了影响,如果当前链表的个数达到8个,则通过treeifyBin方法转化为红黑树,如果oldVal不为空,说明是一次更新操作,没有对元素个数产生影响,则直接返回旧值;如果插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount; 辅助工具类 Array 和 ArrayList 有何区别? Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有 ArrayList 有。 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。 如何实现 Array 和 List 之间的转换? Array 转 List: Arrays. asList(array) ;List 转 Array:List 的 toArray() 方法。 comparable 和 comparator的区别? comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序 一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort(). 方法如何比较元素? TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排 序。 Collections 工具类的 sort 方法有两种重载的形式, 第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较; 第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java 中对函数式编程的支持)。

剑曼红尘 2020-03-24 14:41:57 0 浏览量 回答数 0
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站