【JavaSE】集合学习笔记
集合
-集合的理解和好处
- 数组的缺点
- 长度开始时必须指定,而且一旦指定,不能更改
- 保存的必须为同一类型的元素
- 使用数组进行增加元素的示意代码-比较麻烦
- 集合相比数组的优点
- 可以动态保存任意多个对象,使用比较方便!
- 提供了一系列方便的操作对象的方法:add、remove、set、get等
- 使用集合添加,删除新元素的示意代码-简洁了
-集合框架体系
列举Java中常用的一些集合类
- Java的集合类很多,主要分为两大类:单列集合Collection和双列集合Map
- Collection类下有List和Set两个子类;List类下有ArrayList、LinkedList、Vector三个子类;Set类下有HashSet和TreeSet两个子类。
- Map类下有HashMap、TreeMap、HashTable、Properties四个子类
除了上述提到的集合类外,其他还有很多功能各异的集合类
-Collection接口和常用方法
-
Collection接口实现类的特点
public interface Collection<E> extends Iterable<E>
- collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)
- Collection接口没有直接的实现子类,是通过它的子接口Set和List来实现的
-
Collection接口常用方法,以实现子类
- add:添加单个元素
- remove:删除指定元素
- contains:查找元素是否存在
- size:获取元素个数
- isEmpty:判断是否为空
- clear:清空
- addAll:添加多个元素
- containsAll:查找多个元素是否都存在
- removeAll:删除多个元素
- 说明:以上方法以ArrayList为例
-
Collection接口遍历元素方式1-使用Iterator(迭代器)
基本介绍
- Iterator对象称为选代器,主要用于遍历Collection集合中的元素
- 所有实现了Collection接口的集合类都有一个iterator0方法,用以返回一个实现了Iterator接口的对象,即可以返回一个选代器。
- Iterator的结构
- Iterator仅用于遍历集合,Iterator本身并不存放对象。
Iterator执行原理
- Iterator iterator = coll.iterator();//得到一个集合的选代器
- //hasNext0:判断是否还有下一个元素
- while(iterator.hasNext())
- //next0:指针下移,将下移以后集合位置上的元素返回
- System.out.println(iterator.nextO);
Collection col = new ArrayList(); col.add(newBook("三国演义","罗贯中",10.1); col.add(newBook("小李飞刀”,"古龙",5.1); col.add(new Book("红楼梦","曹雪芹",34.6); Iterator iterator = col.iterator(); while(iterator.hasNext())( Object next = iterator.next(); System.out.printIn(next); //再次遍历需要重置迭代器. iterator = col.iterator():
-
Collection接口遍历对象方式2-for循环增强
增强for循环,可以代替iterator迭代器,特点:增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。//基本语法
for(元素类型元素名:集合名或数组名)(
访问元素}
-
练习
package com.xijie.list_;import java.util.ArrayList;/*** 添加10个以上的元素(比如String"hello")* 在2号位插入一个元素"马妞笔教育”* 获得第5个元素* 删除第6个元素* 修改第7个元素* 再使用选代器遍历集合* 要求:使用List的实现类ArrayList完成。*/ public class ListPractice01 {public static void main(String[] args) {//添加10个以上的元素(比如String"hello")//使用List的实现类ArrayList完成。ArrayList arrayList = new ArrayList();for (int i = 0; i < 10; i++) {arrayList.add("nice0"+i);}System.out.println(arrayList);//在2号位插入一个元素"马妞笔教育”arrayList.add(2,"马妞笔教育");System.out.println(arrayList);//获得第5个元素System.out.println(arrayList.get(4));//删除第6个元素arrayList.remove(5);System.out.println(arrayList);//修改第7个元素arrayList.set(6,"修改第7个元素");System.out.println(arrayList);//再使用选代器遍历集合System.out.println("-----最终遍历-----");for (Object next : arrayList) {System.out.println(next);}} }
-List接口和常用方法
-
List接口基本介绍
List接口是Collection接口的子接口Listjava
- List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
- List集合中的每个元素都有其对应的顺序索引,即支持索引。
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
- JDKAPI中List接口的实现类有:ArrayList、LinkedList、Vector、Stack、CopyOnWriteArrayList、RoleList、RoleUnresolvedList、AttributeList
-
List接口的常用方法
List集合里添加了一些根据索引来操作集合元素的方法
- void add(int index,Object ele):在index位置插入ele元素
- boolean addAll(int index,Collection eles):从index位置开始将eles中的所有元素添加进来
- Object get(int index):获取指定index位置的元素
- int indexOf(Objectobj):返回obj在集合中首次出现的位置
- int lastlndexOf(Objectobj):返回obj在当前集合中末次出现的位置
- Object remove(int index):移除指定index位置的元素,并返回此元素
- Object set(int index,Objectele):设置指定index位置的元素为ele,相当于是替换.
- List subList(int fromIndex, int tolndex):返回从fromIndex到tolndex位置的子集合
-
List接口练习
package com.xijie.list_;import java.util.ArrayList;/*** 添加10个以上的元素(比如String"hello")* 在2号位插入一个元素"马妞笔教育”* 获得第5个元素* 删除第6个元素* 修改第7个元素* 再使用选代器遍历集合* 要求:使用List的实现类ArrayList完成。*/ public class ListPractice01 {public static void main(String[] args) {//添加10个以上的元素(比如String"hello")//使用List的实现类ArrayList完成。ArrayList arrayList = new ArrayList();arrayList.add("nice01");arrayList.add("nice02");arrayList.add("nice03");arrayList.add("nice04");arrayList.add("nice05");arrayList.add("nice06");arrayList.add("nice07");arrayList.add("nice08");arrayList.add("nice09");arrayList.add("nice10");System.out.println(arrayList);//在2号位插入一个元素"马妞笔教育”arrayList.add(2,"马妞笔教育");System.out.println(arrayList);//获得第5个元素System.out.println(arrayList.get(4));//删除第6个元素arrayList.remove(5);System.out.println(arrayList);//修改第7个元素arrayList.set(6,"修改第7个元素");System.out.println(arrayList);//再使用选代器遍历集合System.out.println("-----最终遍历-----");for (Object next : arrayList) {System.out.println(next);}} }
-
List的三种遍历方式[ArrayListLinkedList,Vector]
-
方式一:使用iterator
Iterator iter = col.iterator(); while(iter.hasNext()) Object o = iter.next();
-
方式二:使用增强for
for(Object o:col){}
-
方式三:使用普通for
for(inti=0;i<list.sizeO;i++){Object object =list.get(i);System.out.println(object); }
说明:使用LinkedList完成使用方式和ArrayList一样
-
练习
package com.xijie.list_;import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Vector;/*** 使用List的实现类添加三本图书,并遍历,打印如下效果* 名称:XX 价格:xX 作者:XX* 名称:xX 价格:xX 作者:xX* 名称:XX 价格:XX 作者:xX** 要求* 1)按价格排序,从低到高(使用冒泡法)* 2)要求使用ArrayList、LinkedList和Vector三种集合实现*/ public class ListPractice02 {@SuppressWarnings({"all"})public static void main(String[] args) {//ArrayListSystem.out.println("-----ArrayList实现------");ArrayList arrayList = new ArrayList();arrayList.add(new Book("西游记",58.98,"刘德华"));arrayList.add(new Book("史记",48.98,"刘小花"));arrayList.add(new Book("资治通鉴",68.98,"刘大华"));sort(arrayList);for(Object o : arrayList) {System.out.println(o);}//LinkedListSystem.out.println("-----LinkedList实现------");LinkedList linkedList = new LinkedList();linkedList.add(new Book("西游记",58.98,"刘德华"));linkedList.add(new Book("史记",48.98,"刘小花"));linkedList.add(new Book("资治通鉴",68.98,"刘大华"));sort(linkedList);for(Object o : linkedList) {System.out.println(o);}//VectorSystem.out.println("-----Vector实现------");Vector vector = new Vector();vector.add(new Book("西游记",58.98,"刘德华"));vector.add(new Book("史记",48.98,"刘小花"));vector.add(new Book("资治通鉴",68.98,"刘大华"));sort(vector);for(Object o : vector) {System.out.println(o);}}//按价格排序,从低到高(使用冒泡法)public static void sort(List list) {for (int i = 0; i < list.size(); i++) {for(int j = i; j < list.size()-1; j++) {Book book1 = (Book) list.get(j);Book book2 = (Book) list.get(j+1);if(book1.getPrice() > book2.getPrice()) {Book temp = book1;list.set(j, book2);list.set(j+1, temp);}}}} }class Book{String name;double price;String author;public Book(String name, double price, String author) {this.name = name;this.price = price;this.author = author;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getPrice() {return price;}public void setPrice(double price) {this.price = price;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}//打印如下效果:名称:XX 价格:xX 作者:XX@Overridepublic String toString() {return String.format("名称:%s\t价格:%s\t作者:%s\t", name, price, author);} }
-
-ArrayList
- ArrayList的注意事项
- permits all elements, including null,ArrayList 可以加入null,并且多个
- ArrayList是由数组来实现数据存储的
- ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高)看源码.
- 在多线程情况下,不建议使用ArrayList
- ArrayList的底层操作机制源码分析(重点,难点.)
- ArrayList中维护了一个Object类型的数组elementData.[debug看源码]
transient Object[] elementData;(transient表示不会序列化) - 当创建对象时,如果使用的是无参构造器,则初始elementData容量为0(jdk7是10)
- 当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到合适位置
- 如果使用的是无参构造器,如果第一次添加,需要扩容的话,则扩容elementData为10,如果需要再次扩容的话,则扩容elementData为1.5倍。
- 如果使用的是指定容量capacity的构造器,则初始elementData容量为capacity
- 如果使用的是指定容量capacity的构造器,如果需要扩容,则直接扩容elementData为1.5倍。
- ArrayList中维护了一个Object类型的数组elementData.[debug看源码]
-Vector
-
Vector的基本介绍
-
Vector类的定义说明
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
-
Vector底层也是一个对象数组,protectedObject[]elementData;
-
Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
public synchronized E get(int index) ( if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index);
-
在开发中,需要线程同步安全时,考虑使用Vector
-
-
Vector与ArrayList的比较
底层结构 版本 线程安全与效率 扩容倍数 ArrayList 可变数组 jdk1.2 不安全,效率高 如果有参构造1.5倍
如果是无参
1.第一次10
2.从第二次开始安1.5扩Vector 可变数组 jdk1.0 安全,效率不高 如果是无参,默认10,
满后,就按2倍扩容
如果指定大小,则每次直接按2倍扩
-LinkedList底层结构
-
LinkedList全面说明
- LinkedList实现了双向链表和双端队列特点
- 可以添加任意元素(元素可以重复),包括null
- 线程不安全,没有实现同步
-
LinkedList的底层操作机制
- LinkedList底层维护了一个双向链表
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
- 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
- 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
-
ArrayList和LinkedList的比较
底层结构 增删的效率 改查的效率 ArrayList 可变数组 较低,数组扩容 较高 LinkedList 双向链表 较高,通过链表追加 较低 如何选择ArrayList和LinkedList:
- 如果我们改查的操作多,选择ArrayList
- 如果我们增删的操作多,选择LinkedList
- 一般来说,在程序中,80%-90%都是查询,因此大部分情况下会选择ArrayList
- 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList.
-Set接口和常用方法
-
Set接口基本介绍
-
无序(添加和取出的顺序不一致,但是取出顺序固定),没有索引
-
不允许重复元素,所以最多包含一个null
-
JDKAPI中Set接口的实现类有:
java.util 接口 Set<E> 类型参数: -此set所维护元素的类型 所有超级接口: Collection<E>, Iterable<E> 所有已知子接口: NavigableSet<E>, SortedSetE> 所有已知实现类: AbstractSet, ConcurrentSkipListSet, CopyOnriteArraySet, EnumSet,HashSet, JobStateReasons, LinkedHashSet, TreeSet
-
-
Set接口的常用方法
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样。
-
Set接口的遍历方式
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口。
- 可以使用迭代器
- 增强for
- 不能使用索引的方式来获取.
-
Set接口常用方法举例
Set set = new HashSet(); set.add("john"); set.add("lucy"): set.add("john"); set.add("jack"); set.add(null); set.add(null); Iterator iterator = set.iterator(); while (iterator.hasNextO) { Object object = iterator.next(); System.out.printIn(object)> System.out.printIn(); for (Object object : set) { System.out.println("obj=" + object); }
-Set接口实现类-HashSet
-
HashSet的全面说明
-
HashSet实现了Set接口
-
HashSet实际上是HashMap
public HashSet(){map = new HashMap<>(); }
-
可以存放null值,但是只能有一个null
-
HashSet不保证元素是有序的,取决于hash后,再确定索引的结果(不保证存放元素的顺序和取出顺序一致)
-
不能有重复元素/对象.在前面Set接口使用已经讲过
-
-
HashSet底层机制说明
分析:HashSet底层是HashMap,HashMap的底层是(数组+链表+红黑树)
简单的数组+链表结构说明:
- 数组中存储Node节点
- Node节点不仅存储数据,还通过Node类型的成员变量存储下一个Node节点的地址
- 数组中的单个元素只存放一个Node,但是该Node可以通过其成员变量后接多个Node
分析HashSet的添加元素底层是如何实现(hash()+equals())
- HashSet底层是HashMap
- 添加一个元素时,先得到hash值-会转成->索引值
- 找到存储数据表table,看这个索引位置是否已经存放的有元素
- 如果没有,直接加入
- 如果有,调用equals(该方法可由类重写)比较,如果相同,就放弃添加,如果不相同,则添加到最后
- 在Java8中,如果一条链表的元素个数超过TREEIFYTHRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(X认64),就会进行树化(红黑树)
分析HashSet的扩容和转成红黑树机制
- HashSet底层是HashMap,第一次添加时,table数组扩容到16,临界值(threshold)是16*加载因子,(loadFactor)是0.75 = 12
- 如果table数组使用到了临界值12,就会扩容到162=32,新的临界值就是32*0.75=24,依次类推
- 在Java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=
MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
-Set接口实现类=LinkedHashSet
-
LinkedHashSet的全面说明
- LinkedHashSet是HashSet 的子类
- LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+双向链表
- LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。
- LinkedHashset 不允许添重复元素
-
LinkedHashSet底层机制说明
-
在LinkedHastSet中维护了一个hash表和双向链表(LinkedHashSet有 head 和 tail)
-
每一个节点有pre和next属性,这样可以形成双向链表
-
在添加一个元素时,先求hash值,在求索引.,确定该元素在hashtable的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加[原则和hashset一样])
tail.next = newElement //简单指定 newElement.pre = tail tail = newEelment;
-
这样的话,我们遍历LinkedHashSet也能确保插入顺序和遍历顺序一致
-
-Map接口和常用方法
-
Map接口实现类的特点【很实用】
注意:这里讲的是JDK8的Map接口特点- Map与Collection井列存在。用于保存具有映射关系的数据:Key-Value
- Map 中的 key 和 value 可以是任何引用类型的数据,会封装到HashMap$Node对象中
- Map 中的 key 不允许重复,原因和Hashset 一样,前而分析过源码,
- Map 中的 value 可以重复
- Map 的key 可以为 null, value 也可以为null ,注意 key 为nul, 只能有一个value 为null,可以多个.
- 常用String类作为Map的 key
- key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value
- Map存放数据的key-value示意图, 一对 k-v 是放在一个Node中的,有因为Node 实现了 Entry 接口,有些书上也说 一对k-v就是一个Entry
-
Map接口常用方法
- put:添加
- get:根据键获取值
- size:获取元素个数
- isEmpty:判断个数是否为0
- clear:清除
- containsKey:查找键是否存在
-
Map接口常用遍历方式
- 先取出所有key,再通过for或iterator遍历value
- 先取出所有value,再通过for或iterator遍历value值
- 先获取EntrySet,再通过for或iterator遍历其中的entry获取key和value
-Map接口实现类-HashMap
-
HashMap小结
- Map接口的常用实现类:HashMap、Hashtable和Properties。
- HashMap是 Map 接口使用频率最高的实现类。
- HashMap 是以 key-val 对的方式来存储数据
- key 不能重复,但是是值可以重复,允许使用null键和null值,
- 如果添加相同的key,则会覆盖原来的key-val,等同于修改.(key不会替换,val会替换)
- 与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的.
- HashMap没有实现同步,因此是线程不安全的
-
HashMap底层机制
-
数据结构
(k,v)是一个Node 实现了Map.Entry<K,V>查看 HashMap 的源码可以看到。
jdk7.0的hashmap 底层实现[数组+链表],jdk8.0 底层[数组+ 链表+红黑树]。 -
扩容机制
- HashMap底层维护了Node类型的数组table,默认为null
- 当创建对象时,将加载因子(loadfactor)初始化为0.75.
- 当添加key-val时,通过key的哈希值得到在table的索引。然后判断该索引处是否有元素如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否和准备加入的key相等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相应处理。如果添加时发现容量不够,则需要扩容。
- 第1次添加,则需要扩容table容量为16,临界值(threshold)为12.
- 以后再扩容,则需要扩容table容量为原来的2倍,临界值为原来的2倍,即24,依次类推
- 在Java8中,如果一条链表的元素个数超过 TREEIFY THRESHOLD(默认是8),并且
table的大小>= MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树)
-
-Map接口实现类-HashTable
-
HashTable的基本介绍
- 存放的元素是键值对: 即 K-V
- hashtable的键和值都不能为null
- hashTable 使用方法基本上和HashMap一样
- hashTable 是线程安全的,hashMap 是线程不安全的
- 简单看下底层结构
-
简单说明一下Hashtable的底层
- 底层有数组 Hashtable$Entry[] 初始化大小为 11
- 临界值 threshold 8=11 *0.752。
- 扩容:按照自己的扩容机制来进行即可
- 执行 方法 addEntry(hashr key,value,index);添加K-V 封装到Entry
- 当 if(count >= threshold)满足时,就进行扩容
- 按照 int newcapacity=(oldCapacity<<1)+ 1;的大小扩容
-
Hashtable 和HashMap对比
版本 线程安全 效率 允许null作为键值 HashMap 1.2 不安全 高 可以 Hashtable 1.0 安全 较低 不可以
-Map接口实现类-Properties
- 基本介绍
- Properties类继承自Hashtable类并且实现了Map接口,也是使用一种键值对的形式来保存数据。
- 他的使用特点和Hashtable类似
- Properties 还可以用于从 xxx.properties 文件中,加载数据到Properties类对象并进行读取和修改
- 说明: 工作后 xxx.properties 文件通常作为配置文件,这个知识点在IO流举例,有兴趣可先看文章
-开发中如何选择集合实现类
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
-
先判断存储的类型(一组对象[单列]或一组键值对[双列])
-
一组对象[单列]:Collection接口
允许重复:List
增删多:LinkedList[底层维护了一个双向链表
改查多:ArrayList[底层维护 Object类型的可变数组]
不允许重复:Set
无序:HashSet[底层是HashMap ,维护了一个哈希表 即(数组+链表+红黑树)]
排序:TreeSet
插入和取出顺序一致:LinkedHashSet,维护数组+双向链表
-
一组键值对[双列]:Map
- 键无序:HashMap[底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]
- 键排序:TreeMap
- 键插入和取出顺序一致:LinkedHashMap
- 读取文件 Properties
-Set接口实现类-TreeSet
1. 当我们使用无参构造器,创建TreeSet时,仍然是无序的
2. 希望添加的元素,按照字符串大小来排序
3. 使用TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类)并指定排序规则
-Map接口实现类-TreeMap
与TreeSet类似
-Collection工具类
- 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处元素进行交换
- 查找、替换
- 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中
- boolean replaceAll(List list, Object oldVal, Object newVal): 使用新值替换 List 对象的所有旧值