一、Java集合框架概述
●一方面,面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而Java集合就像一种容器,可以动态地把多个对象的引用放入容器中。
➢数组在内存存储方面的特点:
数组初始化以后,长度就确定了。
数组声明的类型,就决定了进行元素初始化时的类型
➢数组在存储数据方面的弊端:
数组初始化以后,长度就不可变了,不便于扩展
数组中提供的属性和方法少,不便于进行添加、删除、插入等操作,且效率不高。同时无法直接获取存储元素的个数
数组存储的数据是有序的、可以重复的。--->存储数据的特点单一
●Java集合类可以用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组。
二、集合框架
●Java集合可分为Collection和Map两种体系
➢Collection接口:单列数据,定义了存取一组对象的方法的集合
List:元素有序、可重复的集合 --> “动态”数组
ArrayList、LinkedList、Vector
Set: 元素无序、不可重复的集合
HashSet、LinkedHashSet、TreeSet
➢Map接口:双列数据,保存具有映射关系“key-value对”的集合
HashMap、LinkedHashMap、TreeMap、Hashtable、Properties
Collection接口继承树
Map接口继承树
三、Collection接口
1.Collection接口常用的15种方法
package com.xudong.java;
import org.junit.Test;
import java.util.*;
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();
//1.add():将元素e添加到coll中
coll.add("AA");
coll.add("BB");
coll.add(123);//自动装箱
coll.add(new Date());
//2.size():获取添加的元素的个数
System.out.println(coll.size());
//3.addAll(Collection coll1):将coll1集合中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll.addAll(coll1);
System.out.println(coll.size());
System.out.println(coll);
//4.clear():清空集合元素
coll.clear();
//5.isEmpty():判断当前集合是否有元素
System.out.println(coll.isEmpty());
}
@Test
//注意:向Collection接口的实现类的对象中添加数据obj时,要求所在类要重写equals()
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry",21));
//6.contains(Object obj):判断当前集合中是否包含obj
boolean contains = coll.contains("Jerry");
System.out.println(contains);
//7.containsAll(Collection coll1):判断形参coll1中的所有元素是否都存在于当前集合中。
Collection coll1 = Arrays.asList(132,456,789);
System.out.println(coll.containsAll(coll1));
//8.remove(Object obj):从当前集合中移除obj元素。
System.out.println(coll.remove(123));
//9.removeAll(Collection coll1):从当前集合中移除coll1中的所有元素,差集
coll.removeAll(coll1);
System.out.println(coll);
System.out.println(coll1);
//10.retainAll():求交集
coll.retainAll(coll1);
System.out.println(coll);
//11.equals():有序的比较两个集合。全等返回true
//12.hashCode():返回当前对象的哈希值
//13.toArray() 集合 ---> 数组
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
@Test
public void test3(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry",21));
//14.toArray() 集合 ---> 数组
Object[] arr = coll.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//15.asList():数组 ---> 集合
List<String> list = Arrays.asList(new String[]{"A", "C", "H", "M", "B"});//new 包装类的对象
System.out.println(list);
}
}
2.Iterator迭代器接口。遍历集合元素
●Iterator对 象称为迭代器(设计模式的一种),主要用于遍历Collection 集合中的元素。
●GOF给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器模式,就是为容器而生。类似于“公交车.上的售票员”、“火车上的乘务员”、“空姐”。
●Collection接口继承了java.lang.lterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
●Iterator仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator对象,则必须有一个被迭代的集合。
●集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
package com.xudong.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorTest {
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry",21));
//遍历集合种元素
Iterator iterator = coll.iterator();
System.out.println(iterator.next());
//方法一:不推荐
// for (int i = 0; i < coll.size(); i++) {
// System.out.println(iterator.next());
// }
//方法二:
while (iterator.hasNext()){
System.out.println(iterator.next());
}
//remove移除指定元素
Iterator iter = coll.iterator();
while (iter.hasNext()){
Object obj = iter.next();
if ("Tom".equals(obj)){
iter.remove();
}
}
System.out.println(coll);
}
}
Iterator新特性
使用foreach循环遍历集合元素
●Java 5.0提供了foreach 循环迭代访问Collection和数组。
●遍历操作不需获取Collection或数组的长度,无需使用索引访问元素。
●遍历集合的底层调用Iterator完成操作。
●foreach还可以用来遍历数组。
package com.xudong.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
public class forTest {
@Test
public void test(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(false);
coll.add(new Person("Jerry",21));
//for(集合元素类型 局部变量 : 集合对象)
for (Object obj : coll){
System.out.println(obj);
}
}
@Test
public void test1(){
int[] arr = new int[]{1,2,33,2,5,4,6,7};
//for(数组元素类型 局部变量 : 数组对象)
for (int i : arr){ //将arr取到的值赋给i,数组内容不会改变。
System.out.println(i);
}
}
}
3.List接口
3.1 List接口概述
●鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
●List集合类中元素有序、且可重复,集合中的每个元素索都有其对应的顺序索引。
●List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
●JDK API中List接口的实现类常用的有: ArrayList、LinkedList 和 Vector。
ArrayList: 作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
LinkedList: 对于频繁的插入、删除操作,使用此类效率E比ArrayList高; 底层使用双向链表存储
Vector: 作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
3.2 ArrayList 源码分析
jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//eLementData[e] = new Integer(123);
...
list.add(11);//如果此次的添加导致底层eLementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器: ArrayList list = new ArrayList(int capacity)
jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到eLementData
后续的添加和扩容操作与jdk 7无异。
小结: jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8 中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
3.3 LinkedList 源码分析
Linkedlist list = new LinkedList(); //内部声明了Node类型的first和last属性,默认值为null
list. add(123);//将123封装到Node中,创建了Node对象。
3.4 List接口方法
●List除了从Collection集合继承的方法外,List 集合里添加了一些根据索引来操作集合元素的方法。
➢void add(int index, Object ele):在index位置插入ele元素
➢boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
➢Object get(int index):获取指定index位置的元素
➢int indexOf(Object obj):返回obj在集合中首次出现的位置
➢int lastlndexOf(Object obj): 返回obj在当前集合中未次出现的位置
➢Object remove(int index):移除指定index位置的元素,并返回此元素
➢Object set(int index, Object ele):设置指定index位置的元素为ele
➢List subList(int fromIndex, int tolndex):返回从fromIndex到toIndex位置的子集合
4.Set接口
4.1 Set接口概述
●Set接口是Collection的子接口,set接口没有提供额外的方法
●Set集合是无序的,不可重复的。
➢无序性:不等于随机性。存储的数据根据哈希值存储
➢不可重复性:保证添加的元素按照equals()判断时,不能返回true。
●Set判断两个对象是否相同不是使用==运算符,而是根据equals()方法
4.2 Set实现类之一:HashSet
●HashSet是Set接口的典型实现,大多数时候使用Set集合时都使用这个实现类。
●HashSet按Hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能。本质上是数组与链表的结合体。
●HashSet具有以下特点:
➢不能保证元素的排列顺序
➢HashSet不是线程安全的
➢集合元素可以是null
●HashSet集合判断两个元素相等的标准:两个对象通过hashCode()方法比较相等,并且两个对象的equals()方法返回值也相等。
●对于存放在Set容器中的对象,对应的类一定要重写equals()和hashCode(Object obj)方法,以实现对象相等规则。即:“ 相等的对象必须具有相等的散列码”。
4.3 Set实现类之二:LinkedHashSet
●LinkedHashSet是HashSet的子类
●LinkedHashSet根据元素的hashCode值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的。遍历其内部数据时,可以按照添加的顺序遍历。
●LinkedHashSet插入性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能。
●LinkedHashSet不允许集合元素重复。
4.4 Set实现类之三:TreeSet
●TreeSet是SortedSet接口的实现类,TreeSet 可以确保集合元素处于排序状态。
●TreeSet底层使用红黑树结构存储数据。可以按照添加对象的指定属性,进行排序。
●新增的方法如下:(了解)
Comparator comparator()
Object first()
Object last()
Object lower(Object e)
Object higher(Object e)
SortedSet subSet(fromElement, toElement)
SortedSet headSet(toElement)
SortedSet tailSet(fromElement)
●TreeSet两种排序方法:自然排序和定制排序。默认情况下,TreeSet采用自然排序。
●向TreeSet中添加的数据,要求是相同类的对象。
添加元素的过程,以HashSet为例:
我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断数组此位置上是否已经有元素:
如果此位置上没有其他元素,则元素a添加成功。---> 情况1
如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
如果hash值不相同,则元素a添加成功。---> 情况2
如果hash值相同,进而需要调用元素a所在类的equlas()方法:
equals()返回true,元素a添加失败
equaLs()返回false,则元素a添加成功。--->情况3
对于添加成功的情况2和情况3而言:
元素a与已经存在指定索引位置上数据以链表的方式存储。
jdk 7 :元素a放到数組中,指向原来的元素。
jdk 8 :原来的元素在数組中,指向元素a
Map接口
1.Map的实现类
/----Map:双列数据,存储key-value对的数据--- 类似于高中的函数: y = f(x)
/----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null 的key和value
/----LinkedHashMap:保证在遍历map元素时,可 以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,
指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高FHashMap。
/---- TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序底层使用红黑树
/----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key value
/----Properties: 常用来处理配置文件。key 和value都是String类型
HashMap的底层: 数组+链表 (jdk7及之前)
数组+链表+红黑树 (jdk 8)
Map结构的理解:
Map中的key:无序的、不可重复的,使用Set存储所有的key ---> key所在的类要重写 equals() 和 HashCode() (HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value。---> value 所在的类要重写 equals()
一个键值对: key-value 构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry
2.Map接口常用的方法:
●添加、删除、修改操作:
➢Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
➢void putAI(Map m):将m中的所有key-value对存放到当前map中
➢Object remove(Object key):移除指定key的key-value对,并返回value
➢void clear():清空当前map中的所有数据
●元素查询的操作:
➢Object get(Object key):获取指定key对应的value
➢boolean containsKey(Object key):是否包含指定的key
➢boolean containsValue(Object value):是否包含指定的value
➢int size():返回map中key-value对的个数
➢boolean isEmpty():判断当前map是否为空
➢boolean equals(Object obj):判断当前map和参数对象obj是否相等
●元视图操作的方法:
➢Set keySet():返回所有key构成的Set集合
➢Collection values():返回所有value构成的Collection集合
➢Set entrySet():返回所有key-value对构成的Set集合
3.HashMap的底层实现原理
➢JDK7.0
HashMap map = new HashMap():
在实例化以后,底层创建了长度是16的一维数组Entry[] table.
...可能已经执行过多i次put...
map.put (key1, value1):
首先,调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算以后,得到在Entry 数组中的存放位置。如果此位置上的数据为空,此时的key1-value1 添加成功。---- 情况1
●如果此位置上的数据不为空,( 意味着此位置上存在一个或多个数据(以链表形式存在)),比较key1和已经存在的一个或多个数据的哈希值:
●如果key1的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1 添加成功。---- 情况2
●如果key1的哈希值和已经存在的某一个数据(key2-value2) 的哈希值相同,继续比较:调用key1所在类的equals(key2)
●如果equals()返回false:此时key1-value1添加成功。---- 情况3
●如果equaLs()返回true:使用value1替换value2。
补充:关于情况2和情况3:此时key1-value1 和原来的数据以链表的方式存储。
在不断添加的过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。
默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
➢jdk8相较于jdk7在底层实现方面的不同:
1. new HashMap():底层没有创建一个长度为16的数组
2. jdk 8底层的数组是: Node[], 而非Entry[]
3.首次调用put()方法时,底层创建长度为16的数组
4. jdk7 底层结构只有:数组+链表。jdk8 中底层结构:数组+链表+红黑树。
当数组的某一个索引位置上的元素以链表形式存在的数据个数> 8且当前数组的长度> 64时,此时此索引位置上的所有数据改为使用红黑树存储。
4.HashMap源码中的重要常量
**DEFAULT_INITIAL_CAPACITY **: HashMap的默认容量,16
**MAXIMUM_CAPACITY **: HashMap的最大支持容量, 2^30
DEFAULT_LOAD_FACTOR: HashMap的默认加载因子0.75
TREEIFY_THRESHOLD: Bucket中链表长度大于该默认值8,转化为红黑树
UNTREEIFY_THRESHOLD: Bucket中红黑树存储的Node小于该默认值,转化为链表MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量64。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行) resize扩容操作这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)
table:存储元素的数组,总是2的n次幂
entrySet:存储具体元素的集
size: HashMap中存 储的键值对的数量
modCount: HashMap扩容和结构改变的次数。
threshold:扩容的临界值12,=容量填充因子 (160.75)= 12
loadFactor:填充因子
5.LinkedHashMap底层原理
6.Properties
●Properties类是Hashtable的子类,该对象用于处理属性文件
●由于属性文件里的key、value 都是字符串类型,所以Properties里的key和value都是字符串类型
●存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法
五、Collections工具类
●Collections是一个操作Set、List 和Map等集合的工具类
●Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法
●排序操作: ( 均为static方法)
➢reverse(List):反转List中元素的顺序
➢shuffle(List):对List集合元素进行随机排序
➢sort(List):根据元素的自然顺序对指定List集合元素按升序排序
➢sort(List, Comparator): 根据指定的Comparator产生的顺序对List 集合元素进行排序
➢swap(List, int, int): 将指定list集合中的i处元素和j处元素进行交换
1.Collections常用方法
查找、替换
●Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
●Object max(Collection,Comparator): 根据Comparator指定的顺序,返回给定集合中的最大元索
●Object min(Collection)
●Object min(Collection,Comparator)
●int frequency(Collection,Object): 返回指定集合中指定元素的出现次数
●void copy(List dest,List src):将src中的内容复制到dest中
List dest = Array.asList(new Object[src.size()]);
Sysout.out.println(dest.size);//src.size()
Collections.copy(dest,src);
Sysout.out.println(dest);
●boolean replaceAll(List list, Object oldVal, Object newVal):使用新值替换List对象的所有旧值
同步控制
●Collections类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题