Java基础回顾系列-第六天-Java集合

Java基础回顾系列-第六天-Java集合

    • 集合概述
      • 数组的弊端
      • 集合框架的优点
      • Java集合关系图
      • 集合框架体系图
      • java.util.Collection接口
    • List集合
      • java.util.List接口
        • java.util.ArrayList
        • java.util.LinkedList
        • java.util.Vector
    • Set集合
      • java.util.Set接口
        • java.util.HashSet
        • java.util.LinkedHashSet
        • java.util.TreeSet
    • 集合输出
      • Iterator迭代输出
      • foreach
      • ListIterator双向迭代接口
      • Enumeration
    • Map集合
      • java.util.HashMap
      • java.util.LinkedHashMap
      • java.util.HashTable
      • java.util.TreeMap
      • java.util.Map.Entry接口
    • 集合工具类
      • Stack栈
      • Queue队列
      • java.util.Properties属性操作
      • java.util.Collections工具类

集合概述

参考博文:Java集合类: Set、List、Map、Queue使用场景梳理

参考官方文档:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/doc-files/coll-reference.html

Java 集合就像一种容器,可以把多个对象的引用放入容器中。
Java 集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。所有的集合类都位于java.util包下,后来为了处理多线程环境下的并发安全问题,java5还在java.util.concurrent包下提供了一些多线程支持的集合类。
Java 集合可分为 SetListMap 三种体系:

  • Set:无序、不可重复的集合
  • List:有序,可重复的集合
  • Map:具有映射关系的集合,每个元素都包含key和value,key不允许重复,value可以

注意: 在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 Java5 增加了泛型以后,Java 集合可以记住容器中对象的数据类型

数组的弊端

既然数组可以存储多个数据,为什么还需要集合类?

  1. 数组长度是不可变的,一旦初始化之后,长度是固定的;
  2. 参考以下集合框架的优点,相对应的就是数组的缺点。

集合框架的优点

  • 通过提供数据结构和算法减少编程工作,因此您不必自己编写它们。
  • 通过提供数据结构和算法的高性能实现来提高性能。由于每个接口的各种实现是可互换的,因此可以通过切换实现来调整程序。
  • 通过建立共同语言来来回传递集合,提供不相关API之间的互操作性
  • 通过要求您学习多个集合API 来减少学习API所需的工作量
  • 通过不要求您生成临时集合API,减少设计和实现API所需的工作量
  • 通过为集合和算法提供标准接口来促进软件重用,以便对其进行操作。

Java集合关系图

下图由于CSDN对图像尺寸有限制,可能有点模糊,可以点此访问 https://img-blog.csdn.net/20160124221843905。
在这里插入图片描述

集合框架体系图

Java基础回顾系列-第六天-Java集合_第1张图片
Java基础回顾系列-第六天-Java集合_第2张图片
Java基础回顾系列-第六天-Java集合_第3张图片

java.util.Collection接口

Collection接口是单值集合操作的最大父接口,在该接口中定义有所有单值数据的处理对象。
很少直接使用此接口操作。

方法 语义
boolean add​(E e) 添加单个实例元素到此集合中
boolean addAll​(Collection c) 将指定集合中的所有元素添加到此集合中
void clear() 删除所有元素
boolean contains​(Object o) 是否存在指定元素的单个实例(如果存在)
boolean remove​(Object o) 移除指定元素的单个实例(如果存在)
int size() 返回此集合中的元素数。
Object[] toArray() 将元素转换为数组返回
boolean retainAll​(Collection c) 是否保留交集元素
Iterator iterator() 返回此集合中元素的迭代器。

List集合

java.util.List接口

有序集合:public interface List extends Collection

方法 语义
void add​(int index, E element) 在指定索引位置上增加单个实例元素
E set​(int index, E element) 在指定索引位置上修改单个实例元素
ListIterator listIterator() 返回此集合中元素的列表迭代器。
@SafeVarargs static List of​(E... elements) 返回包含任意数量元素的不可修改列表。

三个常用子类:

  • java.util.ArrayList:大部分首选(使用比率90%),线程不安全。
  • java.util.LinkedList:8%
  • java.util.Vector:2%

①、List list1 = new ArrayList();
底层数据结构是数组,查询快,增删慢;线程不安全,效率高
②、List list2 = new Vector();
底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合
③、List list3 = new LinkedList();
底层数据结构是链表,查询慢,增删快;线程不安全,效率高

怎么记呢?我们可以想象:
  数组就像身上编了号站成一排的人,要找第10个人很容易,根据人身上的编号很快就能找到。但插入、删除慢,要望某个位置插入或删除一个人时,后面的人身上的编号都要变。当然,加入或删除的人始终末尾的也快。
  链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但插入、删除快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。

java.util.ArrayList

public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable

ArrayList存储容量不足时,会以成倍的形式开辟存储容量,比如默认的10,20,40…

构造函数 语义
public ArrayList() 构造一个初始容量为10的空列表。
ArrayList​(int initialCapacity) 构造一个指定初始容量的列表。

java.util.LinkedList

基于链表实现

public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, Serializable

没有提供像ArrayList​(int initialCapacity)构造一个指定初始容量的列表。为啥?基于链表,不需要。
类型不限制,可存储为null,且元素是在最后一个元素增加。性能快。

ArrayList与LinkedList区别?

  • ArrayList是数组实现的集合操作,而LinkedList是根据双向链表实现的集合操作;
  • 在使用List集合中的get()方法根据索引获取数据时,ArrayList的时间复杂度为S(0),而LinkedList的时间复杂度为S(N),N为集合的长度。简而言之,ArrayList与LinkedList 相比,查询和修改值比较快,但是增删速度比较快。
  • ArrayList在使用默认初始化对象数组的大小长度是10,如果空间不足会采用2倍的形式进行容量的扩充,如果保存大数据量的时候有可能会造成垃圾的产生以及性能的下降,但是这个时候可以使用LinkedList的子类保存。

java.util.Vector

public class Vector extends AbstractList implements List, RandomAccess, Cloneable, Serializable

是一个原始古老的程序类。 Vector和ArrayList一样,都是通过数组实现的,但是Vector是线程安全的。和ArrayList相比,其中的很多方法都通过同步(synchronized)处理来保证线程安全。
如果你的程序不涉及到线程安全问题,那么使用ArrayList是更好的选择(因为Vector使用synchronized,必然会影响效率)。
二者之间还有一个区别,就是扩容策略不一样。在List被第一次创建的时候,会有一个初始大小,随着不断向List中增加元素,当List认为容量不够的时候就会进行扩容。Vector缺省情况下自动增长原来一倍的数组长度,ArrayList增长原来的50%。

Set集合

java.util.Set接口

Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象。

有序集合:public interface Set extends Collection

注意:是Set集合并不像List集合扩充了许多新的方法,所以无法使用List获取指定元素的方法public E get​(int index)

二个常用子类:

  • java.util.HashSet
  • java.util.LinkedHashSet:
  • java.util.TreeSet:

java.util.HashSet

HashSet有以下特点

  • 不能保证元素的排列顺序,顺序有可能发生变化
  • 不是同步的
  • 集合元素可以是null,但只能放入一个null

当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

java.util.LinkedHashSet

LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

java.util.TreeSet

TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0
自然排序
自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。
Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。
obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。
如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0
定制排序
自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法

public class TreeSet extends AbstractSet implements NavigableSet, Cloneable, Serializable

自定义类排序时,自定义类必须实现Comparable接口,实现方法
如果自定义类属性有很多呢?这种情况下首选HashSet实现,使用hashCode(对象编码)以及equals(对象比较)来比较。

集合输出

Iterator迭代输出

该方式适用于Collection的所有子类。 使用频率比重高。

方法 语义
boolean hasNext() 判断迭代中是否有下一个元素。
E next() 返回迭代中的下一个元素。
default void remove() 底层集合中移除此迭代器返回的最后一个元素。

Java 中的 Iterator 功能比较简单,并且只能单向移动:

  • 使用方法 iterator() 要求容器返回一个 Iterator。第一次调用 Iterator 的 next() 方法时,它返回序列的第一个元素。注意:iterator() 方法是 java.lang.Iterable 接口,被 Collection 继承。
  • 使用 next() 获得序列中的下一个元素。
  • 使用 hasNext() 检查序列中是否还有元素。
  • 使用 remove() 将迭代器新返回的元素删除。

foreach

该方式适用于Collection的所有子类。 使用频率比重一般。

ListIterator双向迭代接口

仅适用于List接口的子类。使用频率比重极低。
Iterator输出特点:只能够由前向后进行内容的迭代处理,而如果要想进行双向迭代,那么就必须依靠Iterator的子接口:ListIterator来实现。

hasPrevious():判断是否有上一个元素
previous():取得上一个元素
listIterator():取得ListIterator接口对象

如果要想由后向前遍历,只能先执行由前向后遍历。


List<String> list = new ArrayList<>();
list.add("Hello");
list.add("HaHa");
list.add("HeHe");
ListIterator<String> listIterator = list.listIterator();
System.out.println("从后往前输出");
while (listIterator.hasNext()) {//判断是否有下一个元素
	System.out.println(listIterator.next()+"、");//取得下一个元素
}
System.out.println("\r\n从后往前输出");
while (listIterator.hasPrevious()) {//判断是否有上一个元素
	System.out.println(listIterator.previous()+"、");//取得上一个元素
}

Enumeration

仅适用于Vector类。

hasMoreElements():判断是否有下一个元素
nextElement():取得下一个元素
elements():取得Enumeration的接口对象

Map集合

特点:

  • Map用于保存具有"映射关系"的数据,因此Map集合里保存着两组值,一组值用于保存Map里的key,另外一组值用于保存Map里的value
  • key和value都可以是任何引用类型的数据。
  • Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较结果总是返回false。

关于Map,我们要从代码复用的角度去理解,java是先实现了Map,然后通过包装了一个所有value都为null的Map就实现了Set集合。
Map的这些实现类和子接口中key集的存储形式和Set集合完全相同(即key不能重复)
Map的这些实现类和子接口中value集的存储形式和List非常类似(即value可以重复、根据索引来查找)

1.HashMap和Hashtable的效率大致相同,因为它们的实现机制几乎完全一样。但HashMap通常比Hashtable要快一点,因为Hashtable是线程同步的,不过该类(Hashtable)是个古老类,建议使用ConcurrentHashMap代替 Hashtable
2. TreeMap通常比HashMap、Hashtable要慢(尤其是在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理key-value对
3. 使用TreeMap的一个好处就是: TreeMap中的key-value对总是处于有序状态,无须专门进行排序操作。

方法 语义
V put​(K key, V value) 将指定的值与此映射中的指定键相关联
V get​(Object key) 返回指定键映射到的值,或者null此映射是否不包含键的映射。
Set> entrySet() 返回Set此映射中包含的映射的视图。
boolean containsKey​(Object key) 是否存在指定key。
boolean containsValue​(Object value) 是否存在指定value
Set keySet() 集合的key转为Set。
V remove​(Object key) 移除指定key项的值

java.util.HashMap

和HashSet集合不能保证元素的顺序一样,HashMap也不能保证key-value对的顺序。并且类似于HashSet判断两个key是否相等的标准也是: 两个key通过equals()方法比较返回true、同时两个key的hashCode值也必须相等。
任何null对象都可以用作键或值,不论key或value都可以为null。

put的使用
put(“A”, 1);//key不重复返回null。
put(“A”, 2);//key重复返回1旧值。

初始化容量为16个,容量扩充是(当前容量*0.75),初始化即12时进行成倍扩充容量数。

java.util.LinkedHashMap

LinkedHashMap也使用双向链表来维护key-value对的次序,该链表负责维护Map的迭代顺序,与key-value对的插入顺序一致(注意和TreeMap对所有的key-value进行排序进行区
分)

java.util.HashTable

是一个古老的Map实现类。Hashtable是同步的。如果不需要线程安全的实现,建议使用它来 HashMap代替Hashtable。如果需要线程安全的高度并发实现,则建议使用ConcurrentHashMap代替 Hashtable。
任何非null对象都可以用作键或值,即不允许null对象的数据,不论key或value。

java.util.TreeMap

TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对(节点)时,需要根据key对节点进行排序。TreeMap可以保证所有的key-value对处于有序状态。同样,TreeMap也有两种排序方式: 自然排序定制排序
key不允许为null,因为需要依赖于接口对象中的compareTo(或compare)方法执行比较

java.util.Map.Entry接口

集合工具类

Stack栈

Stack是Vector提供的一个子类,用于模拟"栈"这种数据结构(LIFO后进先出)

public class Stack extends Vector
Deques也可以用作LIFO(后进先出)堆栈。应优先使用此接口,而不是遗留Stack类。当deque用作堆栈时,元素将从双端队列的开头推出并弹出。

方法 语义
public boolean empty() 测试此堆栈是否为空。
public E peek() 查看此堆栈顶部的对象,而不将其从堆栈中删除。
public E pop() 移除此堆栈顶部的对象,并将该对象作为此函数的值返回。
public E push​(E item) 将项目推到此堆栈的顶部。
public int search​(Object o) 返回对象在此堆栈上的从1开始的位置。

Queue队列

Queue用于模拟"队列"这种数据结构(先进先出FIFO)。队列的头部保存着队列中存放时间最长的元素,队列的尾部保存着队列中存放时间最短的元素。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素,队列不允许随机访问队列中的元素。结合生活中常见的排队就会很好理解这个概念。
注意:LinkedList也实现了Queue。
优先级队列:PriorityQueue 优先级队列的元素根据其 自然顺序排序,或者根据使用的Comparator 构造函数在队列构造时提供。请注意,此实现不同步。PriorityQueue 如果任何线程修改队列,则 多个线程不应同时访问实例。相反,使用线程安全PriorityBlockingQueue类。
java队列——queue详细分析:https://www.cnblogs.com/lemon-flm/p/7877898.html

Deque接口及其实现提供了更完整和一致的LIFO堆栈操作集,应该优先使用该类。例如:
Deque stack = new ArrayDeque();

java.util.Properties属性操作

Properties 继承于 Hashtable.表示一个持久的属性集.属性列表中每个键及其对应值都是一个字符串,且该类是线程安全的
Properties 类被许多Java类使用。例如,在获取环境变量时它就作为System.getProperties()方法的返回值。主要用于读取配置信息。

类结构:
public class Properties extends Hashtable
注意:因为Properties继承自Hashtable,所以 put和putAll方法可以应用于 Properties对象。强烈建议不要使用它们,因为它们允许调用者插入其键或值不是的条目 Strings。

方法 语义
public void load​(InputStream inStream) throws IOException 从输入字节流中读取属性列表(键和元素对)。
public void loadFromXML​(InputStream in) throws IOException, InvalidPropertiesFormatException 将指定输入流上的XML文档表示的所有属性加载到此属性表中。
public void storeToXML​(OutputStream os, String comment) throws IOException 发出表示此表中包含的所有属性的XML文档。
public String getProperty​(String key, String defaultValue) 搜索具有指定键的属性。
public Object setProperty​(String key, String value) 设置指定键的属性,调用Hashtable方法put。

Java关于Properties用法的总结(一):https://www.cnblogs.com/bakari/p/3562244.html
java读取properties文件总结:http://www.cnblogs.com/xdp-gacl/p/3640211.html

java.util.Collections工具类

Java中Collection和Collections的区别 https://www.cnblogs.com/cathyqq/p/5279859.html
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

public class CollectionsTest {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        list.add(34);
        list.add(55);
        list.add(56);
        list.add(89);
        list.add(12);
        list.add(23);
        list.add(126);
        System.out.println(list);

        //对集合进行排序
        Collections.sort(list);
        System.out.println(list);

        //对集合进行随机排序
        Collections.shuffle(list);
        System.out.println(list);

        //获取集合最大值、最小值
        int max = Collections.max(list);
        int min = Collections.min(list);
        System.out.println("Max:" + max + " Min: " + min);

        List<String> list2 = Arrays.asList("Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday".split(","));
        System.out.println(list2);

        //查找集合指定元素,返回元素所在索引
        //若元素不存在,n表示该元素最有可能存在的位置索引
        int index1 = Collections.binarySearch(list2, "Thursday");
        int index2 = Collections.binarySearch(list2, "TTTTTT");
        System.out.println(index1);
        int n = -index2 - 1;

        //查找子串在集合中首次出现的位置
        List<String> subList = Arrays.asList("Friday,Saturday".split(","));
        int index3 = Collections.indexOfSubList(list2, subList);
        System.out.println(index3);
        int index4 = Collections.lastIndexOfSubList(list2, subList);
        System.out.println(index4);

        //替换集合中指定的元素,若元素存在返回true,否则返回false
        boolean flag = Collections.replaceAll(list2, "Sunday", "tttttt");
        System.out.println(flag);
        System.out.println(list2);

        //反转集合中的元素的顺序
        Collections.reverse(list2);
        System.out.println(list2);

        //集合中的元素向后移动k位置,后面的元素出现在集合开始的位置
        Collections.rotate(list2, 3);
        System.out.println(list2);

        //将集合list3中的元素复制到list2中,并覆盖相应索引位置的元素
        List<String> list3 = Arrays.asList("copy1,copy2,copy3".split(","));
        Collections.copy(list2, list3);
        System.out.println(list2);

        //交换集合中指定元素的位置
        Collections.swap(list2, 0, 3);
        System.out.println(list2);

        //替换集合中的所有元素,用对象object
        Collections.fill(list2, "替换");
        System.out.println(list2);

        //生成一个指定大小与内容的集合
        List<String> list4 = Collections.nCopies(5, "哈哈");
        System.out.println(list4);

        //为集合生成一个Enumeration
        List<String> list5 = Arrays.asList("I love my country!".split(" "));
        System.out.println(list5);
        Enumeration<String> e = Collections.enumeration(list5);
        while (e.hasMoreElements()) {
            System.out.println(e.nextElement());
        }
    }
}
方法 语义
Collections.sort(list) //list: 1 2 4 5 6 7 8 list升序。
Collections.sort(list,Collections.reverseOrder())//list:8 7 6 5 4 2 1 list降序。
Collections.reverse(list)//list:4 1 8 6 2 7 5 list逆序。
Arrays.sort(a)//a: 1 2 4 5 6 7 8 数组升序。
Arrays.sort(a,Collections.reverseOrder())//a: 8 7 6 5 4 2 1 数组降序。

你可能感兴趣的:(Java基础)