Java基础知识2----集合

1. 集合介绍

相对于数据来说,集合的大小是不固定的。

1.1 问题一:List,Set,Queue和Map的区别?

  1. List:存储的元素是有顺序的。可重复的。
  2. Queue:存储的元素是有顺序的。可重复的。按照FIFO排队规则确定顺序。
  3. Set:存储的元素是无序的,不可重复的。
  4. Map:元素为键值对,键key的信息是无序的,不可重复的。

2. List(Stack,ArrayList,LinkedList)

2.1 Stack(Vector的实现类)

  1. 底层数据结构:Object[] 数组
  2. List古老的实现类,线程是安全的,目前用的少。

2.2 ArrayList

  1. 底层数据结构:Object[] 数组
  2. 元素特点:有序(默认元素插入顺序),可重复,有索引,线程不安全

2.3 LinkedList

  1. 底层数据结构:双向链表
  2. 元素特点:有序(默认元素插入顺序),可重复,有索引。
  3. 常常使用ListedList作为栈和队列的结构。

2.4 LinkedList和ArrayList的五点区别?

  1. 线程安全:两者都是不同步的,也不保证线程安全,但是Vector能够保证。
  2. 底层数据结构:ArrayList的底层为数组,LinkedList的底层为链表
  3. 时间复杂度:LinkedList更加方便插入和删除
  4. 快速随机访问 :ArrayList更加方便进行随机访问。
  5. 内存占用:链表结构的LinkedList更加占用内存。
  6. 扩容:数组结构的ArrayList需要扩容,而LinkedList不用。
  7. 一般情况下,均有有限使用ArrayList.

2.5 List常用的API

//1)新建ArrayList和LinedList对象
List<Integer> data1=new ArrayList<>();
List<Integer> data2=new LinkedList<>();
//2)增删改查
data1.add(2)     //增加:在list集合最后位置添加元素2
data1.add(1,2)   //增加:在索引1的位置插入元素2
data1.remove(2)  //删除:把索引2位置的元素删除
data1.set(1,5)   //吸怪:把索引1位置的元素修改为5
data.get(1)      //查看:查看索引1位置元素的值
//3)常规操作
data1.clear()       //清空集合
data1.contains(2)   //判断某个对象中是否包含在集合中,返回true或者false
data1.isEmpty()     //判断集合是否为空
data1.toArray()     //把集合元素转换为数组存储

2.6 RandomAccess接口

1)ArrayList实现了RandomAccess接口,但是LinkedList没有实现。
2)RandomAccess其实没有具体的内容,主要是标志是否支持快速随机访问。
3)数据结构的ArrayList天然支持快速随机访问

2.7 ArrayList的扩容机制是什么?

  1. ArrayList有三种构造器,每种构造器有不同的扩容机制
    1) 无参构造器,无参构造
    2)有参构造器,传容量构造
    3) 有参构造器,传列表构造
  2. ArrayList的扩容机制
    1)第一种情况:当ArrayList的容量为0时,此时添加元素的话,需要扩容,三种构造方法创建的
    (1) 无参构造,创建ArrayList后容量为0,添加第一个元素后,容量变为10,此后若需要扩容,则正常扩容。
    (2) 传容量构造,当参数为0时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。
    (3) 传列表构造,当列表为空时,创建ArrayList后容量为0,添加第一个元素后,容量为1,此时ArrayList是满的,下次添加元素时需正常扩容。
    2)第二种情况,当ArrayList的容量大于0,并且ArrayList是满的时,此时添加元素的话,进行正常扩容,每次扩容到原来的1.5倍(新=旧+旧/2)。

2.8 List集合如何遍历?

方法一:for循环遍历

List<Integer> list=new ArrayList<>(Arrays.asList(1,2,3,4,5));
//方法一:循环遍历
  for(int i=0;i<list.size();i++){
  System.out.println(list.get(i));
  }

方法二:foreach遍历

List<Integer> list=new ArrayList<>(Arrays.asList(1,2,3,4,5));
for(Integer i: list){
  System.out.println(i);
}

2.9 List集合中元素的顺序如何指定?

以整型集合为例

  1. 初始顺序为元素添加进去的顺序
  2. Collections.sort()排序:默认升序
        List<Integer> list=new ArrayList<>(Arrays.asList(3,5,2,4,1));
        Collections.sort(list);
        System.out.print("默认升序:");
        for(Integer i: list) System.out.print(i+" ");
        System.out.println();
  1. Collections.reverse()排序:默认降序
        List<Integer> list=new ArrayList<>(Arrays.asList(3,5,2,4,1));
        Collections.reverse(list);
        System.out.print("修改为降序:");
        for(Integer i: list) System.out.print(i+" ");
        System.out.println();
  1. Collections.sort()重写Comperator类的compare方法排序:自定义
        //4)自定义排序为降序
        List<Integer> list1=new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7));
        Collections.sort(list1,(a,b)->b-a);
        System.out.print("自定义排序为降序:");
        for(Integer i: list1) System.out.print(i+" ");
        System.out.println();
  1. 在类中重写方法CompareTo(常用于对Map集合进行操作)

3. Queue(ArrayDeque,LinkedList和PriorityQueue)

3.0 Deque与Queue

  1. Queue:单端队列,准守先进先去的原则。
  2. Deque:双端队列,准守先进先出的原则。
  3. 函数方法两种类型,针对出现异常的情况:
    1)抛出异常的方法:add,remove,element/addfirst,removefirst,getfirst
    2)返回特殊值的方法:offer,poll,peek/offerfirst,pollfirst,peekfirst
  4. Deque还有函数方法:push和poll,能够用来模拟栈。

3.1 Deque->ArrayDeque

  • 底层数据结构:Object[ ] 数组+双指针

3.2 Deque->LinkedList

- LinkedList作为栈的操作:

//LinkedList作为一个栈的使用
        //1)新建栈对象
        Deque<Integer> stack=new LinkedList<>();
        //2)栈的压入操作
        stack.addLast(2);
        stack.addLast(3);
        stack.addLast(1);
        System.out.println(stack);
        //3)栈的弹出操作(使用弹出操作之前,一定要判断栈中还有元素)
        stack.removeLast();
        stack.removeLast();
        //4)查看栈头操作(保证栈中元素非空)
        System.out.println(stack.getLast());

- LinkedList作为队列的操作:

        //LinkedList作为一个队列的使用
        //1)新建队列对象
        Deque<Integer> queue=new LinkedList<>();
        //2)入队操作
        queue.addLast(2);
        queue.addLast(3);
        queue.addLast(1);
        //3)出队操作(一定要保证队列元素非空)
        queue.removeFirst();
        queue.removeFirst();
        //4)查看操作(一定要保证队列元素非空)
        System.out.println(queue.getFirst());

3.3 PriorityQueue(重点容易忘)

  • 底层数据结构:Object[ ] 数组来实现二叉堆(二叉树)
  • 可以用来实现小顶堆大顶堆(代码要重写一遍了)
  • 在面试中可能更多的会出现在手撕算法的时候,典型例题包括堆排序、求第K大的数、带权图的遍历等,所以需要会熟练使用才行。

堆定义:

  • 大顶堆:一种特殊的完全二叉树,任意一个节点都要比左右孩子节点大。
  • 小顶堆:一种特殊的完全二叉树,任意一个节点都要比左右孩子节点小。
//方法一:实现小顶堆的操作(输出第K小的数)
//PriorityQueue实现类能够实现堆排序操作(默认是小顶堆)
PriorityQueue<Integer> minHeap=new PriorityQueue<>(Arrays.asList(5,9,8,2,4));
        int size= minHeap.size();
        for(int i=0;i<size;i++){
            System.out.println("按从小到大顺序输出"+minHeap.poll());
        }
//方法二:实现大顶堆的操作
//PriorityQueue实现类能够实现大顶堆,需要重写Comperator类的compare方法
        PriorityQueue<Integer> maxHeap=new PriorityQueue<>(new Comparator<Integer>(){
            @Override
            public int compare(Integer a,Integer b){
                return b-a;
            }
        });
//使用Lambda表达式简化大顶堆重写操作
        PriorityQueue<Integer> maxHeap=new PriorityQueue<>((a,b)->b-a);

特点:

  1. PriorityQueue本质上还是一个队列,所以有入队(queue.offer()),出队(queue.poll())
    和查看(queue.peek())操作。
  2. 大顶堆与小顶堆的入队和出队操作的时间复杂度均为O(log(n))。
  3. 堆的队列是线程安全的,不允许出现null.

3.4 ArrayDeque和LinkedList的区别

  1. 相同点:两者都实现了Deque接口,两者都有队列的功能。
  2. 不同点:数据结构:ArrayDeque是基于可变长数组和链表组成,LinkedList是基于双向量链组成。
  3. ArrayDeque插入需要扩容,而LinkedList不需要扩容,但是需要申请新的空间,均摊性能相比更慢。
  4. 综合来说,ArrayDeque比LinkedList性能更加好一点。

4. Set(HashSet,LinkedHashSet,TreeSet)

4.0 HashSet、LinkedHashSet 和 TreeSet的异同?

  1. 相同点:都是Set接口的实现类,都能够保证数据不重复,并且都不是线程安全的。
  2. 不同点:底层数据结构不同,HashSet是基于HashMap(数组+链表),LinkedHashSet是基于LinkedListMap(数组+链表,多了一个双向链表)和TreeSet是基于红黑树实现。
  3. 使用场景:HashSet:不需要保证元素插入和取出的顺序; LinkedHashSet:保证元素的插入和去除顺序满足FIFO的顺序 ;TreeSet:常用于对元素需要进行自定义排序的时候。

4.1 HashSet

  1. 底层数据结构:基于HashMap实现,底层采用HashMap保存元素。
  2. 元素性质:无序,唯一

4.2 LinkedHashSet

  1. 底层数据结构:是HashSet的子类,基于LinkedHashMap实现

4.3 TreeSet

  1. 底层数据结构:红黑树实现(自平衡的排序二叉树实现)
  2. 元素性质:有序,唯一

5. Map(HashMap,LinkedHashMap,TreeMap)

5.1 HashMap

  1. 底层数据结构:由数组+链表组成,数组是主体,链表是为了防止哈希冲突(拉链法)

5.2 LinkedHashMap

  1. 底层数据结构:是HashMap的子类,也是由数组+链表组成,额外添加了一条双向链表。

5.3 TreeMap

  1. 底层数据结构:红黑树实现(自平衡的排序二叉树实现)

5.4 Map集合的遍历方式(重点容易忘)

  1. 在for循环中,使用entrySet()遍历Map集合
        Map<Integer,String> map=new TreeMap<>();
        map.put(1,"a");
        map.put(2,"b");
        map.put(3,"c");
        System.out.println(map);
        //Map遍历方式一
        for(Map.Entry<Integer,String> ma: map.entrySet()){
            System.out.print(ma.getKey()+"="+ma.getValue());
            System.out.print("  ");
        }
        System.out.println();
        //输出
        {1=a, 2=b, 3=c}
		1=a  2=b  3=c 
  1. 使用foreach循环遍历map.Key和map.Value
        Map<Integer,String> map=new TreeMap<>();
        map.put(1,"a");
        map.put(2,"b");
        map.put(3,"c");
        System.out.println(map);
        //Map遍历方式二(可以分别遍历键和值)
        for(Integer i:map.keySet()){
            System.out.print(i+"  ");
        }
        for(String  j:map.values()){
            System.out.print(j+"  ");
        }
        //输出
        1  2  3  a  b  c 

6. Set与Map之间的比较

6.1 Hashtable与HashMap的异同

  1. 线程安全:HashMap是非线程安全的,内部方法没有经过synchronized修饰。(如果想保证线程安全,就用ConcurrentHashMap )
  2. 效率:HashMap比Hashtable效率高,Hashtable几乎不用了。
  3. Null key 和Null value的支持:HashMap可以存有一个 Null key和多个Null value。
  4. 初始容量大小和每次扩充容量大小的不同 :HashMap默认初始化大小为16,每次扩容变为原来的两倍;HashMap也可使用自定义的初始容量值(必须是2的幂次方),扩容也会扩为2的幂次方。(HashMap 总是使用 2 的幂作为哈希表的大小
  5. 底层数据结构:HashMap的数据结构为数组+链表(链表是为了解决hash冲突,使用拉链法)。JDK1.8之后,当数组长度大于64链表长度大于阈值(默认为8时),会自动将链表转变为红黑树,以减少搜索是时间。

6.2 HashMap 和 HashSet 的异同

  1. HashSet的底层是使用HashMap实现的,都是数组+链表的组合。
  2. HashSet除了几个特殊的功能之后,其他主要功能都是调用HashMap的方法。

6.3 HashMap 和 TreeMap 的异同

  1. HashMap和TreeMap都是Map的实现类,但是TreeMap是基于红黑树实现的,HashMap是基于数组+链表实现的
  2. TreeMap额外多实现了两个接口:NavigableMap和SortedMap 接口。
  3. NavigableMap:使得TreeMap有在集合内实现搜索的能力:
public NavigableMap<K, V> descendingMap() 返回一个降序排列的Map
public NavigableSet<K> descendingKeySet() 返回一个降序排列的由键名组成的Set
  1. SortedMap :使得TreeMap有在集合内元素根据键的大小排序的能力[一定要学会比较器的重写
//匿名内部类,重写compare方法
TreeMap<Person, String> treeMap = new TreeMap<>(new Comparator<Person>() {
            @Override
            public int compare(Person person1, Person person2) {
                int num = person1.getAge() - person2.getAge();
                return Integer.compare(num, 0);
            }
            });
//匿名内部类,重写compare方法,使用Lambda表达式进行简化。
TreeMap<Person, String> treeMap = new TreeMap<>((person1, person2) -> {
  int num = person1.getAge() - person2.getAge();
  return Integer.compare(num, 0);
});
  1. 综上:TreeMap多了两个功能:集合内元素搜索集合元素按照键的值进行指定排序

6.4 HashSet 如何检查重复

  1. 添加的元素计算hashcode,如果不相等,那么就不存在重复情况,直接加上去。
  2. 如果hashcode相等,那么就用调用equals方法来检查,当hashcode相同时,对象是否真的相同。

6.5 HashMap 的底层实现

HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列

  1. 得到 hash 值:HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值.
  2. 得到存放位置:通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度)。
  3. 判断是覆盖还是拉链法添加:当前位置存在元素的话,判断该元素与要存入的元素的 hash 值以及 key 是否相同,相同就覆盖,不相同就使用拉链法解决冲突。
  4. “拉链法” :将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。(尾插法),其他解决哈希冲突方法有开放地址法。hashmap头插法在多线程情况下会形成链表环,陷入死循环。

6.6 HashMap 的长度为什么是 2 的幂次方

6.7 HashMap 多线程操作导致死循环问题

6.8 HashMap 有哪几种常见的遍历方式?

6.9 ConcurrentHashMap 和 Hashtable 的区别

6.10 ConcurrentHashMap 线程安全的具体实现方式/底层具体实现

7. Collections工具类

  • 排序
 - void reverse(List list)   //反转集合
 - void sort(List list)     //按照升序进行排列
 - void  sort(List list,Comparator c)  //自定义排序规则(按照键的值)
 - void swap(List list,int i,int j)    //交换集合两个索引位置的元素
  • 查找和替换操作
//使用二分查找集合中的元素,首先集合必须是有序的,返回索引值。
int binarySearch(List list, Object key)  

8. Java集合使用的注意事项

8.1 集合判空

  • 判断所有集合内部的元素是否为空,使用 isEmpty() 方法,而不是 size()==0 的方式。
  • isEmpty() 方法时间复杂度恒为O(1),size()==0不一定。后者更加稳定

8.2 集合转 Map

  • 在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。(不太理解)

8.3 集合的遍历

  • 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式
  • Iterator是一个迭代器,每个集合都可以得到一个迭代器,然后迭代器中有remove()的方法。
  • 使用普通的for循环进行遍历,可以直接删除元素。
  • 如果并发操作,需要对 Iterator 对象加锁。

8.4 集合去重

集合去重,可以使用Set集合,避免使用List集合,然后又用contains方法去判断。

// Set 去重代码示例:list集合去重。
public static <T> Set<T> removeDuplicateBySet(List<T> data) {
    if (CollectionUtils.isEmpty(data)) {
        return new HashSet<>();
    }
    return new HashSet<>(data);
}

区别:

  • HashSet集合中的contains使用哈希值查找,找一个数时间复杂度为O(1),查找n个数,总时间复杂度为O(n).
  • List集合中的contains使用遍历查找,找一个数时间复杂度为O(n),查找n个数,总时间复杂度为O(n^2).

8.5 集合转变为数组:toArray()

//1)toArray():集合转变为数组
//注意:在使用toArray()时候,返回的是一个Object[]数组,
// 需要在括号里添加类型:new String[0],只是声明返回值类型的作用。
String[] s=list.toArray(new String[0]);
System.out.println("s="+Arrays.toString(s));

8.6 数组转变为集合:Arrays.toList()

//1)Arrays.asList:数组转变为集合
//注意:一定不能直接赋值给list集合,需要转化为ArrayList
//List<String> list=Arrays.asList(string); 这个操作会使得list无法进行修改。
String[] string={"2","3","a","b"};
List<String> list=new ArrayList<>(Arrays.asList(string));
System.out.println("list="+list);

主要参考文章

https://javaguide.cn/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值