前述
复习一下Java中的集合类,是面试笔试中常考察的一个点,特地做的整理。
什么是集合类?
集合类,也叫容器类。Java集合类可以用来存储数量庞大的对象。
我们和数组进行对比:
数组:存储基本数据类型,数据类型单一,长度固定,不能动态增大容量。
集合:存储的即可是基本类型的值,也可以是对象,可以存储多种数据类型,长度可变,可以动态增大容量。
Java集合类的体系
Java集合类主要有两个接口派生而出:Collection和Map。即集合类都是实现的这两个接口。我们在实际编程中经常使用的有 List、Set、Queue(这些是实现的 Collection 接口)HashMap、TreeMap、HashTable(这些实现的 Map 接口)
Collection接口结构
Collection 接口位于 Java.util 包下,是一个父接口, List、Set、Queue 都是实现的 Collection 接口。Collection 做为父接口提供一些操作集合类的方法,因此它的子接口也有这些方法。
Collection 接口不能被实例化,并且在实际的编程过程中几乎不会使用它进行数据的存储。
Map接口结构
Map 接口实现的是键值对的存储,类似 python 中的 dict。
Map中比较常见的是 HashMap、TreeMap、Hashtable 类。
Set接口
Set 接口继承自 Collection 接口,Collection 接口有的方法 Set 接口中也有。
Set 接口自身的特性:
-
- 不允许重复元素
- 不区分先后顺序(无序)
- 允许值是 null
Set 接口有两个比较常用的具体实现,HashSet、TreeSet。下面分别说一下这两个集合类。
HashSet
主要特点是快速查找元素。HashSet 是基于 Hash 算法来实现的,在每次添加新的对象的时候,会根据散列码来判断对象是否重复,散列码的获取是通过 Object 的 hashCode() 来实现的。同样 HashSet 也是无序的。
1 import java.util.*; 2 /** 3 * @author jyroy 4 * HashSet使用 5 */ 6 public class HashSetDemo { 7 public static void main(String[] args) { 8 HashSet hashSet = new HashSet(); 9 hashSet.add("Tom"); 10 hashSet.add("Jack"); 11 hashSet.add("Roy"); 12 System.out.println(hashSet); 13 } 14 }
当有重复元素添加时,结果如下
1 import java.util.*; 2 /** 3 * @author jyroy 4 * HashSet使用 5 */ 6 public class HashSetDemo { 7 public static void main(String[] args) { 8 HashSet hashSet = new HashSet(); 9 hashSet.add("Tom"); 10 hashSet.add("Tom"); 11 hashSet.add("Jack"); 12 hashSet.add("Roy"); 13 System.out.println(hashSet); 14 } 15 }
TreeSet
主要特点是会进行自然排序,同样不能重复。参考如下的代码,可以看出 TreeSet 把原本无序的值进行了重新排序,依据的就是自然排序。
1 import java.util.*; 2 public class TreeSetDemo { 3 public static void main(String[] args) { 4 TreeSet treeSet = new TreeSet(); 5 treeSet.add("Tom"); 6 treeSet.add("Jack"); 7 treeSet.add("Roy"); 8 System.out.println(treeSet); 9 } 10 }
然而,在实际开发中很多时候我们很多时候遵循的不是自然排序,而是有自己定义的排序规则,我们要做的就是新建一个类并且实现 compareTo() 方法。
下面的代码实现的是根据年龄由小到大进行排序,比较简单,一看就懂的代码。
1 import java.util.TreeSet; 2 class Person implements Comparable{ 3 public String name; 4 public int age; 5 @Override 6 public String toString() { 7 return "Person [name=" + name + ", age=" + age + "]"; 8 } 9 @Override 10 public int compareTo(Object o) { 11 Person person = (Person) o; 12 if(this.age < person.age) { 13 return -1; 14 }else if(this.age > person.age) { 15 return 1; 16 }else { 17 return 0; 18 } 19 } 20 } 21 public class TreeSetDemoCompareto { 22 public static void main(String[] args) { 23 TreeSet treeSet = new TreeSet(); 24 Person person1 = new Person(); 25 Person person2 = new Person(); 26 Person person3 = new Person(); 27 person1.name = "Tom"; 28 person1.age = 20; 29 person2.name = "Jack"; 30 person2.age = 21; 31 person3.name = "Roy"; 32 person3.age = 22; 33 treeSet.add(person1); 34 treeSet.add(person2); 35 treeSet.add(person3); 36 System.out.println(treeSet); 37 } 38 }
List接口
List 接口继承了 Collection 接口,Collection 接口有的方法 List 中也有。
List 接口自身的特性:
-
- 利用数组方式提供了获取、修改、删除的功能。
- 可以通过方法来获取元素的位置。
- 允许重复元素的有序集合。
List 接口有两个比较常用的具体实现,ArrayList、Vector、LinkedList。下面分别说一下这两个集合类。
ArrayList
ArrayList 本身是通过数组的方式实现的,大小可以随着对象的增加而增加。查询效率比较高,增加、删除的效率比较低,这也是数组的一种特性。非线程安全的。而且是有序的。
1 import java.util.*; 2 /** 3 * @author jyroy 4 */ 5 public class ArrayListDemo { 6 public static void main(String[] args) { 7 List arrayList = new ArrayList(); 8 arrayList.add("Tom"); 9 arrayList.add("Jack"); 10 arrayList.add("Roy"); 11 System.out.println(arrayList); 12 } 13 }
Vector
Vector 和 ArrayList 是相似的,都是通过数组来实现的。
最大的区别就是线程安全方面。Vector 是线程安全的,同步的,Vector类对集合的元素操作时都加了synchronized,保证线程安全。而 ArrayList 是非线程安全的。也是因为线程安全的问题,ArrayList 的查询效率比 Vector 要高。
另外一个区别是在扩容方面,Vector默认扩容是增长一倍的容量,Arraylist是增长50%+1的容量。
Vector与ArrayList的remove,add(index,obj)方法都会导致内部数组进行数据拷贝的操作,这样在大数据量时,可能会影响效率。
1 import java.util.*; 2 public class VectorDemo { 3 public static void main(String[] args) { 4 List vectorList = new Vector(); 5 vectorList.add("Roy"); 6 vectorList.add("Tom"); 7 vectorList.add("Jack"); 8 System.out.println(vectorList); 9 } 10 }
LinkedList
LinkedList 和 ArrayList、Vector相比,在方法的使用上都是相似的。
LinkedList 是基于双向链表的,比较利于对象的增加和删除操作,但是对查询的支持不够好,这是链表的特点。 LinkedList 同样是线程不安全的。
1 import java.util.*; 2 public class LinkedListDemo { 3 public static void main(String[] args) { 4 List linkedList = new LinkedList(); 5 linkedList.add("Roy"); 6 linkedList.add("Jack"); 7 linkedList.remove(0); 8 linkedList.add("Tom"); 9 System.out.println(linkedList); 10 } 11 }
Queue接口
Queue 是 Collection 的子接口,中文称为队列,特性就是先进先出,先进入队列的对象,在进行删除的时候最先被删除。所有的删除操作都是在队首进行的,所有的插入操作都是在队尾进行的。
1 import java.util.*; 2 public class QueueDemo { 3 public static void main(String[] args) { 4 Queue queue = new LinkedList(); 5 queue.add("Tom"); 6 queue.add("Roy"); 7 System.out.println(queue.poll()); //出队列操作 8 queue.add("Jack"); 9 System.out.println(queue); 10 } 11 }
HashMap
HashMap 是基于散列表的 Map 接口的实现类,线程不安全。
散列表是通过关键码值(Key value)而直接进行访问的数据结构。它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置。
下面的代码实现了获取key为 2 的元素的 value。
1 import java.util.*; 2 public class HashMapDemo { 3 public static void main(String[] args) { 4 HashMap hashMap = new HashMap(); 5 hashMap.put(1, "Tom"); 6 hashMap.put(2, "Jack"); 7 hashMap.put(3, "Roy"); 8 System.out.println(hashMap.get(2)); 9 System.out.println(hashMap); 10 } 11 }
TreeMap
TreeMap 是根据红黑树算法实现的,TreeMap最大的特性就是支持自然排序。从下面的代码中也可以非常清晰的看出 TreeMap 利用 key 值进行了自然排序。
红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
1 import java.util.*; 2 public class TreeMapDemo { 3 public static void main(String[] args) { 4 TreeMap treeMap = new TreeMap(); 5 treeMap.put("2", "Tom"); 6 treeMap.put("1", "Roy"); 7 treeMap.put("3", "Jack"); 8 System.out.println(treeMap); 9 } 10 }
HashTable
HashTable 和 HashMap 是基本一致的。
最大的区别是 HashTable 是线程安全的,HashMap 不是。
1 import java.util.*; 2 public class HashTableDemo { 3 public static void main(String[] args) { 4 Hashtable hashtable = new Hashtable(); 5 hashtable.put("2", "Tom"); 6 hashtable.put("1", "Roy"); 7 hashtable.put("3", "Jack"); 8 System.out.println(hashtable); 9 } 10 }
常见问题
这些问题是在其他一些大佬的博客中看到,做了一个整理,都是 Java 集合类的问题。
1. ArrayList和LinkedList的特点和区别?
ArrayList: 底层用数组实现,由于数组可以通过下标直接访问指定索引的元素,因此,ArrayList通过索引查询元素非常快。但由于插入和删除元素时都会进行数组的重新排列,因此,ArrayList的插入和删除操作比较慢。
LinkedList:底层用链表实现,由于链表没有具体的下标,因此,访问某个索引的节点时需要遍历该节点前面的所有元素,速度比较慢。由于插入和删除元素时只需要更新相应元素的指针(或引用),不用重新排列元素,因此,LinkedList对插入和删除操作比较快。
LinkedList 比 ArrayList消耗更多的内存,因为 LinkedList 中的每个节点存储了前后节点的引用。
2. ArrayList和Vector的区别?
ArrayList非线程安全,Vector线程安全。在扩容时,ArrayList默认扩容当前容量的50%,但Vector默认扩容当前容量的100%。
3. HashSet和TreeSet的区别?
HashSet基于HashMap,用键来存放HashSet的值,由于HashMap的键不能重复,因此,HashSet的值也不会重复,这是集合的一个特点。
TreeSet基于TreeMap,也是用键来存放TreeSet的值。TreeMap的底层实现是红黑树,其根据键排序,可以得到排好序的数据。
4. HashMap和HashTable的区别?
HashMap非线程安全,HashTable线程安全。
HashMap可以允许一个null键和多个null值,但HashTable不允许,会出现NullPointerException。
5. HashMap和TreeMap的区别?
HashMap中存放的键是随机的,具有较快的访问和存取速度,TreeMap中的键是按照自然排序排好的。
6. Java集合框架的基础接口有哪些?
Collection 和 Map ,一个元素集合,一个是键值对集合; 其中 List、Set、Queue 接口继承了 Collection 接口,一个是有序元素集合,一个是无序元素集合,一个是队列; 而 ArrayList 和 LinkedList 实现了 List 接口,HashSet 实现了 Set 接口,这几个都比较常用; HashMap、HashTable、TreeMap 实现了 Map 接口,并且 HashTable 是线程安全的,HashMap 是非线程安全的,但是 HashMap 性能更好。
7. 如何决定选用HashMap还是TreeMap?
8. 哪些集合类提供对元素的随机访问?
ArrayList、HashMap、TreeMap和HashTable类提供对元素的随机访问。
9. Array和ArrayList有何区别?什么时候更适合用Array?
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
Array是指定大小的,而ArrayList大小是根据内容自动扩张的。
Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。尽管ArrayList明显是更好的选择,但也有些时候Array比较好用。
(1)如果列表的大小已经指定,大部分情况下是存储和遍历它们。
(2)对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。
(3)如果你要使用多维数组,使用[][]比List<List<>>更容易。