集合
回顾:
数据结构: 计算机存储数据的一种形式
数组,链表,栈,堆 队列, 树,图
/** * */ package com.qfedu.Day16.Lianbiao; public class MyLinkedList { //存在节点 连接链表使用的(链表的组成) private Node first; // 第一个节点 private Node last; // 最后一个节点 private int size; //节点的数量 //对链表中的节点进行描述 class Node{ //定义上一个节点 和 下一个节点 Node prev; Node next; Object ele; //当前节点中存储的数据 //构造方法 方便给基点中的数据进行赋值 public Node(Object ele) { this.ele = ele; } } //第一个方法 向头节点添加元素 public void addFirst(Object ele) { //1. 创建节点对象 Node node = new Node(ele); if(size == 0) { this.first = node; this.last = node; }else { //把之前的第一个基点作为新增基点的下一个节点 node.next = this.first; //把新增的基点作为原有节点的上一个基点 this.first.prev = node; //重置开始节点 this.first = node; } size ++; } //第二个方法 向头节点添加元素 public void addLast(Object ele) { //1. 创建节点对象 Node node = new Node(ele); if(size == 0 ) { this.first = node; this.last = node; }else { //把新增的节点作为原有节点的下一个节点 this.last.next = node; //把之前最后一个节点作为当前节点的上一个节点 node.prev = this.last; //重置最后一个基点 this.last = node; } size++; } //删除元素 public void remove(Object ele) { //先获取第一个节点 Node current = this.first; //寻找节点 for(int i = 0; i < size; i++) { if(!current.ele.equals(ele)){ if(current.next == null) { return; } current = current.next; } } //删除 if(current == first) { this.first = current.next; this.first.prev = null; }else if(current == last) { this.last = current.prev; this.last.next = null; }else { //当前对象的上一个节点对象的下一个节点 得到 当前对象的下一个节点 current.prev.next = current.next; //当前对象的下一个基点对象的上一个节点 的带 当前对象的上一个节点 current.next.prev = current.prev; } size--; } //打印当前链表中存储的元素 @Override public String toString() { if (size == 0) { return "[]"; } StringBuilder sbr = new StringBuilder(); // 获取节点对象 Node node = this.first; sbr.append("["); for (int i = 0; i < size; i++) { sbr.append(node.ele); if (i != size - 1) { sbr.append(","); } else { sbr.append("]"); } node = node.next; // 取自己的下一个节点 } return sbr.toString(); } }
/**
*
*/
package com.qfedu.Day16.Lianbiao;
public class Test {
/**
* @param args
*/
public static void main(String[] args) {
MyLinkedList ml = new MyLinkedList();
ml.addFirst("第一个元素");
ml.addLast("第二个元素");
ml.addFirst("第0个元素");
System.out.println(ml);
}
}
数组--> 实现了自己的ArrayList
Java集合
List集合:有序可以存储重复,并且可以使用下标的形式
ArrayList ---> API
ArrayList分析:
大O表示法:表示时间复杂度,专门用来计算性能相关的
增删改查(CRUD)
1.保存:
若把数据保存在数组的最后一位,最少要操作一次
若把数组把保存在数组中第一个位置,如果存在多个元素,此时需要操作N次
平均(N+1)/2 若需要扩容,性能更慢
2.删除操作:
如果删除 最后一个元素 ,最少操作一次
如果删除第一个元素,操作N次
平均(N+1)/2
3.修改 操作1次 (下标)
3.查询:
如果使用下标查询元素 操作1次
如果使用元素查询:此时使用线性搜索 N次
平均(N+1)/2
ps: 使用二分查找数组中的元素
1.排序(从小到大)
2.开始的位置和一个结束的位置(数组的最后一位)求和/2 = 中间值
3.使用要查找的数值 和 中间值进行比较
4.若当前数值小于中间值 --> 左半区寻找 重现计算中间值 并且 最后的位置需要移动 = 中间值 -1
5.若当前数值大于中间值 --> 右半区寻找 重新计算中间值 并且 开始值的位置需要移动 = 中间值 +1
6.找到就是返回下标(中间值)
7.交叉 开始值 > 结束值 --> 右半区 --> 开始值+1 > 结束值
--> 左半区 --> 开始值 > 结束值 -1
找不到 返回-1
结论:基于数组结构的ArrayList做查询和修改是非常快,但是做保存和删除比较慢了(数组扩容和减容)
ps:
java7之前 使用new ArrayList创建一个集合对象,一个元素都不存的情况下,对空间需要开始10个空间
java7之后 对创建没有元素的ArrayList做一个优化
Object[] arraylist = new Object[]{}; == ArrayList al = new ArrayList();
当第一次使用add方式,才会重新去初始化这个数组
如果就像别保证保存和删除的性能,怎么办?
数据结构:链表
实现双向链表 --> 内部类
成员内部类 静态内部类 局部内部类(打死都不使用 --> 定义在方法体中) 匿名内部类
public class {
public void show(){
//java7最后 默认添加final --> 只要局部内部类访访问了当前变量
//方法体重当前变量需要被局部内部类所使用 --> final
int a = 10; //final
class A{
System.out.println(a);
}
}
}
成员内部类和静态内部类的最大区别:
静态内部类使用static修饰 --> 只有内部类才可以使用static修饰 外部类补允许
成员内部类是不能定义static修饰的变量和方法
看Lianbao包
若每次存储数据的话都需要实现双向链表过于繁琐
所以Java集合框架中就提供了一个融合了双向链表和队列的集合
LinkedList集合:
/** * */ package com.qfedu.Day16.ForList; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Vector; public class ForListDemo { public static void main(String[] args) { List list = new ArrayList(); list.add("A"); list.add("B"); list.add("C"); list.add("D"); list.add("E"); System.out.println("普通for循环遍历打印的结果:"); //1.普通for循环 for(int i = 0;i<list.size();i++) { System.out.println(list.get(i)); } System.out.println("增强for循环遍历打印的结果:"); //2.增强for循环 //ps:对于集合来说就是一个迭代器 /* * 通过反编译.class文件的到的结果 * Object obj; * for (Iterator iterator = list.iterator(); iterator.hasNext();){ obj = iterator.next(); System.out.println(obj) } */ for(Object obj : list) { System.out.println(obj); } System.out.println("迭代器遍历打印的结果:"); //3.迭代器 //3.1需要先获取迭代器对象 Iterator it = list.iterator(); //3.2通过迭代器对象的hasNext方法获取是否还有下一个元素 while(it.hasNext()) { System.out.println(it.next()); } System.out.println("for循环迭代器版本"); //for循环版本: for(Iterator itr = list.iterator();itr.hasNext();) { System.out.println(itr.next()); } //ps:以上三种的效果都是一样,但实际迭代器是不同 //4.枚举迭代器: //这个只是用来掩饰无需记住,不会再使用这种方式 Vector v = new Vector(); v.addAll(list); //4.1获取枚举迭代器的对象 Enumeration en = v.elements(); //判断枚举迭代器中是否存在下一个元素 while(en.hasMoreElements()) { //获取迭代器中的对象 System.out.println(en.nextElement()); } System.out.println("-----------------------------以下是listIterator的结果---------------------------"); //这个是listIterator的对象 ListIterator lit = list.listIterator(); while(lit.hasNext()) { System.out.println(lit.next()); } while(lit.hasPrevious()) {//上一个元素 指针必须在后面 System.out.println(lit.previous()); } } } --------------------------------------------------------------- /** * */ package com.qfedu.Day16.LinkedList; import java.util.Arrays; import java.util.LinkedList; public class LinkedListDemo { @SuppressWarnings("rawtypes") public static void main(String[] args) { //创建LinkedList集合对象 LinkedList list = new LinkedList(); //将集合中的元素存到当前集合中 //将参数中集合对象的元素存到新的集合中 LinkedList list1 = new LinkedList(list); //常用方法: //向集合中存储元素-->(包括任何数据类型) list.add("第一个元素"); LinkedList list2 = new LinkedList(); list2.add("1"); list2.add("2"); list2.add("3"); //这样添加对象是将集合添加到了当前集合对象中 //list.add(list2); //这样添加对象是将当前参数集合中的元素存到了集合对象 list.addAll(list2); /* * add(int index, E element) 在此列表中指定的位置插入指定的元素。 addAll(int index, Collection<? extends E> c) 将指定 collection 中的所有元素从指定位置开始插入此列表。 支持下标 -->0开始 size()-1 */ System.out.println(list); //将元素添加到集合的开头 list.addFirst("第0个元素"); System.out.println(list); //将元素添加到集合的末尾 list.addLast("最后一个元素"); System.out.println(list); //clear() 清空集合中的元素 --> 集合依旧存在 //判断集合中是否存在指定元素 存在即true 即false list.add("1"); System.out.println(list); boolean result = list.contains("1"); System.out.println(result); //获取集合中的第一个元素但是不删除该元素 Object obj = list.element(); System.out.println(obj); System.out.println(list); //get(int index) 返回此列表中指定位置处的元素 下标 //获取第一个或最后一个元素 System.out.println(list.getFirst()); System.out.println(list.getLast()); /* int indexOf(Object o) 返回此列表中首次出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。 int lastIndexOf(Object o) 返回此列表中最后出现的指定元素的索引,如果此列表中不包含该元素,则返回 -1。 返回值的是第一次出现的下标,找不到返回-1 */ //将指定元素添加到末尾 boolean res = list.offer("ppp");//添加成功返回true System.out.println(list); System.out.println(res); /* * boolean offerFirst(E e) 在此列表的开头插入指定的元素。 boolean offerLast(E e) 在此列表末尾插入指定的元素。 添加成功是不返回true */ //获取集合中的第一个元素但是不删除 Object obj2 = list.peek(); System.out.println(obj2); System.out.println(list); /* * E peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空(集合中没有元素),则返回 null。 E peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空(集合中没有元素),则返回 null。 */ //获取集合中第一个元素并删除 Object obj3 = list.poll(); System.out.println(obj3); System.out.println(list); /* E pollFirst() 获取并移除此列表的第一个元素;如果此列表为空(集合中没有元素),则返回 null。 E pollLast() 获取并移除此列表的最后一个元素;如果此列表为空(集合中没有元素),则返回 null。 */ //获取集合中第一个元素并删除 Object obj4 = list.pop(); System.out.println(obj4); System.out.println(list); //向集合添加元素 list.push("0"); //--> addfist System.out.println(list); //集合中删除元素 //1.删除集合中的第一个元素并获取 Object obj5 = list.remove(); System.out.println(obj5); System.out.println(list); //2.根据下标删除指定元素并获取 Object obj6 = list.remove(2); System.out.println(obj6); System.out.println(list); //3.删除集合中的指定元素 //若删除成功返回 true 失败 返回false boolean res1 = list.remove("1"); System.out.println(list); System.out.println(res1); /* * E removeFirst() 移除并返回此列表的第一个元素。 E removeLast() 移除并返回此列表的最后一个元素。 会得到删除的元素 * */ //传入下标替换指定元素,并获取原有值 Object obj7 = list.set(0, "yui"); System.out.println(obj7); System.out.println(list); //将LinkedList集合集合对象转换为数组 Object[] objs = list.toArray(); System.out.println(Arrays.toString(objs)); } } --------------------------------------------------------------- /** * */ package com.qfedu.Day16.List; import java.util.ArrayList; import java.util.List; public class ListDemo { @SuppressWarnings("rawtypes") public static void main(String[] args) { List list1 = new ArrayList(); List list2 = new ArrayList(); list1.add(1); list1.add(2); list1.add(3); list1.add(4); list2.add(2); list2.add(3); //判断当前集合中是否存在指定集合中的元素 boolean res = list1.containsAll(list2); System.out.println(res); List list3 = new ArrayList(); //数学: 空集是任何集合的子集 System.out.println(list1.containsAll(list3)); //保留参数集合中的元素,删除其他元素 // list1.retainAll(list2); //删除参数集合中的元素在当前集合 //list1.removeAll(list2); // System.out.println(list1); //截取当前当前 包括开始的位置,不包括结束的位置 List list4 = list1.subList(1, 3); //list4获取的不是新的堆空间 //list4获取的是截取之后的空间 System.out.println(list4); } } ----------------------------------------------------------------- /** * */ package com.qfedu.Day16.Iterator; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class IteratorDemo { public static void main(String[] args) { List list = new ArrayList(); list.add("A"); list.add("B"); list.add("C"); list.add("D"); list.add("E"); //创建迭代器对象 // Iterator it = list.iterator(); // while(it.hasNext()) { // if("B".equals(it.next())) { // //通过集合删除了"B"元素 // //list.remove("B");绝对不要这样操作 // //迭代器对象.remove(); // it.remove(); // } // // } //迭代器bug --> 官方的 快速失败迭代器 //修改不了原码 --> 坚决避免在迭代器中使用集合对象的增删来操作 //迭代器中只要删除集合的倒数的二个元素都不异常 Iterator it = list.iterator(); while(it.hasNext()) { if("D".equals(it.next())) { list.remove("D"); } } for(Object obj : list) { System.out.println(obj); } } }
API
看LinkedList包
LinkedList分析
增加:
双向链表可以直接在第一个或最后一个位置添加节点
如果是中间添加 ,同样的也是只要1次即可
删除:
如果是删除第一个元素直接删除即可
如果是最后一个元素 直接删除即可
若删除中间的元素:
若直接找到节点 就是1次
若是通过下标的方式 (1+N)/2次
查询: (N+1)/2 平均
修改 (N+1)/2 平均
总结:LinkedList增加和删除元素效率高,查询和修改元素效率低
ArrayList和LinkedList场景:
查询和修改某个数据结构:推荐使用ArrayList
增加和删除某个数据结构:推荐使用LinkedList
ps:开发中实际使用次数最多的就是 ArrayList --> 数组
Vector和队列和栈 (了解)
ArrayList类是java集合框架出现之后用来取代Vector类的
二者底层实现原理基本都是基于数组(一模一样)
Vector 所有的方法都使用了线程同步锁synchronized,多线程安全效率低,比较适用于多线程的环境下
ps: 1.5版本一下 swing 图形界面-->当前下的某些方法使用Vector作为参数
ArrayList 所有方法并没有使用线程同步锁,多线程不安全效率高,比较适用于单线程的情况下
Collections.synchronizedList --> 线程安全的集合
Vector的常用方法和ArrayList近乎是一样的
队列是一中线性的类表,特殊之处在于允许在表的前段,进行删除操作,从而可以在表的后端进行插入操作 Queue
先进先出(FIFO)
单向对象(Queue) 双向队列(Deque) --->允许在表的头尾,进行插入和删除工作
栈集合(Stack) --> pop push 它也是一种线性列表(受限制) 后进先出
List集合
通过图的说明ArrayList和LinkedList可以在List中实现了一些通用的方法
List接口(DRY原则)
接口可以看做是一个特殊的抽象父类
接口同样支持多态 --> 接口可以接受一个实现类的引用
面向接口编程
同理为了达到通用性,在设计方法的时,方法的参数需要是一个集合,使用接口类型作为参数的数据类型
这样一来就可以在方法中使用不同的集合了
例如:
public static void show(List list){
}
当调用哪个show方法时,可以传入的参数有哪些 --> ArrayList 或 LinkedList 或 Vector
可以限制当前方法使用的集合的类型
若以后再使用List接口集合是建议:
List list = new 当前实现类即可(); --->对象的向上转型 ---.>list只能调用父类的方法和属性,子类的特有属性和方法不行了
看List包
剩余方法可以参考API
总结:
Vector类打死都不用,就选着ArrayList
无论是增删改查,可以根据不同的场景选着不同的集合
ps:个人意见就可以完全使用ArrayList就可以了
不用担心性能,不用担心多线程下的不安全问题 --> collections类
集合的遍历方式 4种
普通for循环
增强for循环
迭代器 Iterator
标准 直接通过集合对象.iterator() -->获取迭代器对象
boolean hasNext() 判断迭代器中是否还有下一个元素 有返回true 没有返回false
E next() 可以获取当前迭代器中存储的元素对象
增强版 listIterator 直接通过集合对象.listIterator
法摘要
void add(E e) 向迭代器中添加元素
boolean hasNext() 判断迭代器中是否还有下一个元素
E next() 获取当前对象
boolean hasPrevious() 判断迭代器中是否还有上一个元素
E previous() 获取当前对象。
void remove() 删除元素
void set(E e) 修改元素
枚举迭代器(Vector的固有迭代,不用)
并且已经过时,而且被Iteratror所取代
迭代器存在的问题
ConcurrentModificationException并发修改异常
迭代器相当于拷贝了一份原有集合
迭代器和原有集合之前每一个元素存在着一种映射关系
影响到了集合和迭代器堆元素修改时的一些问题
若在迭代其中 使用当前集合对象自身的删除方法,删除集合中的元素,集合中的中元素确实是删减了但是
迭代器中的元素时不变的,所以会出现ConcurrentModificationException
若需要在迭代器中删除元素,需要使用迭代器对象自身的remove,而不是集合中remove
若使用迭代器中的remove修改元素,集合同步会被修改
ps:
当前迭代器迭代器集合的时候会创建一张单索引的表或数据结构只想当前结合.若通过集合的增删功能是修改集合大小
当前迭代器所创建的单索引表或数据结构不会发生改变,在次当执行hasNext方式,迭代器会判断单索引表或数据的大小
是否等于集合的大小,若不等于就抛出异常ConcurrentModificationException并发修改异常
总结:
若使用迭代器遍历数组,绝对不能砸迭代器中使用集合自身的删除增加方法,需要使用迭代器对象本身的增加删除方法
快速失败迭代器问题:
只要在得带器中使用集合对象删除,集合中的倒数第二个元素,这个时候得带器是不会抛出异常的
若删除其他的其他的就会出现异常
假如集合的大小为5,删除了地3个元素(倒数第2个),因为调用了底层的next所以原码中cursor就会进行++
cursor = cursor + 1 3+1 --> 4
hasNext是size原本的值是5,因为删除了一个元素所以大小变成了4, 所以cursor == size 因为条件 cursor != size;
所以为这个时候返回了false,循环是进入不了的就没办法执行next方法,所以就不会报出异常信息
public boolean hasNext() {
return cursor != size;
}
ps:cursor记录当前迭代器修改次数,用来和集合中的size进行比较