JAVA集合

JAVA集合

Java集合类主要由两个接口派生而出:Collection和Map,Collection存储着对象的集合,而Map存储着键值对(两个对象)的映射表。

在这里插入图片描述

Collection体系

在这里插入图片描述

1. List

  • ArrayList:基于动态数组实现,支持随机访问。数组的默认大小为10,扩容为原来的1.5倍,只序列化数组中有元素填充那部分内容,保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。

    构造方法

    //使用无参构造方法初始化一个ArrayList对象时,该数组的大小为0,只有当调用add()方法添加元素时,才会使得数组的大小变为默认大小10.
    ArrayList()
    ArrayList(int initialCapacity)
    ArrayList(Collection<? extends E> c)
    

    其他方法

    add(E e)
    add(int index, E element)
    addAll(Collection<? extends E> c)
    addAll(int index, Collection<? extends E> c)
    clear()              //从集合中删除所有元素 
        
    //返回此ArrayList实例的浅表副本,元素本身不会被复制,复制的是地址,需要强转,对于复制品的操作相当于对于原集合的操作,取别名。注:浅克隆与深克隆的区别。
    public Object clone()
    ArrayList<Integer> arrayList2 = (ArrayList<Integer>) arrayList1.clone();
    
    boolean contains(Object o)  //如果集合中包含指定的元素,则返回 true 
    get(int index)              //返回集合中指定位置的元素
    indexOf(Object o)           //返回此列表中第一次出现的指定元素的索引,如果此列表不包含该元素,则返回-1。
    lastIndexOf(Object o)       //返回此列表中指定元素最后一次出现的索引,如果此列表不包含该元素,则返回-1。
    isEmpty()                   //集合为空吗
    iterator()				   //返回该集合元素的迭代器
    listIterator()
    listIterator(int index)
    
    //删除指定位置的元素,删除指定元素的第一个匹配项,删除指定集合中包含的所有元素
    E remove(int index)
    boolean remove(Object o)
    boolean removeAll(Collection<?> c)
    
    boolean retainAll(Collection<?> c) //仅保留此列表中包含在指定集合中的元素。
    set(int index, E element) 
    size()                             //返回此列表中的元素数
    subList(int fromIndex, int toIndex) //返回指定的 fromIndex (包含)和 toIndex (不包括)之间的此列表部分的视图。 
    Object[] toArray()
    trimToSize()            //将此 ArrayList实例的容量调整为列表的当前大小。
    

    如何解决ArrayList线程不安全的情况?

    可以使用 Collections.synchronizedList(); 得到一个线程安全的 ArrayList。

    List<String> list = new ArrayList<>();
    List<String> synList = Collections.synchronizedList(list);
    

    也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类。

    List<String> list = new CopyOnWriteArrayList<>();
    
  • Vector:和ArrayList类似,但它是线程安全的。它的实现与 ArrayList 类似,但是使用了 synchronized 进行同步。Vector进行扩容时可以指定增量大小,当增量大小为0时,每次扩容都会增加当前容量的一倍,即翻一番。

    构造方法

    Vector()//构造一个空向量,使其内部数据数组的大小为 10 ,其标准容量增量为零。
    Vector(int initialCapacity)//构造一个具有指定初始容量且容量增量等于零的空向量。 
    Vector(int initialCapacity, int capacityIncrement)//构造具有指定初始容量和容量增量的空向量。
    Vector(Collection<? extends E> c)//按照集合的迭代器返回的顺序构造一个包含指定集合元素的向量。
    

    其他方法

    capacity() //返回此向量的当前容量, Vector有多大
    size()     //有效数据量
    ...
    
  • LinkedList:基于双向链表实现,只能顺序访问,但可以快速地进行插入和删除元素操作。其还可以用作栈、队列和双端队列。

    基于双向链表实现,使用 Node 存储链表节点信息。

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
    }
    

    每个链表存储了 first 和 last 指针:

    transient Node<E> first;
    transient Node<E> last;
    

在这里插入图片描述

构造方法

LinkedList()
LinkedList(Collection<? extends E> c)

模拟栈

LinkList<Integer> linkList = new LinkList<>();
linkList.push(1);
linkList.push(2);
linkList.pop();

模拟队列

LinkList<Integer> linkList = new LinkList<>();
//队尾插入元素,队头删除元素
linkedList.addLast(1);
linkedList.removeFirst();
//队头插入元素,队尾删除元素        
linkedList.addFirst(2);
linkedList.removeLast();

模拟双端队列

LinkList<Integer> linkList = new LinkList<>();
//队头插入和删除元素
linkedList.addFirst(2);
linkedList.removeFirst();
//队尾插入和删除元素        
linkedList.addLast(1);
linkedList.removeLast();

2. Set

  • HashSet:基于哈希表实现,底层为HashMap。支持快速查找,但不支持有序操作。并且失去了元素的插入顺序信息,也就是说使用迭代器遍历HashSet得到的结果是不确定的。

    HashSet<String> hashSet = new HashSet<>();
    hashSet.add("锄禾日当午");
    hashSet.add("汗滴禾下土");
    hashSet.add("谁知盘中餐");
    hashSet.add("粒粒皆辛苦");
    Iterator iterator = hashSet.iterator();
    while(iterator.hasNext()){
    	System.out.println(iterator.next());    //打印结果的顺序不是添加顺序
    }
    //汗滴禾下土
    //谁知盘中餐
    //锄禾日当午
    //粒粒皆辛苦
    

    方法

    //构造方法:由于HashSet的底层为HashMap,所以采用HashMap的默认初始容量16和负载因子0.75
    HashSet()
    HashSet(int initialCapacity)
    HashSet(int initialCapacity, float loadFactor)
    HashSet(Collection<? extends E> c)
    
  • LinkedHashSet:具有HashSet的查找效率,并且内部使用双向链表维护元素的插入操作,插入顺序即为迭代器的遍历顺序。

  • TreeSet:基于红黑树实现,底层为TreeMap。支持有序操作,例如根据一个范围查找元素的操作。但是查找效率不如HashSet,HashSet的查找效率为O(1),TreeSet则为O(logN)。

    当TreeSet中的E为自定义数据类型时,其需要实现Comparable接口,即重写CompareTo()方法!(也可以通过Comparator接口来实现比较方法)

    为什么需要实现Comparable接口
    这是因为TreeSet是有序的,这种有序是按照自然顺序排列的,JVM需要知道这些数据如何排序以进行存储,这就需要我们制定排序的规则了,而重写compareTo(T o)方法就是我们重写排序规则。当E为String时,因为String类已经实现了Comparable接口,所以我们可以直接使用。

public class ComparableTest {
    public static void main(String[] args) {
        Student student1 = new Student("贾宝玉", 14, 88.5);
        Student student2 = new Student("林黛玉", 13, 90.5);
        Student student3 = new Student("史湘云", 13, 85);
        Student student4 = new Student("薛宝钗", 15, 91);

        TreeSet<Student> treeSet = new TreeSet<>();
        treeSet.add(student1);
        treeSet.add(student2);
        treeSet.add(student3);
        treeSet.add(student4);

        Iterator<Student> iterator = treeSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

class Student implements Comparable<Student>{
    private String name;
    private int age;
    private double score;

    public Student() {
    }

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    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;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

    @Override
    //成绩从大到小,年龄从小到大
    public int compareTo(Student stu) {
        if(this.score == stu.score){
            return this.age - stu.age;
        }else if(this.score < stu.score){
            return 1;
        }
        return -1;
    }
}
//通过Comparator接口实现比较方法
//参数为Comparator实现类对象的TreeSet构造方法:TreeSet(Comparator<? super E> comparator)
public class ComparatorTest {
    public static void main(String[] args) {
    	//通过匿名类的方式实现Comparator接口
        TreeSet<Stu> treeSet = new TreeSet<>(new Comparator<Stu>() {
            @Override
            public int compare(Stu o1, Stu o2) {
                if(o1.getScore() == o2.getScore()){
                    return o1.getAge() - o2.getAge();
                }else if(o1.getScore() > o2.getScore()){
                    return -1;
                }
                return 1;
            }
        });

        Stu stu1 = new Stu("贾宝玉", 14, 88.5);
        Stu stu2 = new Stu("林黛玉", 13, 90.5);
        Stu stu3 = new Stu("史湘云", 13, 85);
        Stu stu4 = new Stu("薛宝钗", 15, 91);
        treeSet.add(stu1);
        treeSet.add(stu2);
        treeSet.add(stu3);
        treeSet.add(stu4);

        Iterator<Stu> iterator = treeSet.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

}
class Stu{
    private String name;
    private int age;
    private double score;

    public Stu() {
    }

    public Stu(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    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;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

3. Queue

  • LinkedList:可以用它实现双端队列。
  • PriorityQueue:基于堆结构实现,可以用它来实现队列优先。

Iterator 和 ListIterator迭代器

Iterator迭代器可以作为Collection的迭代器遍历其中的元素,而ListIterator只能遍历List集合的元素,并且提供了更多的方法,具体请查看API文档!!!

/**
 * Iterator的主要方法:
 * hasNext(), next(), remove()
 */
ArrayList<Integer> arrayList1 = new ArrayList<>();
arrayList1.add(1);
arrayList1.add(2);
arrayList1.add(3);
arrayList1.add(4);
arrayList1.add(5);

//iterator()方法返回一个迭代器对象,
//hasNext()方法的返回值是Boolean类型
//next()方法返回数据并使得指针+1
//remove()方法,只能在调用next()后调用该方法一次
Iterator<Integer> iterator =  arrayList1.iterator();
iterator.next();
iterator.remove();

//输出2,3,4,5,1被删除了
while(iterator.hasNext()){                           //如果存在下一个元素
    Integer i = iterator.next();				   //指针后移一位并返回下一个元素的值
	System.out.println(i);
}
/**
 * ListIterator的主要方法:
 * void add(E e):在next()返回的元素之前插入,后指针指向插入元素的位置。
 * Boolean hasNext()
 * E next()
 * int nextIndex() 
 * Boolean hasPrevious()
 * E previous()
 * int previousIndex() 
 * void remove(E e) :将当前指针所指向的元素删除,指针上移一次
 * void set(E e):将当前指针所指向的元素改为e
 */
ArrayList<Integer> arrayList2 = new ArrayList<>();
arrayList2.add(1);
arrayList2.add(2);

ListIterator<Integer> listIterator =  arrayList1.listIterator();
//验证add()方法
listIterator.next();                         //先让指针指向1
listIterator.add(99);                        //添加99, 1,99,2  指针指向了99
listIterator.previous();                     //需要回退两步,才能到达初始状态,空的头指针
listIterator.previous();
while(listIterator.hasNext()){               //用迭代器进行遍历:1,99,2
    System.out.println(listIterator.next());
}

//假设指针在初始位置
listIterator.next();
listIterator.next();
listIterator.remove();        //1,2 指针移动2的位置,移除2后,指针指向1
listIterator.set(99);         //将1变为99

Map体系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e7hOeWnz-1618228084281)(C:\Users\Administrator\Desktop\笔记\Java集合\map.png)]

  • HashMap:基于哈希表实现。

    • 采用数组+链表的存储方式(数组默认大小为16);

在这里插入图片描述

  • 将key进行哈希运算得到哈希值之后与16进行取模运算,得到几就存储在下标为几的哈希桶中。HashMap在JDK1.8及以后的版本中引入了红黑树结构,若桶中数据量大于等于8时,链表转换成树结构;若桶中数据量小于等于6时,树结构还原成链表。

  • 为什么大于等于8时要将链表结构转化为红黑树结构?而小于等于6时又将树结构重新转化为链表结构?那为什么不是小于等于7时又将树结构重新转化为链表结构呢?

    答:因为红黑树的平均查找时间为log(n),而链表的平均查找时间为n/2。当数据量大于等于8时,红黑树的查找效率将超过链表的查找效率。而哈希桶中从8及以上的数据量变为6及以下时,两者的查找效率基本上没有区别,但是构建和调整树使得其成为红黑树的时间成本却比较大。还有选择6和8,中间有个差值7可以有效防止链表和树频繁转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

  • 为什么哈希桶的初始大小为16,其他数值不行吗?

    首先,哈希桶的大小需设置成2的k次幂,因为再对一个key的哈希值进行取模运算时,HashMap是按照等价的(n-1)& hash位运算进行的,因为位运算的效率比较高。当n为2的k次幂,那么(n-1)& hash的结果与hash % n的运算结果是等价的,而且n-1的二进制补码是每个二进制位上全为1的数,比如2^4 - 1 = 15,转化成二进制为1111。15与0-15进行位与运算还是该数本身,但是如果map的长度不是2的幂次,比如为15,那长度-1就是14,二进制为1110,无论与谁相与最后一位一定是0,0001,0011,0101,1001,1011,0111,1101这几个位置就永远都不能存放元素了,空间浪费相当大,也增加了添加元素发生碰撞的概率。

    ​ 其次,桶的初始化大小设置成2,4,8,那么可能会因为初始容量不足造成很多hash冲突;设置成32,64的话又显得浪费空间了。

  • 为什么负载因子是0.75?

    答:负载因子为0.75在时间和空间成本之间提供了良好的折中。较低的负载因子会提高查询效率,但是会造成空间浪费;较高的负载因子虽然可以有效利用空间,但是查询效率低下。

  • HashMap,HashTable与ConcurrentHashTable的区别?

    HashMap:线程不安全,效率高。

    HashTable:线程安全,效率低。当一个线程对该集合进行操作时,其他线程不能操作。

    ConcurrentHashTable:采用分段锁机制,保证线程安全,效率又较高。当一个线程对一个哈希桶进行操作时,其他线程不能操作这个哈希桶,但可以操作其他哈希桶。

  • 方法

//构造方法
HashMap();                                     //使用默认初始容量16和负载因子0.75
HashMap(int initialCapacity);				  //使用指定初始容量和负载因子0.75
HashMap(int initialCapacity, float loadFactor); //使用指定初始容量和负载因子
HashMap(Map<? extends K, ?extends V> m);

//其他方法
put(K key, V value)             //将指定的值与此映射中的指定键相关联。 
containsKey(Object key)         //如果此映射包含指定键的映射,则返回 true 。
containsValue(Object value)     //如果此映射将一个或多个键映射到指定值,则返回 true 。 
get(Object key)                 //返回指定键映射到的值,如果此映射不包含键的映射,则返回 null 。 
keySet()                        //返回此映射中包含的键的Set视图。
    
//遍历HashMap
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("小米", 1);
hashMap.put("华为", 2);
hashMap.put("OPPO", 3);
hashMap.put("vivo", 4);

Set<String> set = hashMap.keySet();                        //方式一
for (String key:set) {
    System.out.println(key + "-->" + hashMap.get(key));
}

Set<Map.Entry<String, Integer>> set1 = hashMap.entrySet(); //方式二
for(Map.Entry<String, Integer> entry:set1){
    System.out.println(entry);
}
  • TreeMap:基于红黑树实现。

  • HashTable:和HashMap类似,但它是线程安全的,这意味着同一时刻多个线程同时写入 HashTable 不会导致数据不一致。它是遗留类,不应该去使用它,而是使用 ConcurrentHashMap 来支持线程安全,ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。

  • LinkedHashMap:使用双向链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值