Java集合容器介绍

Java集合容器介绍:

分两大类:Collection,Map

Queue接口也继承了Collection接口,有两种实现方式:
1)循环数组 ArrayDeque
2)链表形式  直接使用LinkedList就行

1、Iterator接口

            Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法。它的一个子接口LinkedIterator在它的基础上又添加了三种方法,分别是add(),previous(),hasPrevious()。
        也就是说如果是先Iterator接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会在遍历到,通常无序集合实现的都是这个接口,比如HashSet,HashMap;而那些元素有序的集合,实现的一般都是LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个元素,比如ArrayList。

2、Collection (集合的最大接口)继承关系

——List 可以存放重复的内容

——Set  不能存放重复的内容,所以的重复内容靠hashCode()和equals()两个方法区分

——Queue  队列接口

——SortedSet  可以对集合中的数据进行排序

3、List的常用子类

1)ArrayList

        底层是数组队列,相当于动态数组。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。

//初始化
List<Integer> indegree = new ArrayList<>();
for (int i = 0; i < graph.size(); i++) {
    
    //初始化为0
     indegree.add(0);
}
//ArrayList转int[]
 int[] res1 = res.stream().mapToInt(Integer::valueOf).toArray();
 //int[]转ArrayList
 int[] tt = {
    
    1,2,3,4};
 List<Integer> collect = Arrays.stream(tt).boxed().collect(Collectors.toList());
1)当前数组是由默认构造方法生成的空数组,并且第一次添加数据,默认扩充大小是10(0->10)
2)一般是oldCapacity + (oldCapacity >> 1),相当于扩容1.5倍。
3)当扩充1.5倍不够时,则newCapacity = minCapacity,扩充为需要的大小
4)如果我们大概知道元素的个数,优先考虑List<Person> list2 = new ArrayList<>(50)以指定个数的方式去构造,这样可以避免底层数组的多次拷贝。
5)但指定大小new ArrayList<>(0)为0,前4次扩充都是+1

      ArrayList 中的操作不是线程安全的,单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

2)LinkedList

        LinkedList底层的**链表结构(内部使用Node数据结构实现链表结构)**使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性

链表的实现:LinkedList类中的属性first,last节点代表链表的入口
transient Node<E> first;
transient Node<E> last;
    private static class Node<E> {
    
    
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
    
    
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

3) vector,stack,queue

vector是同步版的ArrayList
queue都是基于ArrayList或者LinkedList实现的,
Stack 是继承Vector的,是类,而不是接口

Queue q = new LinkedList<>();   //链队
Queue q1 = new ArrayDeque<>(); // 数组队列,遍历更快,不要寻址
//虽然ArrayDeque底层用数组实现,但其不开放随机访问的接口,所以如果要随机访问,需要转到数组进行操作
Stack stack = new Stack();  // 数组栈
Deque deque = new LinkedList() //双端队列
链栈可以自己手动实现,自定义节点类即可,也可以继承LinkedList,添加一些栈常用方法

4)PriorityQueue

     PriorityQueue是类,其作用是每次以O(1)取出队列中权值最小的元素,再以O(log)维护队列

public class PriorityQueue<E> extends AbstractQueue<E>

       PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。(不指定Comparator时默认为最小堆)。
    有一个用户类Customer,它没有提供任何类型的排序。当我们用它建立优先队列时,应该为其提供一个比较器对象。

//优先队列自然排序示例
Queue<Integer> integerPriorityQueue = new PriorityQueue<>(7);
//匿名Comparator实现
    public static Comparator<Customer> idComparator = new Comparator<Customer>(){
    
    
// 自定义降序规则,构建大顶堆,头是最大元素
        @Override
        public int compare(Customer c1, Customer c2) {
    
    
            return (int) (c1.getId() - c2.getId());
        }
    };
   // 返回-1(负数),第一个参数放前面
   // 例子1:  if(o1>o2) return -1; 代表o1放前面,而o1>o2,所以是倒序
   // 例子2: return (int) (c1.getId() - c2.getId());
   //这个代表,当c1<c2时,返回负数,代表c1在前,而c1<c2,故是升序

//优先队列使用示例
 Queue<Customer> customerPriorityQueue = new PriorityQueue<>(7, idComparator);

4、Set的常用子类

1)HashSet

        无序,唯一,基于 HashMap 实现的,底层采用 HashMap 来保存元素,
HashMap<T, Object>存储数据

2)TreeSet

        有序,唯一,通过compareTo或者compare方法来保证元素的唯一性,元素以二叉树的形式存放,红黑树(自平衡的排序二叉树。)

3)LinkedHashSet

        其内部是通过 LinkedHashMap 来实现的。

5 Map的常用子类

1)HashMap

        HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。

        获取HashMap.get(key) 的时间复杂度为O(size Of Key),因为计算key的hashCode需要这些时间

//内部有Node类(kv两个属性),数组是Node数组,每个元素是Node
static class Node<K,V> implements Map.Entry<K,V> {
    
    
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
    
    
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        ...
  }
transient Node<K,V>[] table;

HashMap的put函数原理图
在这里插入图片描述

当使用自定义类,作为hashmap的key时,需要对自定义类重写hashcode和equals方法
//1.计算对象的Hash Code,找到对应的数组下标,获取链表---------------------第一步就是要用到hashCode()方法
//
//2.遍历链表检查对应Hash Code位置中的Key 对象和当前Key对象是否相等,相等的话用返回对应的Value--------------第二步就是要用到equals()方法

2)LinkedHashMap

        它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,LinkedHashMap 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。

它比HashMap多了两个属性:

//链表的头结点
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
//该属性指取得键值对的方式,是个布尔值,false表示插入顺序,true表示访问顺序,也就是访问次数.
private final boolean accessOrder;
//默认都是采用插入顺序来维持取出键值对的次序

        LinkedHashMap和HashMap的区别在于他们的基本数据机构上,我们来看一下LinkedHashMap的基本数据结构Entry:

  private static class Entry<K,V> extends HashMap.Entry<K,V> {
    
    
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;

        Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
    
    
            super(hash, key, value, next);
        }

next是用于维护HashMap指定table位置上连接的Entry顺序的;
before、after是用于维护Entry插入的先后顺序的.

        正是因为before、after和head,tail的存在,LinkedHashMap才形成了循环双向链表.
head是双向链表的入口.

i)使用

Map<String, Integer> accessMap = new LinkedHashMap<>(16,0.75f,true);
accessMap.put("c",100);
accessMap.put("d",200);
accessMap.put("a",500);
accessMap.get("c");
accessMap.put("d",300);
//输出
a 500
c 100
d 300

ii)自定义实现LinkedHashMap

 HashMap存储的是Node类型,即kv键值对。
 //自定义实现的话,需要保存节点的先后顺序。
 LinkedHashMap源码是对HashMapNode类型进行修改,加入了before,after属性来保存节点之间的先后顺序,next是解决哈希冲突属性
// 自定义实现:
 Map<Integer, Node1> cache = new HashMap<Integer, Node1>();
 //Node类里面的key为Integer类,Value为Node1类
 自定义一个类Node1,作为Value的存储类型,里面包含after,before属性,保存节点先后顺序

将hashtable替代,jdk1.2.效率高

3)TreeMap

底层是二叉树数据结构,红黑树(平衡二叉排序树)

static final class Entry<K,V> implements Map.Entry<K,V> {
    
    
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        ...
        }
TreeSet底层:TreeMap<E,Object> 
自然排序:TreeMap的所有key必须实现Comparable接口,所有的key都是同一个类的对象
----这样才能对插入的key进行比较
定制排序:创建TreeMap对象传入了一个Comparator对象,该对象负责对TreeMap中所有的key进行排序,采用定制排序不要求Map的key实现Comparable接口。等下面分析到比较方法的时候在分析这两种比较有何不同


4)Map的遍历

Map的遍历方式有两种   一种是entrySet   而另一种是 Keyset
1Keyset
Map<Student,String> map = new HashMap<Student,String>();
 
Set<Student> ks = map.KeySet();
Iterator<Student> it = ks.iterator();
While(it.hasNext())
{
    
    
    Student key = (Student)it.next();
    String value = map.get(key);
    System.out.println("key"+key.getName()+",value:"+value);
}
//简化上述写法
Map<String,String> map = new HashMap<Student,String>();
 
for(String key : map.keySet())
{
    
    
    System.out.println(key+"="+map.get(key));
}



2)entrySet

Iterator<Map.Entry<Student,String>> it = map.entrySet().iterator();
while(it.hasNext())
{
    
     
    Map.Entry<Student,String> me = it.next();
    Student key = (Student)me.getKey();
    String value = me.getValue();
    System.out.println(key.getName()+".."+value);    //key是student的对象 所以可通过getName获取数据
    

猜你喜欢

转载自blog.csdn.net/weixin_43849906/article/details/119886513
今日推荐