Java面试知识点7

Java有哪些常用容器(集合)?

在这里插入图片描述

List:存放有序,列表存储,元素可重复。
ArrayList:基于数组实现的有序集合;
LinkedList:基于链表实现的有序集合;
Vector:矢量队列。

Set:无序,元素不可重复,底层为hashMap=数组加链表。
HashSet:基于hash实现的不重复集合,无序;
LinkedHashSet:基于hash实现的不重复集合,有序;
SortedSet:可排序不重复集合;
NavigableSet:可导航搜索的不重复集合;
TreeSet:基于红黑树实现的可排序不重复集合。

Queue:队列。
BlockingQueue:阻塞队列;
Deque:可两端操作线性集合;

Map:元素不可重复。
Hashtable:基于哈希表实现的健值映射集合,key,value均不可为null;
HashMap:类似HashTable,但方法不同步,key,value可为null;
LinkedHashMap:根据插入顺序实现的健值映射集合;
IdentityHashMap:基于哈希表实现的健值映射集合,两个key引用相等==,认为是同一个key;
SortedMap:可排序健值映射集合;
NavigableMap:可导航搜索的健值映射集合;
WeakHashMap:弱引用健,不阻塞垃圾回收器回收,key回收后自动移除健值对。

比较的点
1.有序、无序;
2.可重复,不可重复;
3.健,值是否可为null;
4.底层实现的数据结构(赎罪,链表,哈希);
5.线程安全性;

2.ArrayList和Vector的联系和区别

相同点
1底层都是用数组实现;
2.功能相同,实现增删改查等操作的方法相似;
3.长度可变的数组结构;

不同点
1.Vector是早起JDK版本提供,ArrayList是新版本替代Vector的;
2.Vector的方法是同步的,线程安全,ArrayList非线程安全,但性能比Vector好;
3.默认初始化容量都是10,Vector扩容默认会翻倍,可指定扩容的大小,ArrayList只增加50%。

3.Collection和Collections有什么区别?

Collections相当一一个包装类,它包含各种有关操作的静态多态方法,不能实例化,像一个Collection集合框架中的工具类;

Collection是JDK中集合层次结构的最根本的接口,定义了集合类的基本方法;

4.List、Set、Map之间的区别是什么?

List:List的元素以线性方式存储,可以存放重复,有序集合

List主要有两大实现类 :

  1. ArrayList:长度可变数组,可以对元素进行随机访问,ArrayList中插入与删除元素的速度较慢,扩容是1.5倍,然后调用Arrays.copyof()对原来的数组进行复制。
  2. LinkedList:采用链表数据结构,插入和删除较快基于链表。

Set:Set中的对象不按照特定(hashCode)的方式进行排序,无重复对象

主要有两大实现类:

  1. HashSet:按照哈希算法来存取集合中的对象,存取速度较快,当HashSet中的元素超过 loadFactor(默认值0.75)时,就可以近似两倍扩容,无序;
  2. SortedSet:可排序;
  3. TreeSet:TreeSet实现了SortedSet接口,能够对集合中的集合对象进行排序,利用二叉链表实现;
  4. linkedHashSet:无序;

Map:Map是一种把键对象和值对象映射的集合,他的每一个元素都包含一个键对象和值对象,key是无序,唯一,value不要求有序,允许重复。

Map主要有以下两个实现类:

  1. HashMap:HashMap基于散列表实现,其插入和查询<K,V>的开销都是固定的,可以通过构造器设置容量和负载因子来调整容器的性能;
  2. LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得<K,V>的顺序是其插入次序,或者是最近最少使用(LRU)的次序。

5.HashMap和Hashtable区别?

JDK1.8中,区别如下

  1. 线程安全性不同,HashMap线程不安全;Hashtable中的方法是Synchronized的;
  2. key,value是否允许null,HashMap的key和value允许为null,key只允许一个为空,Hashtable的key和value都不可为null;
  3. 迭代器不同,HashMap的Iterator是fail-fast迭代器,Hashtable还使用了enumerator迭代器;
  4. hash的计算方式不同。HashMap计算了hash值,Hashtable使用了key的hashCode方法;
  5. 默认初始大小和扩容不同,HashMap默认初始大小16,容量必须是2的整数次幂,扩容时将容量变为原来的2倍,Hashtable默认初始大小11,扩容时将容量变为原来的2倍加1;
  6. 是否有contains方法,HashMap没有contains方法,Hashtable包含contains方法,类似containsValue;
  7. 父类不同,HashMap继承自AbstractMap;Hashtable继承自Dictionary。

6.如何决定使用HashMap还是TreeMap?

1.HashMap基于散列同(数组和链表)实现;TreeMap基于红黑树实现;

2.HashMap不支持排序;TreeMap默认是按照key值升序排序的,可以指定排序的比较器,主要用于存入元素时对元素进行自动排序;

3.大多情况下HashMap有更好的性能,尤其是读数据,所以不需要排序的时候一般使用HashMap.

7.ArrayList和linkedList的区别是什么?

1.ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于双向链表实现的非线程安全的集合;

2.扩容问题:ArrayList使用数组实现,无惨构造函数默认初始化长度为10,数组扩容是会将原数组中的元素重新拷贝到新数组中,长度为原来的1.5倍(扩容代价高);LinkedList不存在扩容问题,新增元素放到集合尾部,修改相应的指针结点即可;

3.LinkedList比ArrayList更占内存空间,因为LinkedList为每一个节点存储了两个引用结点,一个指向前一个元素,一个指向下一个元素;

4.对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList。因为ArrayList直接通过数组下标直接找到元素,LinkedList要移动指针遍历每个元素直到找到为止;

5.新增和删除元素,一般LinkedList的速度要优于ArrayList,因为ArrayList在增删元素时,可能扩容和复制数组;LinkedList实例化对象需要时间外,只需要修改节点指针即可;

6.LinkedList集合不支持高效的随机访问;

7.ArrayList的空间浪费主要体现在list列表的结尾预留一定的容量空间;linkedList的空间花费则体现在它的每一个元素都需要消耗存储指针结点对象的空间;

8.都是非线程安全,允许放null.

8.Array和ArrayList区别?

Array即数组,声明如下int[] array = new int[3],定义一个Array时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定且类型相同

ArrayList是动态数组,长度动态可变,会自动扩容,不使用泛型的时候,可以添加不同类型元素,List list = new ArrayList(3),list.add()。

9.Queue的add()和offer(),remove()和poll(),element()和peek()方法有什么区别?

add()和offer()
1.Queue中的add()和offer()都是用来向队列添加一个元素;
2.在容量已满的情况下,add()方法会抛出ILLegaLStateException异常,offer()方法只会返回false;

remove()和poll()
1.Queue中的remove()和poll()都是用来从队列头部删除一个元素;
2.在队列元素为空的情况下,remove()方法会抛出NoSuchElementException异常,poll()方法只会返回null;

element()和peek()
1.Queue中element()和peek()都是用来返回队列的头元素,不删除
2.在队列元素为空的情况下,element()方法会抛出NoSuchElementException异常,peek()方法只会返回null;

10.哪些集合类是线程安全

1.Vector;
2.Stack;
3.Hashtable
4.java.util.concurrent包下所有的集合类:

  1. ArrayBlockingQueue;
  2. ConcurrentHashMap;
  3. ConcurrentLinkedQueue;
  4. ConcurrentLinkedEeque;

11.迭代器Iterator是什么?怎么使用?有什么特点?

1.迭代器模式,是Java中常用的设计模式之一,用于顺序访问集合对象的元素,无需知道集合对象的底层实现;

2.Iterator是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦;

3.缺点是增加新的集合类需要对应增加新的迭代器类,迭代器类与集合类成对增加;

//使用
Iterator iterator = list.iterator();
while(iterator.hasNext()){
    
    
	...;
}

特点:
1.java.lang.Iterable接口被java.util.Collection接口继承,java.util.Collection接口的iterator()方法返回一个Iterator对象;
2.next():方法获得集合中的下一个元素;
3.hasNext():检查结合中是否还有元素;
4.remove():方法将迭代器返回的元素删除;
5.forEachRemaining(Consumer<? super E> action)方法,遍历所有元素;

12.Iterator和ListIteator有什么区别?

ListIterator继承Iterator,ListIterator比Iterator多方法:

  1. ListIterator有add()方法,可以向list中添加对象,但iterator不能
  2. Listerator可以实现hasPrevious和previous(),可以实现逆向遍历,Iterator不可以
  3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能
  4. ListIterator有set()方法可以实现,可以实现对List的修改,Itearor仅能够遍历,不能修改
  5. 使用范围不同,Iterator可以迭代所有集合,ListIterator只能用于List及其子类。

13.怎么确保一个集合不能被修改?

使用关键字final可以修饰类、方法、成员变量,使用final修饰的类不可以被继承,使用final修饰的变量必须初始化值,如果这个成员变量是基本类型数据,表述这个变量的值是不可以改变的,但是引用指向的对象的内容还是可以改变的。因此,如果要确保一个集合不被修改,我们要清楚集合(list,set,map)都是引用类型数据,所以使用final修饰的集合的内容还是可以修改的。

采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报java.lang.UnsupportedOperationException错;

同理:Collections包也提供了对list和set集合的方法
Collections.unmodifiableList(List);
Collections.unmodifiableSet(Set);

14.为什么基本类型不能作为HashMap的健值?HashMap的健值需要注意什么?

原因
1.Java中是使用泛型来约束HashMap中的key和value的类型,HashMap<K,V>;
2.泛型在Java的规定中必须是对象Object类型的,基本数据类型不是Object类型,不能作为健值;
3.map.put(0,‘xiaoming’)中编译器将key值0进行了自动装箱,变成Integer类型;

注意
HashMap的key相等的条件是, 条件1必须满足,条件2和3必须满足一个
1.key的hash值相等;
2.内存中是同一个对象,即使用==判断key相等;
3.key不为null,且使用equals判断key相等;
所以自定义类作为HashMap的key,需要注意按照自己的设计逻辑,重写自定义类的hashCode()方法和equals()方法。

15.Java中已经有数组类型,为什么还要提供集合?

数组优点

  1. 效率高于集合类;
  2. 能存放基本数据类型和对象,集合只能放对象;

数组缺点

  1. 不是面向对象,存在缺陷;
  2. 数组长度固定无法动态改变,集合类容量动态改变 ;
  3. 数组无法判断其中实际存放了多少元素,只能通过length属性获取数组的申明的长度;
  4. 数组存储的特点是顺序的连续内存,集合的数据结构更为丰富;

JDK提供集合的意义

  1. 集合以类的形式存在,符合面向对象,通过简单的方法和属性调用可实现各种复杂操作;
  2. 集合有多种数据结构,不同类型的集合可适用于不同场合;
  3. 弥补了数组的一些缺点,比数组更灵活,实用,可开发的效率;

16.TreeSer的原理是什么?使用需要注意什么?

TreeSet基于TreeMap实现,TreeMap基于红黑树实现;

特点:

  1. 有序;
  2. 无重复;
  3. 增删元素,判断元素是否存在效率较高,时间复杂度O(log(N));

使用方式:

  1. TreeSet默认构造方法,调用add()方法时会调用对象类实现的Comparable接口的compareTo()方法和集合中的对象比较,根据方法返回的结果有序存储;
  2. TreeSet默认构造方法,存入对象的类未实现Comparable接口,抛出ClassCastException;
  3. TreeSet支持构造方法指定Comparator接口,按照Comparartor实现类的比较逻辑进行有序存储。

17.HashSet和HashMap有什么区别?ArrayList与LinkedList那个插入性能高?

HashSet和HashMap

  1. HashSet底层采用HashMap实现,HashSet的实现比较简单,HashSet的绝大多部分都是通过调用HashMap来实现的;
  2. 调用HashSet的add()方法时,实际上是向HashSet对象内部持有的HashMap对象增加了一个键值对,键是向HashSet中增加的那个对象,值是HashSet类持有的一个私有静态不可变的Object对象。

LinkedList插入性能高

  1. ArrayList是基于数组实现的,添加元素时,存在扩容问题,扩容时需要复制数组,消耗性能;
  2. LinkedList是基于链表实现的,只要将元素添加到链表最后一个元素的下一个即可。

18.LinkedHashMap、LinkedHashSet、LinkedList哪个最适合当做Stack使用?

LinkedList
1.Stack是线性结构,具有先进后出的特点;
2.LinkedList天然支持Stack的特性,调用push(E e)方法放入元素,调用pop()方法取出栈顶元素,内部实现只需要移动指针即可;
3.LinkedHashSet是基于LinkedHashMap实现,记录添加顺序的Set集合;
4.LinkedHashMap是基于HashMap和链表实现的,记录添加顺序的健值对集合;
5.如果要删除后进的元素,需要使用迭代器进行遍历,取出最后一个元素,移除,性能差;

19.Map的实现类中,那些是有序的,那些是无序的,如何保证其有序性?

1.Map的实现类有HashMap,LinkedHashMap,TreeMap;
2.HashMap是无序的
3.LinkedHashMap和TreeMap是有序的,LinkedHashMap记录就添加数据顺序,TreeMap默认升序;
4.LinkedHashMap底层存储结构是哈希表+链表,链表记录了添加数据的顺序;
5.TreeMap底层存储结构是二叉树,二叉树的中序遍历保证了数据的有序性;

20.TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort方法如何比较元素?

TreeMap和TreeSet

1.TreeSet要求存放的对象所属的类必须实现Comparable 接口,该接口提供了比较元素的comparaTo()方法,当插入元素时就会 回调该方法比较元素的大小;
2.TreeMap要求存放的健值对映射的健必须实现Comparable接口从而根据健对元素进行排序;

Collections工具类的sort()方法有两种方式

1.第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;
2.第二种不强制性的要求容器中的元素必须可比较,但要求传入参数Comparable接口的子类,需要重写compare()方法实现元素的比较规则,其实就是通过接口注入比较元素大小的算法,这就是回调模式的应用。

21.Vecotr、ArrayList、LinkedList的存储性能和特性?

  1. ArrayList和Vector都是使用数组存储数据;
  2. 允许直接按序号索引元素;
  3. 插入元素设计数组扩容、元素移动等内存操作;
  4. 根据下标找元素快,存在扩容的情况下插入慢;
  5. Vector对元素的操作,使用了synchronized方法,性能比ArrayList差;
  6. Vector属于遗留容器,早期的JDK中使用的容器;
  7. LinkedList使用双向链表存储元素;
  8. LinkedList按序号查找元素,需要进行前向获后向遍历,所以俺下标查找元素,效率低;
  9. LinkedList非线程安全;
  10. LinkedList使用的链式存储方式与数组的连续存储方式相比,对内存的利用率更高;
  11. LinkedList插入数据时只需要移动指针即可,所以插入速度快;

22.List、Map、Set三个接口,存取元素时各有什么特点?

1.List以索引来存取元素,元素可重复;
2.Set不能存放重复元素 ;
3.Map保存健值对映射,映射关系可以一对一,多对一;
4.List有基于数组和链表两种实现方式;
5.Set、Map容器有基于哈希存储和红黑树两种方式实现;
6.Set基于Map实现,Set里的元素值就是Map的key;

23.Map的遍历方式?

1.Map的keySet()方法,单纯拿到所有key的Set;
2.Map的values()方法,单纯拿到所有值的Collection;
3.keySet()获取到key的Set,遍历Set根据key找值(效率低,不建议使用);
4.获取所有的键值对集合,迭代器遍历;
5.获取所有的键值对集合,for循环遍历;

24.说一下HashMap的实现原理

1.HashMap基于Hash算法实现,通过put(key,value)存储,get(key)来获取value;
2.当传入key时,HashMap会根据key,调用hash(Object key)方法,计算出hash值,根据hash值将value保存在Node对象,Node对象保存在数组里;
3.当计算的hash值相同时,称之为hash冲突,HashMap的做法是用链表和红黑树存储相同hash值的value;
4.当hash冲突的个数:小于等于8使用链表;大于8时,使用红黑树解决链表查询满的问题;

25.Hashset有什么特性,hashset判断存入的对象是否重复是如何比较的?

HashSet是Set接口的实现类,因此,HashSet中的元素也是不能重复的。
HashCode判断元素重复的标准是,首先计算新添加元素的hashCode值,当不重复是,则直接加入到该集合中,若发生重复,也称发生了碰撞,则进一步调用equals判断元素是否在逻辑上相同。

26.ConcurrentHashMap了解吗?

为什么要使用 ConcurrentHashMap
主要基于两个原因:

  1. 在并发编程中使用 HashMap 可能造成死循环(jdk1.7,jdk1.8 中会造成数据丢失)
  2. HashTable 效率非常低下

ConcurrentHashMap 结构
在这里插入图片描述

JDK1.8 的实现已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 Synchronized 和 CAS 来操作,整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本。

基本属性

//node数组最大容量:2^30=1073741824
private static final int MAXIMUM_CAPACITY = 1 << 30;
//默认初始值,必须是2的幂数
private static final int DEFAULT_CAPACITY = 16;
//数组可能最大值,需要与toArray()相关方法关联
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//并发级别,遗留下来的,为兼容以前的版本
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//负载因子
private static final float LOAD_FACTOR = 0.75f;
//链表转红黑树阀值,> 8 链表转换为红黑树
static final int TREEIFY_THRESHOLD = 8;
//树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo))
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
private static final int MIN_TRANSFER_STRIDE = 16;
private static int RESIZE_STAMP_BITS = 16;
//2^15-1,help resize的最大线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
//32-16=16,sizeCtl中记录size大小的偏移量
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//forwarding nodes的hash值
static final int MOVED = -1;
//树根节点的hash值
static final int TREEBIN = -2;
//ReservationNode的hash值
static final int RESERVED = -3;
//可用处理器数量
static final int NCPU = Runtime.getRuntime().availableProcessors();
//存放node的数组
transient volatile Node<K,V>[] table;
/*控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
    *当为负数时:-1代表正在初始化,-N代表有N-1个线程正在 进行扩容
    *当为0时:代表当时的table还没有被初始化
    *当为正数时:表示初始化或者下一次进行扩容的大小
    */
private transient volatile int sizeCtl;

重点概念

  1. table: 默认为null,初始化发生在第一次插入操作,默认大小为16的数组,用来存储Node节点数据,扩容时大小总是2的幂次方。
  2. nextTable: 默认为null,扩容时新生成的数组,其大小为原数组的两倍
  3. Node :保存 key,value 及 key 的 hash 值的数据结构。
class Node<K,V> implements Map.Entry<K,V> {
    
    
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;
    //省略部分代码
}

其中 value 和 next 都用 volatile 修饰,保证并发的可见性。

  1. ForwardingNode: 一个特殊的 Node 节点,hash 值为 -1,其中存储 nextTable 的引用。
final class ForwardingNode<K,V> extends Node<K,V> {
    
    
    final Node<K,V>[] nextTable;
    ForwardingNode(Node<K,V>[] tab) {
    
    
        super(MOVED, null, null, null);
        this.nextTable = tab;
    }
}

只有table发生扩容的时候,ForwardingNode 才会发挥作用,作为一个占位符放在table中表示当前节点为 null 或则已经被移动。

  1. TreeNode类和TreeBin类:  TreeNode类表示的是红黑树上的每个节点。当一个链表上的节点数量超过了指定的值,会将这个链表变为红黑树,当然每个节点就转换为TreeNode。不像HashMap,ConcurrentHashMap在桶里面直接存储的不是TreeNode,而是一个TreeBin,在TreeBin内部维护一个红黑树,也就是说TreeNode在TreeBin内部使用的。

初始化

实例化 ConcurrentHashMap 时带参数时,会根据参数调整 table 的大小,假设参数为 100,最终会调整成 256,确保 table 的大小总是2的幂次方。

//table 初始化
private final Node<K,V>[] initTable() {
    
    
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
    
    
        //如果一个线程发现sizeCtl<0,意味着另外的线程执行CAS操作成功,当前线程只需要让出cpu时间片
        if ((sc = sizeCtl) < 0) 
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
    
    
            try {
    
    
                if ((tab = table) == null || tab.length == 0) {
    
    
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    sc = n - (n >>> 2);
                }
            } finally {
    
    
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

JDK 1.8 中为什么要摒弃分段锁
为什么Doug Lea在JDK1.8为什么要做这么大变动,使用重级锁synchronized,性能反而更高,原因如下:

  1. jdk1.8中锁的粒度更细了。jdk1.7中ConcurrentHashMap 的concurrentLevel(并发数)基本上是固定的。jdk1.8中的concurrentLevel是和数组大小保持一致的,每次扩容,并发度扩大一倍;
  2. 红黑树的引入,对链表的优化使得 hash 冲突时的 put 和 get 效率更高;
  3. 获得JVM的支持 ,ReentrantLock 毕竟是 API 这个级别的,后续的性能优化空间很小。 synchronized 则是 JVM 直接支持的, JVM 能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得 synchronized 能够随着 JDK 版本的升级而不改动代码的前提下获得性能上的提升。

猜你喜欢

转载自blog.csdn.net/qq_42748009/article/details/112302321
今日推荐