JAVA学习笔记06——集合

集合

1.Collection

boolean add(E e):添加元素
boolean remove(Object o):从集合中移除指定元素
void clear():清空集合中的元素
boolean contains(Object o):判断集合中是否存在指定元素
boolean isEmpty():判断集合是否为空
int size():集合中元素个数
在这里插入图片描述

1.1 迭代器iterator

迭代:Collection几何元素的通用获取方法。在取元素之前先要判断集合中是否有元素,如果有,就把这个元素取出来,继续再判断;一直把集合中所有元素全部取出。

常用方法:
public E next():返回迭代的下一个元素
public boolean hasNext():如果有元素可以迭代则返回true

使用步骤:
1.使用集合中的方法iterator()获取迭代器的实现类对象,使用iterator接口接收
2.使用iterator接口中的方法hasNext()判断 是否还有下一个元素
3.使用iterator接口中的方法next()取出下一个元素

public class Demo01 {
    
    
    public static void main(String[] args) {
    
    
        Collection<String> c=new ArrayList<>();
        c.add("Kobe");
        c.add("James");
        c.add("Jordan");
        c.add("Ray");
        Iterator<String> it=c.iterator();   //使用集合中的方法iterator()获取迭代器的实现类对象,
        // 使用Iterator接口接收
        while(it.hasNext()){
    
        //遍历集合,用hasNext()判断是否有下一个元素
            System.out.println(it.next());  //用next()取出下一个元素
        }
    }
}

增强for循环:

    for(元素的数据类型 变量:Collection集合or数组){
    
      
    }

用于遍历Collection和数组,通常只进行遍历,不要再遍历的过程中对集合元素进行增删操作

public class Demo02 {
    
    
    public static void main(String[] args) {
    
    
        int[] array={
    
    24,23,10,8};
        for(int a:array){
    
    
            System.out.println(a);
        }
    }
}

1.2 泛型

泛型是一种未知的数据类型,当我们不知道用什么数据类型时,可以使用泛型

创建集合对象不使用泛型:
好处:默认类型是Object类型,可以存储任意类型的数据
坏处:不安全,容易引发异常

使用泛型:
好处:
避免了类型转换的麻烦
把运行期异常,提升到了编译期

public class GenericClass<E> {
    
    
    private  E name;

    public E getName() {
    
    
        return name;
    }

    public void setName(E name) {
    
    
        this.name = name;
    }
}
public class Demo03 {
    
    
    public static void main(String[] args) {
    
    
        GenericClass gc=new GenericClass();
        gc.setName("泛型");
    }
}

泛型用于方法:
修饰符 <泛型> 返回值类型 方法名称(参数列表){}

public class GenericMethod {
    
    
    public <M> void method01(M m){
    
    
        System.out.println(m);
    }
}

泛型用于接口:
第一种方式:

public interface GenericInterface <I>{
    
    
    void method(I i);
}
public class GenericInterfaceImpl implements GenericInterface<String>{
    
    

    @Override
    public void method(String s) {
    
    
        System.out.println(s);
    }
}

第二种方式:
接口使用什么泛型,实现类就使用什么泛型

public class GenericInterfaceImpl<I> implements GenericInterface<I>{
    
    

    @Override
    public void method(I i) {
    
    
        System.out.println(i);
    }
}
public class demo04 {
    
    
    public static void main(String[] args) {
    
    
        GenericInterfaceImpl impl=new GenericInterfaceImpl();
        impl.method("hello");
    }
}

泛型通配符:

public class demo04 {
    
    
    public static void main(String[] args) {
    
    
        ArrayList<Integer> list1=new ArrayList<>();
        list1.add(24);
        list1.add(23);
        ArrayList<String> list2=new ArrayList<>();
        list2.add("Kobe");
        list2.add("James");
        method(list1);
        method(list2);
    }
    public static void method(ArrayList<?> list){
    
    
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()) {
    
    
            Object next =  iterator.next();
            System.out.println(next);
        }
    }
}

泛型上限限定:? extends E 代表使用的泛型只能E类型的子类/本身
泛型下限限定:? super E 代表使用的泛型只能是E类型的父类/本身

2. ArrayList

2.1 底层数据结构

ArrayList集合介绍
List 接口的可调整大小的数组实现。线程同步不安全。其本质为对象动态数组。
数组:一旦初始化长度就不可以发生改变
数组结构介绍
增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。
查询快:由于数组在内存中是一块连续空间,因此可以根据地址+索引的方式快速获取对应位置上的元素。

2.2 常用方法

public boolean add(E e) :将指定的元素追加到此列表的末尾。
public void add(int index,E element):将指定的元素,添加到该集合中的指定位置上
public E get(int index):返回集合中指定位置的元素
public E remove(int index):移除列表中指定位置的元素,返回的是移除的元素
public E set(int index,E element):用指定元素替换集合中指定位置的元素,返回值事更新前的元素
public void clear():清空集合所有数据
public boolean contains(Object o):判断集合是否包含指定元素

public class demo05 {
    
    
    public static void main(String[] args) {
    
    
        List<Integer> list=new ArrayList<>();
        list.add(23);
        list.add(24);
        list.add(10);
        list.add(8);
        list.add(2,35);
        System.out.println(list);   //[23, 24, 35, 10, 8]
        System.out.println(list.get(2));    //35
        list.remove(2);
        System.out.println(list);   //[23, 24, 10, 8]
        list.set(2,30);
        System.out.println(list);   //[23, 24, 30, 8]
    }
}

public boolean addAll(Collection<?extends E> c):按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
public boolean addAll(i nt index,Collection<? extends E> c):将指定集合中的所有元素插入到此列表中,从指定的位置开始。

2.3 注意事项

ArrayList是如何扩容的?
第一次扩容10
以后每次都是原容量的1.5倍

ArrayList插入删除一定比LinkedList慢吗?

1.数组删除元素确实要比链表慢,慢在需要创建新数组,还有比较麻烦的数据拷贝,但是在ArrayList底层不是每次删除元素都需要扩容,因此在这个方面相对于链表来说数组的性能更好
2.LinkedList删除元素之所以效率并不高,其原理在于底层先需要对整个集合进行折半的动作,然后又需要对集合进行遍历一次,这些操作导致效率变低

ArrayList 和 LinkList区别?
ArrayList
基于动态数组的数据结构
对于随机访问的get和set,ArrayList要优于LinkedList
对于随机操作的add和remove,ArrayList不一定比LinkedList慢 (ArrayList底层由于是动态数组,因此
并不是每次add和remove的时候都需要创建新数组)
LinkedList
基于链表的数据结构
对于顺序操作,LinkedList不一定比ArrayList慢
对于随机操作,LinkedList效率明显较低

3. LinkedList

3.1 底层数据结构

LinkedList底层的数据结构是一个双向链表。

3.2 常用方法

public void addFirst(E e):将指定元素插入列表头
public void addLast(E e):将指定元素插入列表尾
public E getFirtst():返回第一个元素
public E getLast():返回最后一个元素
public E removeFirst():移除并返回列表第一个元素
public E removeLast():移除并返回列表最后一个元素
public E pop():从此列表所表示的堆栈处弹出一个元素
public void push(E e):将元素推入此列表所标识的堆栈
public boolean isEmpty():如果列表不包含元素,则返回true

3.3 注意事项

LinkedList是无容量限制的;
LinkedList是非线程安全的;
LinkedList是基于双向链表实现的,当数据顺序无关的情况下,选择ArrayList还是LinkedList要从各动作的执行效率综合考虑。

4. Map

4.1 底层数据结构

Map<K,V>:K代表所维护的键的类型,V代表映射值得类型
Collection中的集合,元素是孤立存在的,向集合中存储元素采用一个个元素的存储方式
Map中的集合,元素是成对存在的。每个元素由键和值组成,通过键可以找到对应的值
Collection中的集合成为单列集合,Map中的集合称为双列集合
Map中键不可以重复,值可以重复,每个键只对应一个值
在JDK1.8之前,HashMap里用于存储单个键值对即key-value的是静态内部类Entry对象,其在HashMap中是以位桶和链表相结合的方式(拉链法)组织的;在JDK1.8之前的HashMap数据存储结构中,如果位桶数量不扩容,随着数据的增多链表长度会越来越长,查找的时候要逐一equals对比,速度会慢很多。因此JDK 1.8在解决哈希冲突时增加了红黑树方法,当冲突节点数量较小的时候,还是使用原始链表结构存储,当数量较大(>8)的时候,则将链表转化为红黑树存储。存储单个键值对的对象由原来的Entry变为了Node。

4.2 常用方法

public V put(K key,V value):把指定的键与指定的值添加到Map集合中,key不重复,返回null;key重复,会使用新的value替换map中重复的value,返回被替换的value值
public V remove(Object key):把指定的键所对应的键值对元素在Map集合中删除,返回被删除的值;如果key不存在,返回空。
public V get(Object key):根据指定的键,在Map集合中获取对应的值
boolean containsKey(Object key):判断集合中是否包含指定的键
public Set keySet():获取Map集合中所有的键,存储到Set集合中
public Set<Map.Entry<K,V> entrySet()>:获取到集合中所有的键值对对象的集合

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Map<String,Integer> map=new HashMap<>();
        map.put("Kobe",24);
        map.put("James",23);
        map.put("Jordan",23);
        map.put("Curry",30);
        System.out.println(map);
        //{Curry=30, Kobe=24, James=23, Jordan=23}
        int value1=map.remove("James");    //23
        int value2=map.get("Kobe");     //24
        boolean value3=map.containsKey("Jordan");   //true
    }
}

遍历
Set keySet()

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Map<String,Integer> map=new HashMap<>();
        map.put("Kobe",24);
        map.put("James",23);
        map.put("Jordan",23);
        map.put("Curry",30);
        //使用Map集合中的keySet(),把Map集合中所有的key取出来,存储到一个Set集合中
        Set<String> set=map.keySet();
        //遍历Set集合
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
    
    
            String key = iterator.next();
            //通过Map集合中的方法get(key),通过key找到value值
            int value=map.get(key);
            System.out.println(key+":"+value);
        }
    }
}

entrySet()

public class Demo {
    
    
    public static void main(String[] args) {
    
    
        Map<String,Integer> map=new HashMap<>();
        map.put("Kobe",24);
        map.put("James",23);
        map.put("Jordan",23);
        map.put("Curry",30);
        //使用Map集合中的entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中
        Set<Map.Entry<String,Integer>> set=map.entrySet();
        //遍历Set集合,获取每一个Entry对象
        Iterator<Map.Entry<String, Integer>> it = set.iterator();
        while (it.hasNext()) {
    
    
            Map.Entry<String, Integer> entry = it.next();
            //使用Entry对象中的方法获取键与值
            String key=entry.getKey();
            int value=entry.getValue();
            System.out.println(key+":"+value);
        }
    }
}

4.3 注意事项

在这里插入图片描述

1.HashMap存取无序
2.HashMap键与值都可以为null,但是键位置只能是一个null
3.键位置是唯一的,底层的数据结构控制键
4.jdk1.8之前数据结构是:链表+数组;jdk1.8之后是:链表+数组+红黑树
5.阈值>8并且数组长度大于64,才将链表转换为红黑树,变为红黑树是为了高效的查询

4.4 面试题

1.面试题:HashMap中hash函数是怎么实现的?还有哪些hash函数的实现方式?

对于key的hashCode做hash操作,无符号右移16位然后做异或运算。
还有平方取中法,伪随机数法和取余数法。这三种效率都比较低。而无符号右移16位异或运算效率是最高的。至于底层是如何计算的我们下面看源码时给大家讲解。

2.面试题:当两个对象的hashCode相等时会怎么样?

会产生哈希碰撞,若key值内容相同则替换旧的value.不然连接到链表后面,链表长度超过阈值8就转换为红黑树存储。

3.面试题:何时发生哈希碰撞和什么是哈希碰撞,如何解决哈希碰撞?

只要两个元素的key计算的哈希码值相同就会发生哈希碰撞。jdk8前使用链表解决哈希碰撞。jdk8之后使用链表+红黑树解决哈希碰撞。

4.面试题:如果两个键的hashcode相同,如何存储键值对?

hashcode相同,通过equals比较内容是否相同。
相同:则新的value覆盖之前的value
不相同:则将新的键值对添加到哈希表中

5.在不断的添加数据的过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。

6.通过上述描述,当位于一个链表中的元素较多,即hash值相等但是内容不相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度(阀值)超过 8 时且当前数组的长度 > 64时,将链表转换为红黑树,这样大大减少了查找时间。jdk8在哈希表中引入红黑树的原因只是为了查找效率更高。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。如下图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4x0NHA7p-1610453975882)(img/哈希表.png)]

但是这样的话问题来了,传统hashMap的缺点,1.8为什么引入红黑树?这样结构的话不是更麻烦了吗,为何阈值大于8换成红黑树?

JDK 1.8 以前 HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。 当链表长度很小的时候,即使遍历,速度也非常快,但是当链表长度不断变长,肯定会对查询性能有一定的影响,所以才需要转成树。

5. LinkedHashMap

5.1 底层数据结构

LinkedHashMap 继承了 HashMap。LinkedHashMap内部维护了一个双向链表,能保证元素按插入的顺序访问,也能以访问顺序访问,可以用来实现LRU缓存策略。
LinkedHashMap可以看成是 LinkedList + HashMap。
虽然LinkedHashMap增加了时间和空间上的开销,但是它通过维护一个额外的双向链表保证了迭代顺序。特别地,该迭代顺序可以是插入顺序,也可以是访问顺序。因此,根据链表中元素的顺序可以将LinkedHashMap分为:保持插入顺序的LinkedHashMap和保持访问顺序的LinkedHashMap,其中LinkedHashMap的默认实现是按插入顺序排序的。

5.2 常用方法

属性
//双向链接的头结点,最久的
transient LinkedHashMap.Entry<K,V> head;
//双向链接的尾结点,最新的
transient LinkedHashMap.Entry<K,V> tail;
//true表示最近最少使用次序(LRU),false表示插入顺序
final boolean accessOrder;
构造方法

public LinkedHashMap(int initialCapacity, float loadFactor) {
    
    
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity) {
    
    
    super(initialCapacity);
    accessOrder = false;
}

public LinkedHashMap() {
    
    
    super();
    accessOrder = false;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    
    
    super();
    accessOrder = false;
    putMapEntries(m, false);
}

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    
    
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

可以看到,LinkedHashMap的构造方法都是默认调用了父类的构造方法,并且几乎都是把属性accessOrder 赋值为false,除了第五个将其作为参数初始化,也就是说,默认情况下,LinkedHashMap创建对象都是采用插入顺序的方式来维持键值对的次序的。
LRU算法的实现

public class Test {
    
    

    public static void main(String[] args) {
    
    
       Map map = new LinkedHashMap<Integer,Integer>(20,0.75f,true);
        for (int i = 1; i<=5;i++){
    
    
            map.put(i,i);
        }
        System.out.println("正常输出=="+map.toString());
        map.get(3);
        System.out.println("读取元素=="+map.toString());
        map.put(6,6);
        System.out.println("插入元素=="+map.toString());
    }
}
//输出结果
正常输出=={
    
    1=1, 2=2, 3=3, 4=4, 5=5}
读取元素=={
    
    1=1, 2=2, 4=4, 5=5, 3=3}
插入元素=={
    
    1=1, 2=2, 4=4, 5=5, 3=3, 6=6}

可以看到,当初始化的实例时传入值为 true 的 accessOrder 时,不管是插入元素还是读取元素,都是将最近用到的元素放到最后,这是因为 在put 和 get方法中都做了特定的处理。

5.3 注意事项

(1)LinkedHashMap继承自HashMap,具有HashMap的所有特性;
(2)LinkedHashMap内部维护了一个双向链表存储所有的元素;
(3)如果accessOrder为false,则可以按插入元素的顺序遍历元素;
(4)如果accessOrder为true,则可以按访问元素的顺序遍历元素;
(5)LinkedHashMap的实现非常精妙,很多方法都是在HashMap中留的钩子(Hook),直接实现这些Hook就可以实现对应的功能了,并不需要再重写put()等方法;
(6)默认的LinkedHashMap并不会移除旧元素,如果需要移除旧元素,则需要重写removeEldestEntry()方法设定移除策略;
(7)LinkedHashMap可以用来实现LRU缓存淘汰策略

6. TreeMap

6.1 底层数据结构

TreeMap是一个双列集合,底层由红黑树结构构成。元素中键不能重复,元素会按照大小顺序排序

6.2 常用方法

put():添加
get():获取
remove():移除

6.3 注意事项

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
TreeMap 实现了Cloneable接口,意味着它能被克隆。
TreeMap 实现了java.io.Serializable接口,意味着它支持序列化。
TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的
Comparator 进行排序,具体取决于使用的构造方法。
TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

7. Queue

7.1 底层数据结构

队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。
LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用。

7.2 常用方法

void add(Object o):将指定元素加入此队列的尾部。
Object element():获取队列头部元素,但不删除该元素。
boolean offer(Object o):将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)方法更好。
Object peek():获取队列尾部元素,但不删除该元素。如果队列为空,则返回null。
Object poll():获取队列头部元素,并删除该元素。如果队列为空,则返回null。
Object remove():获取队列的头部元素,并删除该元素

8. Stack

8.1 底层数据结构

栈是Vector的一个子类,它实现了一个标准的后进先出的数据结构。
堆栈只定义了默认构造函数,用来创建一个空栈。 堆栈除了包括由Vector定义的所有方法,也定义了自己的一些方法。

8.2 常用方法

boolean empty():测试堆栈是否为空。
Object peek( ) :查看堆栈顶部的对象,但不从堆栈中移除它。
Object pop( ) :移除堆栈顶部的对象,并作为此函数的值返回该对象。
Object push(Object element) :把项压入堆栈顶部。
int search(Object element) :返回对象在堆栈中的位置,以 1 为基数。

猜你喜欢

转载自blog.csdn.net/qq_44708714/article/details/106872659
今日推荐