参考博文: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 集合可分为 Set
、List
和 Map
三种体系:
Set
:无序、不可重复的集合List
:有序,可重复的集合Map
:具有映射关系的集合,每个元素都包含key和value,key不允许重复,value可以注意: 在 Java5 之前,Java 集合会丢失容器中所有对象的数据类型,把所有对象都当成 Object 类型处理;从 Java5 增加了泛型以后,Java 集合可以记住容器中对象的数据类型。
既然数组可以存储多个数据,为什么还需要集合类?
下图由于CSDN对图像尺寸有限制,可能有点模糊,可以点此访问 https://img-blog.csdn.net/20160124221843905。
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 |
返回此集合中元素的迭代器。 |
有序集合:public interface List
方法 | 语义 |
---|---|
void add(int index, E element) |
在指定索引位置上增加单个实例元素 |
E set(int index, E element) |
在指定索引位置上修改单个实例元素 |
ListIterator |
返回此集合中元素的列表迭代器。 |
@SafeVarargs static |
返回包含任意数量元素的不可修改列表。 |
三个常用子类:
①、List list1 = new ArrayList();
底层数据结构是数组,查询快,增删慢;线程不安全,效率高
②、List list2 = new Vector();
底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合
③、List list3 = new LinkedList();
底层数据结构是链表,查询慢,增删快;线程不安全,效率高
怎么记呢?我们可以想象:
数组就像身上编了号站成一排的人,要找第10个人很容易,根据人身上的编号很快就能找到。但插入、删除慢,要望某个位置插入或删除一个人时,后面的人身上的编号都要变。当然,加入或删除的人始终末尾的也快。
链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但插入、删除快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable
ArrayList存储容量不足时,会以成倍的形式开辟存储容量,比如默认的10,20,40…
构造函数 | 语义 |
---|---|
public ArrayList() |
构造一个初始容量为10的空列表。 |
ArrayList(int initialCapacity) |
构造一个指定初始容量的列表。 |
基于链表实现
public class LinkedList extends AbstractSequentialList implements List, Deque, Cloneable, Serializable
没有提供像ArrayList(int initialCapacity)
构造一个指定初始容量的列表。为啥?基于链表,不需要。
类型不限制,可存储为null,且元素是在最后一个元素增加。性能快。
ArrayList与LinkedList区别?
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不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。
Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象。
有序集合:public interface Set
注意:是Set集合并不像List集合扩充了许多新的方法,所以无法使用List获取指定元素的方法
public E get(int index)
。
二个常用子类:
HashSet有以下特点
当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。
简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等
注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
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(对象比较)来比较。
该方式适用于Collection的所有子类。 使用频率比重高。
方法 | 语义 |
---|---|
boolean hasNext() |
判断迭代中是否有下一个元素。 |
E next() |
返回迭代中的下一个元素。 |
default void remove() |
从底层集合中移除此迭代器返回的最后一个元素。 |
Java 中的 Iterator 功能比较简单,并且只能单向移动:
该方式适用于Collection的所有子类。 使用频率比重一般。
仅适用于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()+"、");//取得上一个元素
}
仅适用于Vector类。
hasMoreElements():判断是否有下一个元素
nextElement():取得下一个元素
elements():取得Enumeration的接口对象
特点:
- 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 |
返回Set此映射中包含的映射的视图。 |
boolean containsKey(Object key) |
是否存在指定key。 |
boolean containsValue(Object value) |
是否存在指定value |
Set |
集合的key转为Set。 |
V remove(Object key) |
移除指定key项的值 |
和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时进行成倍扩充容量数。
LinkedHashMap也使用双向链表来维护key-value对的次序,该链表负责维护Map的迭代顺序,与key-value对的插入顺序一致(注意和TreeMap对所有的key-value进行排序进行区
分)
是一个古老的Map实现类。Hashtable是同步的。如果不需要线程安全的实现,建议使用它来 HashMap代替Hashtable。如果需要线程安全的高度并发实现,则建议使用ConcurrentHashMap代替 Hashtable。
任何非null对象都可以用作键或值,即不允许null对象的数据,不论key或value。
TreeMap就是一个红黑树数据结构,每个key-value对即作为红黑树的一个节点。TreeMap存储key-value对(节点)时,需要根据key对节点进行排序。TreeMap可以保证所有的key-value对处于有序状态。同样,TreeMap也有两种排序方式: 自然排序、定制排序
key不允许为null,因为需要依赖于接口对象中的compareTo(或compare)方法执行比较
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用于模拟"队列"这种数据结构(先进先出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();
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中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 |
数组降序。 |