集合(容器)

java集合 的 两大接口 :
Collection 接口 存放 单一元素。
Map 接口 存放 键值对。

Collection 接口 有 三个 子接口

  1. List
  2. Set
  3. Queue

在这里插入图片描述

** List Set Queue Map 区别 **

List(对付顺序的好帮手): 存储的元素是有序的、可重复的。

Set(注重独一无二的性质): 存储的元素是无序的、不可重复的。

Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。

Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x” 代表 key,“y” 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。

** Collection 接口 的 子接口 **

List
Arraylist: Object[] 数组
Vector:Object[] 数组
LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)

Set
HashSet(无序,唯一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
LinkedHashSet: LinkedHashSet 是 HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的。有点类似于我们之前说的 LinkedHashMap 其内部是基于 HashMap 实现一样,不过还是有一点点区别的
TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)

Queue
PriorityQueue: Object[] 数组来实现二叉堆
ArrayQueue: Object[] 数组 + 双指针

** Map **
HashMap: JDK1.8 之前 HashMap 由数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间

LinkedHashMap: LinkedHashMap 继承自 HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。
另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:《LinkedHashMap 源码详细分析(JDK1.8)》
https://www.imooc.com/article/22931

Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的

TreeMap: 红黑树(自平衡的排序二叉树)

** 任何 选用 集合 ? **

主要根据集合的特点来选用,比如我们需要根据键值获取到元素值时就选用 Map 接口下的集合,需要排序时选择 TreeMap,不需要排序时就选择 HashMap,需要保证线程安全就选用 ConcurrentHashMap。

当我们只需要存放元素值时,就选择实现Collection 接口的集合,需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSet 或 HashSet,不需要就选择实现 List 接口的比如 ArrayList 或 LinkedList,然后再根据实现这些接口的集合的特点来选用。

** 选择 集合 的 原因 **
当我们需要保存一组类型相同的数据的时候,我们应该是用一个容器来保存,这个容器就是数组,但是,使用数组存储对象具有一定的弊端, 因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。

数组的缺点是一旦声明之后,长度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。 但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。

** Collection 的 子接口 List **

  1. ArrayList 和 Vector 区别?

     都是 Object [] 数组 
     ArrayList  线程 不安全
     Vector     线程  安全。
    
  2. ArrayList 和 LinkedList 区别 ?

    线程安全 : 都是 线程不安全的。
    底层数据结构 :
    ArrayList 是 Object [] 数组
    LinkedList 是 双向链表。
    (JDK1.6 之前为循环链表,JDK1.7 取消了循环。)

    插入和删除 元素 是否 受位置影响?
    ArrayList 是 数组存储的 ,受 插入和删除 元素的 时间复杂度 受 元素位置影响。

     比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
    

    LinkedList 是 链表 存储的 ,在 头尾 插入 和 删除 元素 不受 元素位置 影响。
    如果是要在指定位置 i 插入和删除元素的话,时间复杂度近似为 O(n) ,因为需要先移动到指定位置再插入。

     (add(E e)、addFirst(E e)、addLast(E e)、removeFirst() 、 removeLast()),近似 O(1),如果是要在指定位置 i 插入和删除元素的话(add(int index, E element),remove(Object o))
    

    是否支持 快速随机查询:
    ArrayList 支持 ,因为快速随机访问就是通过元素的序号快速获取元素对象。
    (对应于get(int index)方法)。

    内存空间占用:
    ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,
    而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间
    (因为要存放直接后继和直接前驱以及数据)。
    在这里插入图片描述

RandomAccess 接口

	public interface RandomAccess {
	}
	

查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。

在 binarySearch() 方法中,它要判断传入的 list 是否 RamdomAccess 的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法

		    public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }

ArrayList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?
底层数据结构有关!
ArrayList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。
链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。
ArrayList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArrayList 实现 RandomAccess 接口才具有快速随机访问功能的!

ArrayList (透彻解析)
···
https://snailclimb.gitee.io/javaguide/#/docs/java/collection/ArrayList%E6%BA%90%E7%A0%81+%E6%89%A9%E5%AE%B9%E6%9C%BA%E5%88%B6%E5%88%86%E6%9E%90
···

** Collection 的 子接口 Set **

** comparable 和 compartor 区别 **

comparable 接口实际上是出自java.lang包
它有一个 compareTo(Object obj)方法用来排序
comparator接口实际上是出自 java.util 包
它有一个compare(Object obj1, Object obj2)方法用来排序

···
一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo()方法或compare()方法,当我们需要对某一个集合实现两种排序方式,
比如一个 song 对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo()方法和使用自制的Comparator方法或者以两个 Comparator 来实现歌名排序和歌星名排序,
第二种代表我们只能使用两个参数版的 Collections.sort().
···

Comparator 定制排序
···

	        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        arrayList.add(-1);
        arrayList.add(3);
        arrayList.add(3);
        arrayList.add(-5);
        arrayList.add(7);
        arrayList.add(4);
        arrayList.add(-9);
        arrayList.add(-7);
        System.out.println("原始数组:");
        System.out.println(arrayList);
        // void reverse(List list):反转
        Collections.reverse(arrayList);
        System.out.println("Collections.reverse(arrayList):");
        System.out.println(arrayList);

        // void sort(List list),按自然排序的升序排序
        Collections.sort(arrayList);
        System.out.println("Collections.sort(arrayList):");
        System.out.println(arrayList);
        // 定制排序的用法
        Collections.sort(arrayList, new Comparator<Integer>() {

            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        System.out.println("定制排序后:");
        System.out.println(arrayList);

原始数组:
[-1, 3, 3, -5, 7, 4, -9, -7]
Collections.reverse(arrayList):
[-7, -9, 4, 7, -5, 3, 3, -1]
Collections.sort(arrayList):
[-9, -7, -5, -1, 3, 3, 4, 7]
定制排序后:
[7, 4, 3, 3, -1, -5, -7, -9]

···

重写 compareTo 方法实现按年龄来排序
···

		// person对象没有实现Comparable接口,所以必须实现,这样才不会出错,才可以使treemap中的数据按顺序排列
// 前面一个例子的String类已经默认实现了Comparable接口,详细可以查看String类的API文档,另外其他
// 像Integer类等都已经实现了Comparable接口,所以不需要另外实现了
public  class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    /**
     * T重写compareTo方法实现按年龄来排序
     */
    @Override
    public int compareTo(Person o) {
        if (this.age > o.getAge()) {
            return 1;
        }
        if (this.age < o.getAge()) {
            return -1;
        }
        return 0;
    }
}

····

    public static void main(String[] args) {
        TreeMap<Person, String> pdata = new TreeMap<Person, String>();
        pdata.put(new Person("张三", 30), "zhangsan");
        pdata.put(new Person("李四", 20), "lisi");
        pdata.put(new Person("王五", 10), "wangwu");
        pdata.put(new Person("小红", 5), "xiaohong");
        // 得到key的值的同时得到key所对应的值
        Set<Person> keys = pdata.keySet();
        for (Person key : keys) {
            System.out.println(key.getAge() + "-" + key.getName());

        }
    }

5-小红
10-王五
20-李四
30-张三
···

** 无序性 和 不可重复性 含义 ? **
···
1、什么是无序性?
无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。

2、什么是不可重复性?
不可重复性是指添加的元素按照 equals()判断时 ,返回 false,需要同时重写 equals()方法和 HashCode()方法。
···

** 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同 **

HashSet 是 Set 接口的主要实现类 ,HashSet 的底层是 HashMap,线程不安全的,可以存储 null 值;

LinkedHashSet 是 HashSet 的子类,能够按照添加的顺序遍历;

TreeSet 底层使用红黑树,元素是有序的,排序的方式有自然排序和定制排序。

** Collection 的 子接口 Queue **

Queue 是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循 先进先出(FIFO) 规则。

Queue 扩展了 Collection 的接口,根据 因为容量问题而导致操作失败后处理方式的不同 可以分为两类方法:
一种在操作失败后会抛出异常,另一种则会返回特殊值。
在这里插入图片描述
Deque 扩展了 Queue 的接口, 增加了在队首和队尾进行插入和删除的方法,同样根据失败后处理方式的不同分为两类:

事实上,Deque 还提供有 push() 和 pop() 等其他方法,可用于模拟栈。
在这里插入图片描述

** ArrayDeque 和 LinkedList 区别 ? **
ArrayDeque 和 LinkedList 都实现了 Deque 接口,两者都具有队列的功能,

但两者有什么区别呢?
···
ArrayDeque 是基于可变长的数组和双指针来实现,
而 LinkedList 则通过链表来实现。

ArrayDeque 不支持存储 NULL 数据,但 LinkedList 支持。

ArrayDeque 是在 JDK1.6 才被引入的,而LinkedList 早在 JDK1.2 时就已经存在。

ArrayDeque 插入时可能存在扩容过程, 不过均摊后的插入操作依然为 O(1)。
虽然 LinkedList 不需要扩容,但是每次插入数据时均需要申请新的堆空间,均摊性能相比更慢。

从性能的角度上,选用 ArrayDeque 来实现队列要比 LinkedList 更好。此外,ArrayDeque 也可以用于实现栈。

···

PriorityQueue
PriorityQueue 是在 JDK1.5 中被引入的,
其与 Queue 的区别在于元素出队顺序是与优先级相关的,即总是优先级最高的元素先出队。

PriorityQueue 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据

PriorityQueue 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素。

PriorityQueue 是非线程安全的,且不支持存储 NULL 和 non-comparable 的对象。

PriorityQueue 默认是小顶堆,但可以接收一个 Comparator 作为构造参数,从而来自定义元素优先级的先后。

** Map 接口 **

** HashTable 和 HashMap 区别 **
线程是否安全:
HashTable 是线程 安全的 ,因为 HashTable 内部 方法 基本上都是 经过了 Synchronized 修饰。
(但 要保证 线程 安全 推荐 使用 ConcurrentHashMap)
效率: HashMap 效率高 ,HashTable 基本上被淘汰了

对于 Null key 和 Null key 的 支持:
HashMap 可以存储 null作为主键 ,但是 null 作为 主键 只能 有一个 ,值可以多个。
HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。

初始容量大小 和 每次扩充 容量 大小的 不同 :
1.HashMap 的 默认初始化大小 是 16 ,之后可以 扩充, 每次扩充 为之前的 2倍。
HashTable 默认 初始化 为 11, 之后 每次扩充 ,容量 为 之前的 2n+1。

2.创建时候如果 给定了 容量 初始值。
HashTable 会 直接 使用 给定的 大小。
但 HashMap 会 将其 扩充为 2的 幂次方大小。
(也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。)

  1. 底层数据结构:
    DK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)
    (将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

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);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
     public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

···
下面这个方法保证了 HashMap 总是使用 2 的幂作为哈希表的大小。

	    /**
 * Returns a power of two size for the given target capacity.
 */
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

···

** HashMap 和 HashSet区别 ** (记得 学完 hashmap 和 hashset 源码先)
HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone()、writeObject()、readObject()是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。
在这里插入图片描述

** HashMap 和 TreeMap 区别 **(记得 学完 hashmap 和 TreeMap 源码先)
TreeMap 和HashMap 都继承自AbstractMap ,但是需要注意的是TreeMap它还实现了NavigableMap接口和SortedMap 接口。

在这里插入图片描述

实现 NavigableMap 接口让 TreeMap 有了对集合内元素的搜索的能力。

实现SortedMap接口让 TreeMap 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,不过我们也可以指定排序的比较器。示例代码如下:

···

/**
 * @author shuang.kou
 * @createTime 2020年06月15日 17:02:00
 */
public class Person {
    private Integer age;

    public Person(Integer age) {
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }


    public static void main(String[] args) {
        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);
            }
        });
        treeMap.put(new Person(3), "person1");
        treeMap.put(new Person(18), "person2");
        treeMap.put(new Person(35), "person3");
        treeMap.put(new Person(16), "person4");
        treeMap.entrySet().stream().forEach(personStringEntry -> {
            System.out.println(personStringEntry.getValue());
        });
    }
}

person1
person4
person2
person3

可以看出,TreeMap 中的元素已经是按照 Person 的 age 字段的升序来排列了。
···
上面,我们是通过传入匿名内部类的方式实现的,你可以将代码替换成 Lambda 表达式实现的方式:

TreeMap<Person, String> treeMap = new TreeMap<>((person1, person2) -> {
  int num = person1.getAge() - person2.getAge();
  return Integer.compare(num, 0);
});

综上,相比于HashMap来说 TreeMap 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力。
···

** HashSet 如何检查 重复 **

当你把对象加入HashSet时,HashSet 会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。
但是如果发现有相同 hashcode 值的对象,这时会调用equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让加入操作成功。

在openjdk8中,HashSet的add()方法只是简单的调用了HashMap的put()方法,并且判断了一下返回值以确保是否有重复元素。

	// Returns: true if this set did not already contain the specified element
// 返回值:当set中没有包含add的元素时返回真
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}

而在HashMap的putVal()方法中也能看到如下说明:

	// Returns : previous value, or null if none
// 返回值:如果插入位置没有元素返回null,否则返回上一个元素
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
...
}

也就是说,在openjdk8中,实际上无论HashSet中是否已经存在了某元素,HashSet都会直接插入,只是会在add()方法的返回值处告诉我们插入前是否存在相同元素。

hashCode()与 equals() 的相关规定:

如果两个对象相等,则 hashcode 一定也是相同的
两个对象相等,对两个 equals() 方法返回 true
两个对象有相同的 hashcode 值,它们也不一定是相等的
综上,equals() 方法被覆盖过,则 hashCode() 方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。

···

** HashMap 的底层实现 **(记得 学完 hashmap 源码先)

** ConcurrentHashMap 和 Hashtable 的区别 **

** ConcurrentHashMap 线程安全的具体实现方式/底层具体实现 **
(记得 学完 ConcurrentHashMap 和 TreeMap 源码先)

** Collections 工具类 **

用法:
1.排序
2.查找  , 替换 工作
3. 同步控制 (不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)

** 排序操作 **
···

	void reverse(List list)//反转
	
	void shuffle(List list)//随机排序
	
	void sort(List list)//按自然排序的升序排序
	
	void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
	
	void swap(List list, int i , int j)//交换两个索引位置的元素
	
	void rotate(List list, int distance)//旋转。
	当distance为正数时,将list后distance个元素整体移到前面。
	当distance为负数时,将 list的前distance个元素整体移到后面

···

** 查找,替换操作 **
···

int binarySearch(List list, Object key)
//对List进行二分查找,返回索引,注意List必须是有序的

int max(Collection coll)//根据元素的自然顺序,返回最大的元素。
 类比int min(Collection coll)
 
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,
排序规则由Comparatator类控制。
类比int min(Collection coll, Comparator c)

void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素

int frequency(Collection c, Object o)//统计元素出现次数

int indexOfSubList(List list, List target)
//统计target在list中第一次出现的索引,找不到则返回-1,
类比int lastIndexOfSubList(List source, list target)

boolean replaceAll(List list, Object oldVal, Object newVal)
//用新元素替换旧元素

···

在这里插入图片描述
1.对于基本类型,==是判断两个值是否相等,比如int ,char等,基本类型没有equals()方法,所以当比较基本类型是否相等时采用 等号即可
2.对于引用类型,= =是判断两个变量是否引用同一个对象,equals则是判断引用的对象是否等价。

在例子中,a是新建了一个String对象,并赋值123“,”而b,c是引用了同一个“123“,由于是不同的对象所以==判断是错误。

equals 和 == 的区别:

对于基本类型来说,== 比较的是值是否相等;

对于引用类型来说,== 比较的是两个引用是否指向同一个对象地址
(两者在内存中存放的地址(堆内存地址)是否指向同一个地方);

对于引用类型(包括包装类型)来说,equals 如果没有被重写,对比它们的地址是否相等;

如果 equals()方法被重写(例如 String),则比较的是地址里的内容。

···

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值