集合类-Java面试高频

集合

集合与数组

集合、数组都是对多个数据进行存储操作的结构,简称Java容器

说明:此时的存储都主要指的是内存层面的存储,不涉及持久化的存储(.txt,.jpg,.avi)

数组存储多个数据方面的特点:

  1. 初始化后,长度确定
  2. 数组定义完成,元素的类型也确定,只能操作指定类型的数据

数组缺点:

  1. 初始化后,长度不可修改
  2. 数组中提供的方法有限,对于添加、删除、插入数据等操作,效率不高
  3. 没有获取数组中实际元素的个数方法
  4. 数组存储数据有序、可重复,对于无序、不可重复,不能满足

Collection:存储单列集合的接口,定义了一些规范,内部有常用的List和Set接口

Collections:操作Collection和Map的工具类

Java集合可分为Collection和Map两种体系

Collection
-----List
-----LinkedList

LinkedList:非同步,对于频繁的插入、删除操作,使用此类效率比ArrayList高,底层是用双向链表存储的,不需要扩容

源码分析:内部声明了Node类型的fist和last属性,默认值为null,当调用add方法时才创建Node对象,创建Node对象

-----ArrayList

ArrayList:非同步,实现了可变大小的元素数组,作为List的主要实现类,线程不安全效率高底层是用数组存储的

底层源码:

jdk7:底层先创建了长度为10的数组,如果添加导致数组容量不够,则扩容。默认情况下,扩容为原来容量

的1.5倍,同时需要将原有的数组复制到新的数组中

jdk8:底层数组初始化时,并没有创建数组,而是在第一次调用add时,底层才创建了长度为10的数组,并将

数据添加到数组,后续和jdk7一致

-----Vector 同步

Vector:作为List的古老实现类,线程安全、效率低,底层是用数组存储

源码分析:jdk7和jdk8当中通过Vector构造器创建对象时,底层都创建了长度为10的数组,在扩容方面,

默认扩容为原来长度的2倍(开发中基本不会用到,涉及线程问题,同样使用ArrayListCollections接口有方法能使线程安全)

​ ------Stack

作为Vector的子类

-----Set

Set:元素无序,不可重复的集合(HashSet、LinkedHashSet(按照输入顺序)、TreeSet(按照自然顺序))

  • Set接口是Collection的子接口,set接口没有提供额外的方法
  • Set集合不允许包含相同的元素
  • Set中判断两个对象是否相同不使用==运算符,而是根据equals()方法
-----HashSet

HashSet:作为Set接口的主要实现类;线程不安全的,可以存储null值

​ ------LinkedHashSet

LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序排序

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,纪录此数据的前一个数据和后一个数据,对于频繁的遍历操作,LinkedHashSet高于HashSet

底层结构为:数组+链表

----TreeSet

TreeSet:可以按照添加对象的指定属性,进行排序

特点:

  1. 向TreeSet里面添加的数据,要求是相同类的对象
  2. 两种排序方式:自然排序(实现Comparable接口)和定制排序(comparator接口)
  3. 自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不是equals()
  4. 定制排序中,比较两个对象是否相同的标准为:compare()返回0,不是equals()
Map
-----HashTable

同步,实现一个key–value映射的哈希表,作为古老的实现类,线程安全,效率低,不能存储null的key和value

HashTable:数组默认大小是11,增加的方式为oldX2+1

-----HashMap

非同步,作为Map的主要实现类,线程不安全,效率高,可以存储null的key和value,key所在的类要重写equals()和hashCode(),value所在类要重写equals() ,数组默认大小是11,增加的方式为2Xold

jdk8相较于jdk7在底层实现方面的不同:

  1. new HashMap():底层没有创建一个长度为16的数组
  2. jdk8底层的数组是:Node[],而非Entry[]
  3. 首次调用put()方法时,底层创建长度为16的数组
  4. jdk7底层结构只有:数组+链表。jdk8中底层结构:数组+链表+红黑树。
    当数组的某一个索引位置上的元素以链表形式存在的数据个数>8且当前数组的长度>64时,此时此索引位置上的所有数据改为使用红黑树存储。

DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR: HashMap 的默认加载因子:0.75
threshold:扩容的临界值,=容量X填充因子:16X0.75 =>12
TREEIFY_THRESHOLD: Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64

HashMap的底层实现原理?以jdk7为例说明:
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table。
...可能已经执行过多次put.. .
map.put ( key1 , vaLue1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。如果此位置上的数据为空,此时的key1-value1添加成功。----情况1

如果此位置上的数据不为空,(意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-vaLue1添加成功。----情况2

如果key1的哈希值和已经存在的某一个数据( key2-value2)的哈希值相同,继续比较:调用key1所在类的equals(key2)
如果equals()返回false:此时key1-value1添加成功。----情况3

如果equals()返回true:使用value1替换value2。
补充:关于情况2和情况3:此时key1-value1和原来的数据以链表的方式存储。
在不断的添加过程中,会涉及到扩容问题,默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
-----LinkedHashMap

保证在遍历map元素时,可以按照添加的顺序进行遍历,对于频繁操作,执行效率高于HashMap

继承于HashMap,内部提供了Entry,替换HashMap中的Node

-----WeakHashMap

改进的HashMap,实现了“弱引用”,如果一个key不被引用,则被GC回收

-----TreeMap

保证按照添加的key-value进行排序,实现排序遍历,此时考虑key的排序,底层是由红黑树来实现的

//三种遍历Collection的方法
@Test
public void test01(){
    ArrayList list = new ArrayList();
    //add()方法将元素添加到集合里面
    list.add(123);
    list.add("dfsa");
    list.add("AAA");
    list.add("AAA");
    list.add(54645);
    list.add(new Person("Tom",12));

    System.out.println(list);

    //通过for循环遍历集合
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }

    //通过迭代器循环遍历集合
    Iterator iterator = list.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }

    //通过foreach循环遍历集合
    for (Object obj: list) {
        System.out.println(obj);
    }

对于HashSet底层数据存储实现的理解:

@Test
public void Test01(){
    HashSet set = new HashSet();
    Person p1 = new Person("tom", 11);
    Person p2 = new Person("jenny", 111);

    set.add(p1);
    set.add(p2);
    System.out.println(set);

    p1.name = "haha";

    //由于p1的name属性修改,导致set在调用remove方法时,在底层根据对应的哈希值删除对象,属性修改导致哈希值发生了改变
    //查找找到的是p1("haha",11)的哈希值,此哈希值对应的数组中位置为空,这样就无法删除p1
    set.remove(p1);
    System.out.println(set);

    //这里用add()方法添加的对象,对应的是p1("haha",11)的哈希值,此哈希值对应的数组中位置为空,可以添加数据
    set.add(new Person("haha",11));
    System.out.println(set);

    //这里用add()方法添加的对象,对应的是p1("tom",11)的哈希值,此哈希值相同,通过equals()方法判断,发现两个数据不一样,
    // 所以可以以链表的形式添加数据
    set.add(new Person("tom",11));
    System.out.println(set);
}

遍历Map集合的方法:

HashMap hashMap = new HashMap();
        hashMap.put(1,233454);
        hashMap.put(3,23904);
        hashMap.put(4,23764);
        hashMap.put(12,2334);
        hashMap.put(31,233244);
//方法一
Set entrySet = hashMap.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()){
    Object obj = iterator.next();
    //entrySet集合中的元素都是entry
    Map.Entry entry = (Map.Entry) obj;
    System.out.println(entry.getKey() + "---------->" + entry.getValue());

}

//方法二
Set keySet = hashMap.keySet();
Iterator iterator1 = keySet.iterator();
while (iterator1.hasNext()){
    Object key = iterator1.next();
    Object value = hashMap.get(key);
    System.out.println(key + "=====" + value);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值