java全端课--集合-下

86 阅读11分钟

一、集合

1.1 Collection的子接口:List

1.1.1 List接口的常用API

首先,List接口是Collection的子接口,会继承Collection的所有方法。

其次,List接口又扩展了一些新的方法,这些方法基本上都与元素的“下标/索引”有关系。

1、增(新增)
  • add(下标,一个元素)
  • addAll(下标,另一个集合)
2、删(新增)
  • remove(下标)
3、改(新增)
  • set(下标,新元素)
  • replaceAll(UnaryOperator接口的实现类对象):需要编写匿名内部类实现UnaryOperator接口,重写apply抽象方法

  • sort(null):用Comparable接口的compareTo方法比较大小
  • sort(Comparator接口的实现类对象):用定制比较器Comparator的compare方法比较大小
public void test(){
  ArrayList<String> list = new ArrayList<>();
  list.add("hello");
  list.add("world");
  list.add("java");

  /*
  replaceAll方法的形参是UnaryOperator<T>类型的接口,
  它又继承了Function<T, R>接口,它的抽象方法  R apply(T t);
  在UnaryOperator<T>接口中的 抽象方法  T apply(T t)
  现在是在集合的replaceAll方法这个上下文中,那么它是要完成把旧元素替换为新元素。
  参数代表旧元素,返回值代表新元素,类型相同,可能值不同了

  需求:把上述单词的首字母改为大写
   */
  UnaryOperator<String> u = new UnaryOperator<String>() {
      @Override
      public String apply(String s) {//s代表list集合的旧元素,apply方法的返回值代表list集合的新元素
          //把s中的单词首字母改为大写
          //思路:截取首字母,把首字母改为大写,再与剩下的字母拼接起来
          /*String first = s.substring(0,1);
          first = first.toUpperCase();
          String after = s.substring(1);
          return first + after;*/

          return s.substring(0,1).toUpperCase().concat(s.substring(1));
      }
  };
  list.replaceAll(u);
  System.out.println(list);//[Hello, World, Java]
}
4、查(新增)
  • E get(下标):返回[下标]对应的元素
  • List subList(起始下标,终止下标):返回子集
  • int indexOf(元素):返回元素首次出现的下标
  • int lastIndexOf(元素):返回元素最后一次的下标

1.1.2 List接口的基础/常用实现类

1、ArrayList和Vector
  • ArrayList:俗称动态数组,因为它底层是用数组结构来存储一组元素。这个内部数组会自动扩容。
  • Vector:旧版的动态数组。

问:ArrayList与Vector的区别?

相同点:动态数组

不同点:

(1)数组的初始化长度:ArrayList从JDK7开始,初始化为 长度为0的空数组,Vector初始化为长度为10的数组。

  • ArrayList在第1次添加元素时,会创建长度为10的数组。

(2)扩容机制/规则:ArrayList是1.5倍扩容,Vector是2倍扩容

  • 1.5倍是谨慎的扩容,空间的利用率高,浪费率低。缺点:扩容频率增加。
  • 2倍是大胆扩容,空间的利用率低,浪费高。优点:扩容频率低。

如果对元素的个数有大致的预判的话,那么可以直接使用**ArrayList**(int initialCapacity) 和 Vector(int initialCapacity)直接给定初始化容量

(3)Vector是古老的动态数组,线程是安全的。 ArrayList比Vector新,线程不安全的。

联想:StringBuffer(旧,线程安全的)和StringBuilder(新,线程不安全的)

2、LinkedList

LinkedList:双向链表

问:动态数组与双向链表有什么区别?

(1)数据结构不同:动态数组的物理结构是数组,双向链表的物理是链表

(2)动态数组的元素是连续存储,当我们创建ArrayList或Vector时,需要在堆中申请一整块连续的内存空间,来存储它的元素。

双向链表,不要求元素是连续存储的,可以见缝插针的存元素,前后元素只要知道对方的地址即可。双向链表不仅仅需要存储元素,还得存储前后元素的地址,这就增加了负担。

补充:早期的时候,GC的算法效率或设计也好没有现在优秀,所以当数组变大以后要找一整块连续存储空间,压力比较大。

现在JVM的GC算法已经很优秀,而且内存的容量比之前大,所以,现在ArrayList的使用频率远远高于LinkedList。

(3)动态数组涉及到扩容。非末尾位置插入和删除元素需要移动元素。双向链表不需要扩容,不需要移动元素,但是每一个元素需要“结点”。

早期的时候,JVM内存拷贝技术一般,所以数组显得非常慢。而现在JVM内存拷贝技术很优秀了,数组的扩容和移动元素的拷贝效率非常高,而链表结点对象的创建和维护的时空消耗反而凸显出来了,使得现在LinkedList看起来更慢。

(4)根据下标的查询,动态数组的效率极高,时间复杂度是O(1),即可以直接根据数组首地址+下标,算出来元素的存储位置。

根据下标的查询,双向链表的查询只能从头或从尾遍历,时间复杂度是O(n)。

(5)不是根据下标查询,都得从头遍历,效率是一样的。

3、Stack

Stack是栈。它是Vector的子类。但是它在Vector的基础上,增加了几个特殊的方法,体现栈的数据结构特点,即先进后出(FILO,First In Last Out)或后进先出(LIFO,Last In First Out)。

stack 新增的方法:

  • push():压入栈
  • pop():弹出栈
  • peek():查看栈顶元素,不拿走
  • search(元素):查看这个元素从栈顶开始数是第几个元素,这里不是下标。

问:Stack和LinkedList都有push,pop,peek等方法,它们有什么区别?

  • Stack成为顺序栈,底层是数组结构。
  • LinkedList成为链式栈,底层是双向链表。

1.2 Collection的子接口:Queue和Deque

Queue称为队列,队列的特点是先进先出(FIFO,First In First Out)。它的实现类也有很多,基础阶段说一个LinkedList。

Queue有一个子接口Deque,它是双端队列(double ended queue)。基础阶段说一个LinkedList。

Queue接口的方法大致如下:添加往队尾添加,出来从队头出来

左边的方法如果添加,删除,查询失败,会抛出异常。右边的方法如果添加,删除,查询失败,会返回特殊值。

Deque接口的方法大致如下:可以从队头、队尾添加,也可以从队头、队尾出来

LinkedList 类:

  • LinkedList 实现了 List、Deque 和 Queue 接口。
  • 因此,LinkedList 可以用作列表(List)、双端队列(Deque)和队列(Queue)

1.3 Collection集合的关系图

1.4 Map集合

Map是双列集合,它用于存储键值对(key,value),键值对又被称为映射关系。

1.4.1 Map接口的API

所有Map集合以Map接口为根接口。Map的实现类有很多,其中使用频率最高的是HashMap。

1、增
  • put(key ,value ):添加一对键值对
  • putAll(另一个Map):将另一个Map中的键值对添加到当前Map中
2、删
  • remove(key):只要key对应就删除一对键值对
  • remove(key, value):要求key,value都对应再删除一对键值对
  • clear():清空map集合
3、改
  • replace(key,新value)
  • replace(key,旧value,新value)
  • replaceAll(BiFunction接口的实现类),需要用匿名内部类实现BiFunction接口接口,重写apply方法,新Value类型 apply(key,旧value)
4、查
  • V get(key):根据key查询value
  • V getOrDefault(key):根据key查询value,如果该key对应的value不存在,用默认值返回
  • boolean containsKey(key):判断某个key有没有
  • boolean containsValue(value):判断某个value有没有
  • boolean isEmpty():判断是否为空
  • int size():返回键值对的数量
5、遍历
  • Set<K> keySet():遍历所有key
  • Collection<V> values():遍历所有value
  • Set<Map.Entry<K,V> entrySet():遍历所有键值对
//方式一:遍历所有的key
//Set集合的元素不可以重复,因为key不能重复
Set<String> keys = map.keySet();//通过keySet()方法,获取map中所有的key
for (String key : keys) {
    System.out.println(key);
}

for (String key : keys) {
    System.out.println(key +"->" + map.get(key));
}
  
//方式二:遍历所有的value
Collection<String> values = map.values();
for (String value : values) {
    System.out.println(value);
}
  
//方式三:遍历所有的键值对
Set<Map.Entry<String, String>> entries = map.entrySet();
//最外层是Set表示,所有的(key,value)的映射关系不会重复
//内层是 Map.Entry,它是所有(key,value)键值对的公共父接口类型
//因为Entry接口是Map接口的内部接口,所以名字上写的是Map.Entry
for (Map.Entry<String, String> entry : entries) {//一个entry代表一个键值对
    //System.out.println(entry);
    System.out.println("key:" + entry.getKey() +"\tvalue = " +entry.getValue());
}

1.4.2 Map集合的特点

  • Map的key不可重复。如果两对(key,value)添加到同一个map,key重复的话,新的value会覆盖旧的value。
  • Map的value可以重复。
  • Map的key不可修改,value可以修改。开发中通常会用Integer,String类的对象作为key,因为它们不可变。当然,如果使用得当,可以是任意类型的对象作为key。

1.4.3 Map的常见实现类

Map的常见实现类:

  • HashMap<K,V>:哈希表
  • Hashtable<K,V>:哈希表
  • LinkedHashMap<K,V>:链式哈希表,LinkedHashMap是HashMap子类。
  • TreeMap<K,V>:红黑树
  • Properties:它是Hashtable的子类,它的key和value默认用String类型。而且添加键值对建议用setProperty方法,获取value建议用getProperty方法。通常用于封装 xx.properties配置文件的信息。

Properties p = new Properties();
//因为jdbc.properties文件是在src下,与咱们写的类是在一起的,所以用类加载器帮我们加载这个文件
p.load(ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"));//加载
System.out.println(p);

String user = p.getProperty("user");
System.out.println("user = " + user);

问:HashMap和Hashtable有什么区别?

  • Hashtable是古老的哈希表,线程安全的。不允许key和value为null。底层数据结构是:数组+单链表。
  • HashMap是比Hashtable更新的哈希表,线程不安全的。允许key和value为null。底层数据结构:Java之后 数组+单链表+红黑树。

问:HashMap与LinkedHashMap有什么区别?

  • HashMap存储键值对遍历没有规律。
  • LinkedHashMap存储键值对按照添加的顺序遍历。有一个双向链表来记录所有键值对的添加顺序。

问:HashMap与TreeMap有什么区别?

  • HashMap存储键值对遍历没有规律。
  • TreeMap存储键值对遍历按照key的大小顺序排列。依赖于Comparable 或 Comparator接口。

1.4.4 Map集合的关系图

1.4.5 Set和Map有什么关系

所有Set的内部结构都是一个Map,存储到Set中的元素都是内部Map的key,它们的value都是一个Object类型的常量对象PRESENT。

1.4.6 如果value是多个对象怎么办?

如果value是多个对象,就再用集合装起来就可以。

1.4.7 Collection、Map、Set、List、Queue有什么区别?

Collection系列的集合是单列集合,存储一组对象。Set和List、Queue都是它的子接口

Map系列的集合是双列集合,存储一组键值对。Map与Collection是独立的,并列的两个接口,没有继承关系。

Set系列的集合不能重复,List系列的集合可以重复,Set系列的集合是无序(不能通过下标进行操作),List系列的集合是有序的(可以通过下标进行操作)。Queue是体现先进先出的集合特点。

1.5 集合工具类Collections

Collections是工具类,它里面提供了很多静态方法,服务于各种集合。

Collections 是一个操作 Set、List 和 Map 等集合的工具类。Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制(线程安全)等方法:

  • public static boolean addAll(Collection<? super T> c,T... elements):将所有指定元素添加到指定 collection 中。

  • public static int binarySearch(List<? extends Comparable<? super T>> list,T key):在List集合中查找某个元素的下标,List的元素必须支持可比较大小,即支持自然排序。而且List集合也事先必须是有序的,否则结果不确定。

  • public static int binarySearch(List<? extends T> list,T key,Comparator<? super T> c):在List集合中查找某个元素的下标,List的元素使用定制比较器c的compare方法比较大小。而且List集合也事先必须是有序的,否则结果不确定。

  • public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,而且支持自然排序

  • public static T max(Collection<? extends T> coll,Comparator<? super T> comp)在coll集合中找出最大的元素,集合中的对象必须是T或T的子类对象,按照比较器comp找出最大者

  • public static void reverse(List<?> list)反转指定列表List中元素的顺序。

  • public static void shuffle(List<?> list) List 集合元素进行随机排序,类似洗牌

  • public static <T extends Comparable<? super T>> void sort(List list)根据元素的自然顺序对指定 List 集合元素按升序排序

  • public static void sort(List list,Comparator<? super T> c)根据指定的 Comparator 产生的顺序对 List 集合元素进行排序

  • public static void swap(List<?> list,int i,int j)将指定 list 集合中的 i 处元素和 j 处元素进行交换

  • public static int frequency(Collection<?> c,Object o)返回指定集合中指定元素的出现次数

  • public static void copy(List<? super T> dest,List<? extends T> src)将src中的内容复制到dest中

  • public static boolean replaceAll(List list,T oldVal,T newVal):使用新值替换 List 对象的所有旧值

  • Collections 类中提供了多个 synchronizedXxx() 方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

  • Collections类中提供了多个unmodifiableXxx()方法,该方法返回指定 Xxx的不可修改的视图。

二、源码分析

2.1 动态数组

ArrayList和Vector是动态数组。