Java集合框架
一、集合框架的概述
1、集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储。
2、数组优缺点
2.1、数组在存储多个数据方面的特点:
> 一单初始化后,其长度就确定了。
> 数组一旦定义好,其元素的类型也就确定了。只能操作指定类型的数据。
比如:String[ ] arr ;int[ ] arr ; 可利用多态性,定义Object[ ] arr 变相操作多类型数据,但Object类中的方法过少,使用不便 ;
2.2、数组在存储多个数据方面的缺点:
> 一旦初始化后,其长度就不可修改。
> 数组中提供的方法非常有限,对于添加删除插入数据等操作,非常不便,同时效率不高。
> 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用。
> 数组存储数据的特点:有序、可重复。对无需不可重复的需求不能满足。
二、集合框架
|---------Collection接口:单列集合,用来存储一个一个的对象。
|--------List接口:存储有序的、可重复的数据。
|--------ArrayList
|--------LinkedList
|--------Victor
|--------Set接口:存储无序的不可重复的数据。
|--------HashSet
|--------LinkedSet
|--------TreeSet
|--------Map接口:双列集合,用来存储多对(key - value)形式的数据。
|--------HashMap
|--------LinkedHashMap
|--------TreeMap
|--------HashTable
|--------Properties
三、Collection中的方法
@Test public void test1(){ Collection coll = new ArrayList(); // add(Object e):将任意类型的元素e 添加到coll中 coll.add("AA"); coll.add(123); coll.add(new Date()); //int size():返回coll中元素个数 System.out.println(coll.size());//3 //addAll(Collection c):将另一个对象中的全部元素添加到调用该方法的对象中。 Collection coll1 = new ArrayList(); coll1.add("CC"); coll1.add(456); coll1.add(0.23456789D); coll.addAll(coll1); System.out.println(coll.size());//6 System.out.println(coll);//[AA, 123, Sun May 23 21:28:40 CST 2021, CC, 456, 0.23456789] //clear():清空当前集合中的所有元素。 coll.clear(); System.out.println(coll.size());//0 //boolean isEmpty():判断当前集合是否为空(size = 0)。 System.out.println(coll.isEmpty());//true //contains(Object o):判断对象o是否在当前集合中 //需要调用要判断的对象所在类被重写的equals()方法。 coll.add(new Person("Tom",20)); System.out.println(coll.contains(new Person("Tom",20)));//true //containsAll(Collection c):判断集合o中的元素是否都在调用该方法的集合中。 coll.add(123); coll.add(456); List<Integer> list = Arrays.asList(123, 456); System.out.println(coll.containsAll(list));//true //remove(Object o):删除集合中的某个元素 System.out.println(coll);//[Person{name='Tom', age=20}, 123, 456] coll.remove(123); System.out.println(coll);//[Person{name='Tom', age=20}, 456] //removeAll(Collection c):从当前集合中删除集合c中所有的元素。 coll.add(789); coll.add(77); coll.add(88); System.out.println(coll);//[Person{name='Tom', age=20}, 456, 789, 77, 88] List<Integer> list1 = Arrays.asList(77, 88); coll.removeAll(list1); System.out.println(coll);//[Person{name='Tom', age=20}, 456, 789] //retainAll(Collection c):将当前集合的元素更改为当前集合与集合c的公共元素。 coll.add(88888); coll.add(99999); coll.add(111111); coll.add("hello"); System.out.println(coll);//[Person{name='Tom', age=20}, 456, 789, 88888, 99999, 111111, hello] List hello = Arrays.asList(88888, 99999, "hello"); coll.retainAll(hello); System.out.println(coll);//[88888, 99999, hello] //equals(Object o):要想返回true,需要当前集合和集合o的元素都相同,如果是List接口下的集合还需要顺序相同。 Collection collection = new ArrayList(); collection.add(88888); collection.add(99999); collection.add(111111); collection.add("hello"); Collection collection2 = new ArrayList(); collection2.add(88888); collection2.add(99999); collection2.add(111111); collection2.add("hello"); Collection collection3 = new ArrayList(); collection3.add(99999); collection3.add(88888); collection3.add(111111); collection3.add("hello"); System.out.println(collection.equals(collection2));//true System.out.println(collection.equals(collection3));//false //hashCode():返回当前对象的哈希值 System.out.println(coll.hashCode());//187713450 //toArray():集合转换成数组 System.out.println( Arrays.toString(coll.toArray()));//[88888, 99999, hello] }
集合元素遍历操作,使用迭代器Iterator接口
@Test public void test2() { Collection collection = new ArrayList(); collection.add(88888); collection.add(99999); collection.add(111111); collection.add("hello"); Iterator iterator = collection.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } }
Iterator中的remove方法
@Test public void test3() { Collection collection = new ArrayList(); collection.add(88888); collection.add(99999); collection.add(111111); collection.add("hello"); Iterator iterator = collection.iterator(); while (iterator.hasNext()) { //java.lang.IllegalStateException 如果没有调用next()就直接remove 会报出该异常 //iterator.remove(); Object next = iterator.next(); if ("hello".equals(next)) { //通过迭代器的remove方法,而不是集合的remove方法。 iterator.remove(); //java.lang.IllegalStateException 如果调用next()方法后 多次调用remove 会报出该异常 //iterator.remove(); } } }
四、List接口
|---------Collection接口:单列集合,用来存储一个一个的对象。
|--------List接口:存储有序的、可重复的数据。
|--------ArrayList:作为List接口的主要实现类,线程不安全,但效率高。底层使用Object类型的数组Object[ ]。
|--------LinkedList:对于频繁的添加、删除操作,使用此类效率比ArrayList要高。底层使用双向链表存储。
|--------Victor:作为List接口的古老实现类,线程安全,效率低。底层使用Object类型的数组 Object[ ] elementData。
1、ArrayList,LinkedList,Victor三者异同
同:三个累都是实现了List接口,都存储有序的、可重复的数据。
不同:
ArrayList:作为List接口的主要实现类,线程不安全,但效率高。底层使用Object类型的数组Object[ ]。
LinkedList:对于频繁的添加、删除操作,使用此类效率比ArrayList要高。底层使用双向链表存储。
Victor:作为List接口的古老实现类,线程安全,效率低。底层使用Object类型的数组 Object[ ] elementData。
2、ArrayList源码分析
2.1、Jdk7
@Test public void test() { ArrayList list = new ArrayList();//此时底层创建了长度是10的Object[ ] 数组elementData list.add(123);//elementData[ 0 ] = new Integer( 123 ) //```````此处省略九次add( )调用 list.add("第11次调用");//如果此次的添加导致elementData数组的容量不足,则扩容。 //默认情况下,扩容为原来容量的倍。同时将原数组中的数据复制到新的数组中。 //建议在开发中使用 ArrayList list = new ArrayList( int capacity)。 }
2.2、Jdk8中ArrayList的变化
@Test public void test() { ArrayList list = new ArrayList();//此时底层Object[] elementData初始化为{} 并没有创建长度为10的数组。 list.add(123);//第一次调用add()方法时才创建长度为10的数组。并将数据添加elementData中 //········· //后续的添加和扩容与Jdk7无异。 }
2.3、小结
jdk7中的ArrayList对象创建类似于单利模式的饿汉式,而Jdk8中的ArrayList对象创建类似于单例模式的懒汉式,延迟了数组的创建,节省内存。
3、LinkedList源码分析
@Test public void test() { LinkedList list = new LinkedList();//内部声明了Node类型的first和last属性,默认值为null。 list.add(123);//将123封装到Node中,创建了Node对象。 /*其中Node体现了双向链表的特点,其定义方式为: private static class Node<E> { E item; LinkedList.Node<E> next; LinkedList.Node<E> prev; Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }*/ }
4、List接口中的常用方法(除Collection接口中的方法外)
@Test public void test() { ArrayList list = new ArrayList(); list.add(123); list.add(456); list.add(789); list.add("aaa"); list.add("bbb"); System.out.println(list);//[123, 456, 789, aaa, bbb] //插入操作add(int index,Object el); list.add(0, "AAAAAA"); System.out.println(list);//[AAAAAA, 123, 456, 789, aaa, bbb] //addAll(int index,Collection els);向集合中index位置插入els的所有元素。 List<Integer> list1 = Arrays.asList(1, 2, 3, 4); list.addAll(2,list1); System.out.println(list);//[AAAAAA, 123, 1, 2, 3, 4, 456, 789, aaa, bbb] //get(int index)获取指定位置的元素。 Object o = list.get(8); System.out.println(o);//aaa //indexOf(Object obj)返回obj在集合中首次出现的索引。 System.out.println(list.indexOf(1));//2 System.out.println(list.indexOf(10));//如果不存在返回-1 //lastIndexOf(Object obj)返回obj在集合中末次出现的索引。 list.add(1); System.out.println(list.lastIndexOf(1));//10 System.out.println(list.lastIndexOf(10));//如果不存在返回-1 //remove(int index)移除指定位置的元素,并返回该元素。 System.out.println(list);//[AAAAAA, 123, 1, 2, 3, 4, 456, 789, aaa, bbb, 1] Object removed = list.remove(10); System.out.println(removed);//1 System.out.println(list);//[AAAAAA, 123, 1, 2, 3, 4, 456, 789, aaa, bbb] //set(int index,Object el) 设置指定位置的元素为el。 list.set(0,"BBBBBBB"); System.out.println(list);//[BBBBBBB, 123, 1, 2, 3, 4, 456, 789, aaa, bbb] //如果指定的index大于等于集合的长度,会报角标越界异常 java.lang.IndexOutOfBoundsException: Index: 10, Size: 10 //list.set(10,"BBBBBBB"); //subList(int fromIndex,int toIndex)返回下标从fromIndex到toIndex的子集和。 List subList = list.subList(1, 5); System.out.println(list);//[BBBBBBB, 123, 1, 2, 3, 4, 456, 789, aaa, bbb] System.out.println(subList);//[123, 1, 2, 3] }
五、Set接口
1、概述
1.1、结构
|---------Collection接口:单列集合,用来存储一个一个的对象。
|--------Set接口:存储无序的、不可重复的数据。
|--------HashSet:作为Set接口的主要实现类;线程不安全;可以存储null值。
|--------LinkedSet:作为HashSet的子类,遍历其内部数据时,可以按照添加时的顺序遍历。
|--------TreeSet:可以指定按照添加对象的某属性进行排序。
1.2、Set接口中没有额外定义新的方法,使用的都是Collection接口中声明过的方法。
1.3、要求:Set中添加的数据,其所属的类一定要重写hashCode( )方法和equals( )方法。
要求:重写的hashCode( )方法和equals( )尽可能保持一致,相等的对象一定具有相等的散列码。
2、Set存储无序的、不可重复的数据
以HashSet为例说明:
2.1、无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。
2.2、不可重复性:保证添加的元素按照equals方法判断时,不能返回true。即:相同的元素只能添加一个。
3、添加元素的过程,以HashSet为例。
我们向HashSet中添加元素a:1、首先调用a元素所在类的HashCode( )方法,算出其哈希值。
2、使用某种算法,通过第一部算出的哈希值算出元素a在HashSet底层数组中的索引位置。
3、判断索引位置是否有元素。
3.1、如果没有则添加成功。--------->情况一
3.2、如果该索引位置存在其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的哈希值。
3.2.1、如果哈希值不同,则元素a添加成功。--------->情况二
3.2.2、如果哈希值相同,则调用元素a所在类的equals( )方法。
3.2.2.1、如果equals( )返回true,则说明元素a 与已存在的元素重复,添加失败。
3.2.2.2、如果equals( )返回false,则说明不存在与元素a重复的元素,元素a添加成功。--------->情况三
对于添加成功的情况二和情况三而言,元素a与已存在指定索引上的数据以链表的方式存储。
4、LinkedHashSet的使用
LinkedHashSet作为HashSet的子类,在添加数据时,每个数据还维护了两个引用,记录此数据的前一个数据和后一个数据。
优点:对于频繁的遍历操作,性能优于HashSet。
5、TreeSet的使用
5.1、向TreeSet中添加的数据必须是同一类型的数据。
5.2、两种排序方式:自然排序(实现Comparable接口)和定制排序(Comparator)。
5.3、自然排序中比较两个对象是否相同的标准为:compareTo( )返回 0 ,而不是equals( )。
5.4、定制排序中比较两个对象是否相同的标准为:compare( )返回 0 ,而不是equals( )。
六、Map接口:
1、结构:
|--------Map接口:双列集合,用来存储多对(key - value)形式的数据。
|--------HashMap:作为Map的主要实现类。线程不安全,效率高。可以存储key或value为null的数据。
底层:数组+链表(Jdk7及以前)
数组+链表+红黑树(Jdk8)
|--------LinkedHashMap:在原有的HashMap底层结构的基础上,添加了一对指针,指向前一个和后一个元素。保证在遍历Map元素时,可以按照添加顺序实现遍历,遍历操作执行效率优于HashMap。
|--------TreeMap:按照添加的key-value进行排序,实现排序遍历。按照key进行自然排序或定制排序。底层使用的红黑树。
|--------HashTable:作为Map古老实现类。线程安全,效率低。不能存储为null的key或value。
|--------Properties:常用来处理配置文件。key和value都是String类型。
2、Map的理解(已HashMap为例)
1、Map中的Key:无序的、不可重复的,使用Set存储Key。------------>key所在类型要重写equals( )和hashCode( ) 。
2、Map中的Value:无序的、可重复的。使用Collection存储所有的value。----------->value所在的类要重写equals( )方法。
3、一个键值对(key - value)构成了一个Entry对象。
4、Map中的Entry:无序的不可重复,使用Set存储所有Entry。
3、HashMap的底层实现原理
3.1、Jdk7
HashMap map = new HashMap();//在实例化之后,底层创建了长度为16的一维数组Entry[] table。
·······假设以存放了很多次·······
map.put("key1","value1");
1、首先调用key1所在类的hashCode()计算key1的哈希值,此哈希值经过某种算法计算以后,得到在Entry数组中的存放位置。
2、判断该位置上是否有数据。
2.1、如果不存在数据,则添加成功。--------->情况1
2.2、如果存在数据(一个或多个数据(多个数据以链表形式存在)),则比较key1和已存在的一个或多个数据的哈希值。
2.2.1、如果key1的哈希值与已存在数据的哈希值都不同,则添加成功。--------->情况2
2.2.2、如果key1的哈希值与已存在数据的哈希值相同,则调用key1所属类的equals( )方法比较key1和哈希值重复数据的key。
2.2.2.1、如果equals( )返回false,则添加成功。--------->情况3
2.2.2.2、如果equals( )返回true,则使用value1替换key值相同的数据的value值。
关于情况2 和情况3:此时key1和value1和原来的数据以链表的方式存储。
在不断添加的过程中,会涉及到扩容问题,默认的扩容方式为:扩容为原来的2倍,并将原有数据复制过来。
3.2、Jdk8
3.2.1、new HashMap( ) :底层没有直接创建长度为16的数组。
3.2.2、Jdk8底层的数组是Node[ ] 而非 Entry[ ]。
3.2.3、首次调用put方法时,底层创建长度为16的数组。
3.2.4、Jdk7底层结构只有数组+链表,而jdk8底层结构为数组+链表+红黑树。
当数组的某一个索引位置上的以链表形式存在的数据长度大于8,且当前数组长度大于64时,此处索引位置上的所有数据改为红黑树存储。
DEFAULT_INITIAL_CAPACITY:HashMap的默认容量。
DEFAULT_LOAD_FACTOR:HashMap默认的加载因子:0.75。
threshold:扩容的临界值:容量*加载因子。
TREEIFY_THRESHOLD:8 链表最大长度大于该默认值,转化为红黑树。
MIN_TREEIFY_CAPACITY:Node[ ]元素树化时,最小的hash表容量。64
4、LinkedHashMap底层实现原理
源码中:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}
5、Map中常用方法
@Test public void test1(){ Map map = new HashMap(); //添加 map.put("aa",123); map.put("cc",789); map.put(1,789); map.put("bb",456); System.out.println(map);//{aa=123, bb=456, cc=789, 1=789} //修改 map.put("aa",1121); System.out.println(map);//{aa=1121, bb=456, cc=789, 1=789} Map map1 = new HashMap(); map1.put("DD",999); map1.put("EE",111); map1.put("FF",222); //全部添加(把一个集合里的元素全部放到当前集合) map.putAll(map1); System.out.println(map);//{aa=1121, bb=456, cc=789, DD=999, EE=111, FF=222, 1=789} //删除 map.remove("DD"); System.out.println(map);//{aa=1121, cc=789, bb=456, EE=111, FF=222, 1=789} //清空 map.clear(); System.out.println(map);//{} //获取指定key对应的value map.putAll(map1); System.out.println(map.get("EE"));//111 //判断是否包含指定的key System.out.println(map.containsKey("EE"));//true //判断是否包含指定的value System.out.println(map.containsValue(222));//true //获取map中元素个数 System.out.println(map.size());//3 //判断当前map是否为空 System.out.println(map.isEmpty());//false //判断两个对象是否相同 System.out.println(map.equals(map1));//true //遍历Map Set set = map.keySet(); Iterator iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next());/*DD EE FF*/ } //遍历value Collection values = map.values(); Iterator iterator1 = values.iterator(); while (iterator1.hasNext()){ System.out.println(iterator1.next());/*999 111 222*/ } //遍历key-value Set entrySet = map.entrySet(); Iterator iterator2 = entrySet.iterator(); while (iterator2.hasNext()){ Map.Entry next = (Map.Entry) iterator2.next(); System.out.println(next.getKey() + " = " + next.getValue());/*DD = 999 EE = 111 FF = 222*/ } }
6、TreeMap
6.1、TreeMap中添加key-value,要求key必须是同一个类创建的对象,因为要按照key进行排序。

浙公网安备 33010602011771号