前言
设计集合的作者真的是太伟大了,就拿一个简简单单的add()方法,背后的动作在深入了解之后你会强烈感受到了人类的智慧
一.集合VS数组
数组完成定义并启动后,类型确定,长度固定。适合元素的个数和类型确定的业务情景,不适合进行CRUD的操作,就像一个铁盒子放的东西数量是固定的
集合的大小不固定,启动后可以动态改变,因此适合做数据个数不确定且需要增删元素的场景,集合就像一个气球,想变大就吹气!
**
二.集合体系特点
**Collection单列集合:每个元素(数据)只包含一个值
==注==:set接口的实现子类也有存在两个值的(键值对[K-V]只不过其中的V是一个静态常量PRESENT)实际起作用的还是对象K(key)所以就把他也算作单列了
Map双列集合:每个元素包含两个值(键值对k-v)**
三.Collection单列集合
Collection这个接口有很多实现类,每一个类也包含了很多内部方法,主流的是List、Set这两个系列
以下是几个常用的实现类
性能对比(🏁)
**ArrayList:数据结构是数组,查询快,增删慢,线程不安全,效率高,存取顺序一致可重复
LinkedList:数据结构是链表,查询慢,增删快,线程不安全,效率高,存取顺序一致可重复
Vector(很少用):数据结构是数组,查询快,增删慢,线程安全,效率低,存取顺序一致可重复
HashSet:数据结构是哈希表,查询快,增删慢,线程不安全,效率高,存取顺序不一致不可重复
LinkedHashSet:数据结构是链表+哈希表,查询快,增删慢,线程不安全,效率高,存取顺序一致不可重复
TreeSet:数据结构是二叉树,查询快,增删慢,线程不安全,效率高,存取顺序不一致不可重复**
1.List系列常用API
List集合类中元素添加顺序和取出顺序一致、且可重复,List集合中的每个元素都有其对应的顺序索引
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
以ArrayList为例演示一下List接口实现类的常用方法
**1.add(E e):在集合末尾新增一个元素
2.add(int index, E element):在指定位置添加元素
3.get(int index):获取指定位置的元素
4.remove(int index) :删除指定位置的元素
5.remove(Object o): 删除指定元素
6.indexOf(Object o) :查询指定元素的位置 lastIndexOf也一样,只是从尾部开始遍历
7.set(int index, E element): 设置指定位置的元素值
8.retainAll(Collection<?> c) :求两个集合的交集
9.subList(int fromlndex, int tolndex) :获取【x,y)区间元素
10.set(int index, Object ele) :设置指定位置元素
11.indexOf(Object obj):返回元素在集合中首次出现的位置
12.lastIndexOf(Object obj):返回元素在当前集合中末次出现的位置**
........
ArrayList col = new ArrayList();
col.add("懒羊羊"); //添加单个元素
col.add(100); //col.add(new Integer(100))
col.remove("懒羊羊"); //删除指定元素
col.remove(0); //删除第一个元素(索引)
System.out.println(col.size()); //获取元素个数
System.out.println(col.isEmpty()); //看是否为空
col.clear();//清空
ArrayList c1 = new ArrayList();
c1.add("AA");
col.addAll(c1); //把集合c1存进集合col
ArrayList c2 = new ArrayList();
c2.add("CC");
c2.add("AA~");
col.containsAll(c2); //查找col里是否有c2
col.removeAll(c2); //删除多个元素 删除col中的c2集合
col returnlist=col.subList(0,2); //获取区间元素
col.set(1, "喜羊羊");//设置指定位置元素
col.indexOf("懒羊羊");//返回懒羊羊在集合中首次出现的位置
col.lastIndexOf("懒羊羊");//返回懒羊羊在当前集合中末次出现的位置
==注==:每一个接口实现类的方法非常多,感兴趣可以自己去看看
2.Set系列常用API
和List系列集合一样,set系列集合也是Collection接口的实现类,所以用法和上述差不太多
==注==:set接口的实现类的对象(Set接口对象),不能存放重复的元素,可以添加一个null,set接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
内部方法大部分都一样,只不过不能使用索引来操作集合元素,因为set接口对象存放数据无序
HashSet hs = new HashSet();
hs.add(null);
hs.add("懒羊羊");//添加单个元素
hs.remove("懒羊羊"); //删除指定元素
hs.remove(0); //删除第一个元素
System.out.println(hs.size()); //获取元素个数
System.out.println(hs.isEmpty()); //看是否为空
hs.clear();//清空
HashSet c1 = new HashSet();
c1.add("AA");
hs.addAll(c1); //把集合c1存进集合hs
HashSet c2 = new HashSet();
c2.add("CC");
c2.add("AA~");
hs.containsAll(c2); //查找hs里是否有c2
hs.removeAll(c2); //删除多个元素 删除hs中的c2集合
四.Map双列集合
Map与Collection并列存在,用于保存具有映射关系的数据:Key-Value,Map接口也有很多实现类,HashMap较为典型,下图是一部分实现类
HashMap:数据结构是哈希表,查询快,线程不安全,无序不重复
LinkedHashMap:数据结构是哈希表+链表,查询快,线程不安全,有序不重复
1.Map系列常用API
Map 中的 key和 value可以是==任何引用类型的数据==,会封装到HashMap$Node对象中,Map中的key不允许重复,value可以重复
Map的key可以为null, value也可以为null,注意key为null, 只能有一个,value为null可以有多个一般用String类作为Map的key
注:key和 value 之间存在==单向一对一==关系,即通过指定的key总能找到对应的value
**1.put(key,value):通过k-v添加元素
2.replace(3, "小灰灰"):在指定元素key的数据替换为新的value
3.clear():清空元素
4.isEmpty():判断是否为空返回boolean类型
5.put(new Object(),"灰太狼"):k-v中的K可以存放对象
6.get(key):用于获取指定K的元素
7.remove(key):删除指定元素**
Map map = new HashMap();
map.put( "1","懒羊羊");//通过k-v添加元素
map.put( "2","慢羊羊");//k-v
map.put("1","沸羊羊");//当有相同的k,就等价于替换
map.put( "3","美羊羊");//k-v
map.put(null, null); //可添加null
map.put(null,"喜羊羊");//通过相同的null等价替换
map.replace(3, "小灰灰");//在指定元素key的数据替换为新的value
map.get(3);//获取k为3的元素
map.size();//获取集合大小
map.clear();//清空元素
map.isEmpty(); //判断是否为空返回boolean类型
map.put(new Object(),"灰太狼"); //k-v
五.集合的遍历方式
集合的遍历就是一个一个的把容器中的元素访问一遍
1.迭代器遍历(🏁)
迭代器在Java中的代表是Iterator,有时又称光标(cursor)是程序设计的软件设计模式,迭代器是集合的专用遍历方式
1.首先要获得迭代器:
Collection col = new ArrayList();
Iterator m= col.iterator(); //获得集合对象col的迭代器 m
2.其次遍历集合:
while (m.hasNext()) {//使用while循环遍历 判断是否还有数据
Object obj =m.next(); //动态绑定 取决于运行类型(什么样的对象)
System.out.println(obj);//打印集合中的元素
}
完成迭代器遍历集合
==注==:如果要在当前基础上再次遍历还需要重新获取一下迭代器
2.增强for循环遍历
增强for循环遍历的本质还是利用迭代器
Collection col = new ArrayList();
for (Object o : col) {
System.out.println(o);
}
3.Lambda表达式遍历
从JDK8开始产生的新技术Lambda表达式遍历,是一种==更简单,更直接==的遍历集合的方式
Collection col = new ArrayList();
col.forEach(s ->{
System.out.println(s);
});
4.Map遍历特例
1.遍历Map时,可以通过Key来遍历,首先要做的是取出所有的key,然后通过key得到value
具体操作:Collection keys = map.keySet();
然后通过迭代器,增强for循环进行遍历,因为存在.get(key)方法,所以遍历过程中可以通过此方法得到集合中的所有元素
可以使用Collection的遍历方法本质也==是多态的体现==
2.第二种方式通过values遍历
具体操作:Collection values = map.values();
同样也可以使用迭代器,增强for循环
注:遍历集合取出的是value
本质也是values()方法实现了Set接口,Set接口继承了Collection接口(==体现多态性==)
3.第三种则是通过EntrySet获取k-v的方式遍历
具体操作:
Collection es = map.entrySet();
for (object entry : es) {
Map.Entry m = (Map.Entry) entry;//将entry转型成成Map.Entry才能使用下面两个方法
system.out.println(m.getKey() + "" + m.getValue());
}
也可以选择迭代器遍历
六.集合存储对象过程
1.ArrayList集合底层原理(🏳️🌈)
ArrayList底层是基于数组实现的:
根据索引定位元素快,增删需要做元素的移位操作。第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。
通过ArrayList的内部方法.add()来解读源码:
1.当创建ArrayList对象时,如果使用的是无参构造器,首先创建空的elementData数组
2.执行add()向空数组扩容
这个方法里包含了很多动作它先通过ensureCapacityInternal()判断是否要扩容
再通过calculateCapacity()判断elementData是否为空数组,若是则返回10,并将空数组扩容至为空间为103.当执行的add操作大于当前容量(10)则与当前空间大小比较并执行grow()方法进行扩容
4.前面我们知道初始elementData容量为0,第1次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍,注意没有拷贝数祖前里面的元素都是null
2.LinkedList集合底层原理(🏳️🌈)
原码解读:
1.首先也是通过构造器创建一个大小为0的链表,并且头尾都指向null
2.当执行add()添加操作时调用linkLast()方法
使用linkLast()将新的节点加入到双向链表尾部,也就是last指向“1”这个节点
同理也使用Linkedfirst()让first指向“1”这个节点
最后完成了add(1)的操作 将1添加到双向链表中
感想
我真是好后悔没把数据结构学到位
写到这里:==懒羊羊寿命-2年==