集合Collection,也是一个数据容器,类似于数组,但是和数组是不一样的。集合是一个可变的容器,可以随时向集合中添加元素,也可以随时从集合中删除元素。另外,集合还提供了若干个用来操作集合中数据的方法。
集合里的数据,我们称之为元素(elements);集合只能用来存储引用类型的数据 ,不能存储八大基本数据类型的数据。
泛型的引入
Java SE 5.0以前,集合的元素只要是Object类型就行,那个时候任何对象都可以存放在集合内,但是从集合中获取对象后,需要进行正确的强制类型转换。但是,Java SE 5.0 以后,可以使用新特性"泛型",用来指定要存放在集合中的对象类型。避免了强制转换的麻烦。
集合与数组
-
数组是定长的容器,一旦实例化完成,长度不能改变。集合是变长的,可以随时的进行增删操作。
-
数组中可以存储基本数据类型和引用数据类型的元素,集合中只能存储引用数据类型的元素。
-
数组的操作比较单一,只能通过下标进行访问。集合中提供了若干个方便对元素进行操作的方法。
小贴士: 在存储引用类型时,集合与数组,存储的其实都是==对象的地址==。
Collection接口
Collection 接口是 List、Set 和 Queue 接口的父接口,该接口里定义了他们三个子接口的共同方法。既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。作为父接口,其子类集合的对象,存储元素的特点,可能是无序的,也可能是有序的,因此在父接口中并没有定义通过下标获取元素的方法功能。
在进行集合的遍历的时候,方式其实很多。但是,基本上所有的遍历,都与 Iterable 接口有关。这个接 口提供了对集合集合进行迭代的方法。只有实现了这个接口,才可以使用增强for循环进行遍历。
java
public class CollectionDemo {
public static void main(String[] args) {
//使用多态的向上造型创建一个子类型对象
Collection<String> c1 = new ArrayList<>();
//1. E add(E e) 向集合中添加元素
c1.add("A");
c1.add("B");
c1.add("C");
//2. boolean isEmpty()
boolean empty = c1.isEmpty();
System.out.println("empty: " + empty);
//3. int size(): 返回的是集合元素的个数
System.out.println(c1.size());
//4. String toString()
System.out.println(c1);
Collection<String> c2 = new ArrayList<>();
c2.add("B");
c2.add("C");
c2.add("D");
//5. addAll(Collection c)
c1.addAll(c2);
System.out.println(c1);
/*
* 6. boolean contains(Object o):
* 查看是否包含字符串 "B"
*/
boolean b = c1.contains("B");
System.out.println("是否包含字符串B :"+b);
/*
* 7. boolean containsAll(Collection c)
* 查看c1是否包含c2
*/
boolean b1 = c1.containsAll(c2);
System.out.println("c1集合是否包含c2集合:"+b1);
/*
* 8. boolean equals(Object o)
* 测试: 创建一个新的集合c3 添加元素 "A" "B" "C" "B","D","D"
* 判断两个集合是否相同
*/
Collection<String> c3 = new ArrayList<>();
c3.add("A");
c3.add("B");
c3.add("C");
c3.add("B");
c3.add("C");
c3.add("D");
System.out.println("c1和c3相同吗? :" + c1.equals(c3));
/** 9. boolean remove(Object o)
* 移除c1集合里的元素B,并查看剩下的元素 */
boolean d = c1.remove("B");
System.out.println(" 移除元素B :" + d);
System.out.println(c1);
/** 10. boolean removeAll(Collection<?> c)
* 解析: 从集合中删除另一个集合c中所具有的元素。
*
* 新建一个集合c4, 添加元素"B","C"
* 测试c1移除子集c4 */
Collection<String> c4 = new ArrayList<>();
c4.add("B");
c4.add("C");
c1.removeAll(c4);
System.out.println(c1);
/** 11. boolean retainAll(Collection c)
* 测试:向c1里添加"E","F","G", 然后保留子集c5, c5里的元素有"A","E" */
c1.add("E");
c1.add("F");
c1.add("G");
Collection<String> c5 = new ArrayList<>();
c5.add("A");
c5.add("E");
c5.add("H");
c1.retainAll(c5);
System.out.println(c1);
/** 12. void clear():
* 测试:清空c1 */
c1.clear();
System.out.println(c1);
System.out.println(c1.size());
}
}
-
增强for循环中不允许对集合中的元素进行修改,修改是无效的
-
增强for循环中不允许对集合的长度进行修改,否则会出现 ConcurrentModificationException
java
for (String ele : collection) {
System.out.println(ele);
}
迭代器Iterator,是一个接口, Collection集合中有一个方法 iterator() 可以获取这个接口的实现类 对象。在这个迭代器中,维护了一个引用,指向集合中的某一个元素。默认指向一个集合前不存在的元 素,可以认为是下标为-1的元素。
迭代器的工作原理:循环调用 next() 方法进行向后的元素指向,并返回新的指向的元素。同时,在向 后进行遍历的过程中,使用 hasNext() 判断是否还有下一个元素可以迭代。
在迭代器使用的过程中,需要注意:
-
不允许对集合中的元素进行修改
-
不允许对集合的长度进行修改
List子接口
-
List 是一个元素有序、且可重复的集合,集合中的每个元素都有其对应的顺序索引,从0开始
-
List 允许使用重复元素,可以通过索引来访问指定位置的集合元素。
-
List 默认按元素的添加顺序设置元素的索引。
-
List 集合里添加了一些根据索引来操作集合元素的方法
ArrayList和LinkedList
这两个类都是List接口的实现类(子类)。两者在实现上的底层原理对比
-
ArrayList是实现了基于动态数组的数据结构,对象存储在连续的位置上
-
LinkedList基于双链表的数据结构,链表中的每个节点都包含了前一个和后一个元素的引用。
-
对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。
-
对于插入和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
PS:当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义
Queue子接口
-
队列Queue也是Collection的一个子接口,它也是常用的数据结构,可以将队列看成特殊的线性表,队列限制对线性表的访问方式:只能从一端添加(offer)元素,从另一端取出(poll)元素。
-
队列遵循先进先出(FIFO first Input First Output)的原则
-
实现类LinkedList也实现了该接口,选择此类实现Queue的原因在于Queue经常要进行添加和删除操作,而LinkedList在这方面效率比较高。
-
其主要方法如下:
方法 | 解析 |
---|---|
boolean offer(E e) | 作用:将一个对象添加到队尾,如果添加成功返回true |
E poll() | 作用:从队首删除并返回这个元素 |
E peek() | 作用:查看队首的元素 |
Deque
Deque是Queue的子接口,定义了所谓的"双端队列",即从队列的两端分别可以入队(offer)和出队(poll)。同样,LinkedList实现了该接口
Set子接口
-
Set集合中的元素是无序的(取出的顺序与存入的顺序无关)
-
Set集合中的元素不能重复
即不能把同一个东西或者相似的东西两次添加到同一个Set容器中,每次放入时都会进行判断是否存在,如果存在,就不添加。 如果能放入,与我们的设计初衷相违背了。 实现类
1)HashSet
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。
HashSet 按 Hash 算法来存储集合中的元素,因此具有很好的存取和查找性能。
HashSet 具有以下特点:
-
- 不能保证元素的排列顺序
-
- HashSet 不是线程安全的
-
- 集合元素可以是 null
当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值决定该对象在 HashSet 中的存储位置。
如果两个元素的 equals() 方法返回 true,但它们的 hashCode() 返回值不相等,hashSet 将会把它们存储在不同的位置,但依然可以添加成功。
2)LinkedHashSet
-
LinkedHashSet 是 HashSet 的子类
-
LinkedHashSet 集合根据元素的 hashCode 值来决定元素的存储位置,但它同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
-
LinkedHashSet 性能插入性能略低于 HashSet,但在迭代访问 Set 里的全部元素时有很好的性能。
-
LinkedHashSet 不允许集合元素重复。
3)TreeSet
TreeSet 是 SortedSet 接口的实现类, TreeSet集合是用来对元素进行排序的,同样他也可以保证元素的唯一。TreeSet 可以确保集合元素处于排序状态。
TreeSet 支持两种排序方法:自然排序和定制排序。默认情况下,TreeSet 采用自然排序
List排序
Comparable接口
如果集合里的元素想要排序,那么元素对象之间一定要有大小之分。这个大小之分是如何界定的呢??
此时,元素类型必须是Comparable接口的实现类,该接口提供了方法
- int compareTo(T t) 。规范了其子类是可以比较的,因此子类必须重写此抽象方法。
比较规则:(默认升序)
-
如当前对象大于给定对象,那么返回值应为>0的整数
-
若小于给定对象,那么返回值应为<0的整数
-
若两个对象相等,则应返回0
工具类提供的排序方法
Collections是集合的工具类,提供了很多便于我们操作集合的方法,其中就有用于集合排序的sort方法。
- static void sort(List<T> list)
作用是对指定的集合元素进行自然排序。前提元素类型必须实现Comparable接口
Comparator比较器接口
java类一旦定义好,就不要轻易再去修改它。因此当java类实现了Comparable接口,也就代表比较规则已经确定.
但是,有的时候我们想临时改变一下比较规则,怎么办呢?
此时我们可以采用Comparator接口回调的方式。它提供了一个抽象方法:
- int compare(T o1 , T o2)
比较规则如下
-
若o1>02, 则返回值应该>0
-
若o1<o2,则返回值应该<0
-
若o1==o2, 则返回值应该为0
工具类中提供了sort方法的重载方法
- static void sort(List<T> list , Comparator c)
作用:使用比较器c,指定临时排序规则
Collections
Collections 是一个操作 Set、List 和 Map 等集合的工具类,提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
排序操作
-
reverse(List):反转 List 中元素的顺序
-
shuffle(List):对 List 集合元素进行随机排序
-
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
-
sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
-
swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
2)查找、替换
-
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
-
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
-
Object min(Collection)
-
Object min(Collection,Comparator)
-
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
-
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
Map接口
Map是集合框架中的另一个父接口,它用来保存具有映射(一对一)关系的数据,这样的数据称之为键值对(Key-Value-Pair)。key可以看成是value的索引。特点如下:
-
key和value必须是引用类型的数据
-
作为key,在Map集合中不允许重复
-
key可以为null
-
key和value之间存在单向一对一关系,通过指定的key总能找到唯一,确定的value
根据内部数据结构的不同,Map接口有多种实现类,其中常用的有内部为hash表实现的
HashMap和内部为排序二叉树实现的TreeMap
HashMap的实现原理
HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突
图中,紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中
装载因子及其HashMap优化
capacity:容量,hash表里bucket(桶)的数量,也就是散列数组大小
initial capacity:初始容量,创建hash表时,初始bucket的数量,默认构建容量为16,也可以使用特定容量。
size:大小,当前散列表中存储数据的数量
load factor:加载银子,默认值0.75也就是75%,当向散列表增加数据时,如果size/capacity的值大于loadfactor,则发生扩容并且重新散列(rebash)
性能优化:加载因子较小时,散列查找性能会提高,但是也浪费了散列桶空间容量。0.75是性能和空间相对平衡结果。在创建散列表时指定合理容量,减少rehash能提供性能
TreeMap
TreeMap 存储 Key-Value对时,需要根据 Key 对 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
TreeMap 的 Key 的排序:
-
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
-
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口
Properties
Properties 类是 Hashtable 的子类,该对象用于处理属性文件
由于属性文件里的 key、value 都是字符串类型,所以 properties 里的 Key 和 Value 都是字符串类型的