目录
数据结构
集合
List接口
ArrayList和LinkedList的使用场景
Set接口
Map接口
哈希碰撞(哈希冲突)
集合小节
概念:保存数据的一种方式
常见的数据结构
通过数组来保存,基于数组的数据结构(动态数组,长度可变的数组)
基于数组的结构的优缺点
优点:查改快
1)、通过下标查询元素,效率高
2)、通过下标修改元素,效率高
缺点:增删插慢
1)、在需要扩容的时候:添加慢,删除慢,插入元素慢
通过一个对象变量来保存数据,基于变量保存数据的结构称为链表结构
优点:增删插快
缺点:查改慢
队列
单向队列:一个是进口,一个是出口,先进先出(FIFO)
双向队列:两端既是进口又是出口
栈结构:只有一个口,既是进口也是出口
先进后出:FILO(后进先出:LIFO)
树形结构:是链表的一种变形结构
概念:基于某种数据结构的容器
集合的体系结构:
Collection接口:所有集合框架的根接口(父接口):定义方法的标准
常用子接口:List Set Queue等
1. 第一个子接口:list 可以存放重复元素,而且有序(有序:存入的顺序和取出来的顺序一致(存取一致)),有序可重复
使用接口的实现类(我们不想要重写接口中的每个方法,所以就用一个抽象类来实现接口,先重写一次,充当过滤器的作用,这样后续就可以选择想要的方法了)
abstract class AbstractList implements List { // 实现list里面的所有的抽象方法 } class ArrayList extends AbstractList implements List { // 继承AbstractList后,想重写哪个就哪个 再实现list接口是为了看上去规范,因为接口的方法已经被AbstractList重写了 }
List接口的实现子类:
ArrayList:基于数组的集合(线程不安全)
LinkedList:基于链表的集合
Vector:基于数组的集合(线程安全)
2. 第二个子接口:set 不能存放相同的元素(去重),无序(存取不一致),无序不可重复
3. 还有其他的子接口,这里是常用的两个
List接口的实现类
一:ArrayList: 基于数组的集合 public class ArrayList
构造方法:
ArrayList( ) 构造一个初始容量为十的空列表。
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) 但是这时候jvm还没有将容量扩容到十,只有使用这个对象的时候才会扩容,这是jvm的一种延迟机制,防止对象没有使用从而导致内存的浪费
ArrayList(int initialCapacity) 构造具有指定初始容量的空列表。
ArrayList list = new ArrayList(20); // 创建ArrayList的对象 但是初始化的长度是20
常用方法:
(1)add(E e) 将指定的元素追加到此列表的末尾。 (2)add(int index, E element) 在此列表中的指定位置插入指定的元素。
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add(true); // (1) System.out.println(list); // 打印[true] list.add(0,'a'); // (2) System.out.println(list); // 打印[a, true] ArrayList list2 = new ArrayList(); // 创建ArrayList的新对象 list2.add(list); // (1) 将对象list添加到了list2中 也就是将集合作为一个值添加到集合 list2.add(1); // 自动将int类型的1自动装箱成integer类型 System.out.println(list2); // 打印[[a, true], 1]
size( ) 返回此列表中的元素数
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add(true); list2.add(1); System.out.println(list.size()); // 打印2
addAll(int index, Collection extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始。
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add(true); // (1) list.add("a"); list2.add(1); ArrayList list2 = new ArrayList(); // 创建ArrayList的新对象 list2.addAll(0,list); // 将list集合里面元素一个一个添加list2里面 System.out.println(list2); // 打印[true, a, 1]
contains(Object o) 判断集合中是否存在该对象,有则返回 true
。
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add(true); // (1) System.out.println(list.contains(true)); // 打印 true
get(int index): 通过指定下标获取对象
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add("a"); list.add(true); // (1) System.out.println(list.get(0)); // 打印 a
indexOf(Object o) : 查询对象在集合中第一次出现位置,找不到则返回-1
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add(true); // (1) list.add("a"); System.out.println(list.indexOf('a')); // 打印 1
lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add(true); // (1) list.add("a"); list.add("a"); System.out.println(list.lastIndexOf('a')); // 打印2
(1) remove(int index) :通过下标删除指定对象 (2) remove(Object o) :删除指定对象 (3) removeAll(Collection> c) 从此列表中删除指定集合中包含的所有元素。
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add(true); // (1) list.add("a"); list.add("a"); list.remove(0); // 删除下标为0的对象 System.out.println(list); // 打印[a, a] list.remove("a"); // 直接删除a这个对象 System.out.println(list); // 打印[a] list.removeAll(list); // 删除这个列表中的所有元素 System.out.println(list); // 打印[ ]
set(int index, E element) 用指定的元素替换此列表中指定位置的元素。
ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) list.add(true); // (1) list.add("a"); list.set(0,"张飞"); // 将下标为0的那个元素,换成"张飞" System.out.println(list); // 打印[张飞, a]
ArrayList集合的遍历:
// 方法1: 循环(for) ArrayList list = new ArrayList(); // 创建ArrayList的对象(基于数组的集合) for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } // 方法2:foreach循环 for (Object o : list) { System.out.println(o); } // 方法3:迭代器 (1)单向迭代器 (2)双向迭代器 // 单向迭代器:只能从第一个元素到最后一个元素 iterator() Iterator it = list.iterator();// 获取遍历list集合的迭代器 while (it.hasNext()) { // 判断后面一个元素是否有值 System.out.println(it.next()); // 有就输出 } // 双向迭代器:可以从后往前迭代 listIterator(int index) ListIterator li = list.listIterator(); // 获取遍历list集合的迭代器 不管是单向迭代器还是双向迭代器指针默认从第一个元素开始 while (li.hasNext()) { // 正向迭代 System.out.println(li.next()); } System.out.println("==========================="); while (li.hasPrevious()) { // 反向迭代 注意:这里由于上面的正向迭代已经执行到了最后,此时指针已经到了最后,所以hasPrevious()里面不用加参数就本身就是向前迭代,但是如果前面没有那个正向迭代,上面创建迭代器的时候就必须要给参数,写需要从哪个位置开始的 如:listIterator(list.size()) System.out.println(li.previous()); }
二:LinkedList: 基于链表的集合 public class LinkedList
构造方法:
LinkedList( ) 构造一个空列表
LinkedList list = new LinkedList(); // 创建LinkedList对象
LinkedList(Collection extends E> c) 构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序
LinkedList linkedList = new LinkedList(); // 创建LinkedList对象 LinkedList linkedList2 = new LinkedList(linkedList); // 将linkedList的所以元素放进linkedList2
常用方法:大多跟ArrayList的方法一样 以下是实现其他接口的方法
LinkedList实现的队列(双向队列) implements Deque
LinkedList栈结构 栈的特征:FILO
1. (1) add(E e):在此双端队列的尾部 (2) addFirst(E e) 插入此双端队列的前面 (3) addLast(E e) 双端队列的末尾指定元素
2. getFirst( ):检索,但不删除,这个deque的第一个元素 getLast( ):检索,但不删除,这个deque的最后一个元素。
LinkedList list = new LinkedList(); // 创建LinkedList对象 // 在头尾操作的方法 list.addFirst("关羽"); // 最前面 list.addLast("刘备"); // 最后面 list.add("张飞"); // 最后面 System.out.println(linkedList); // 打印 [张飞, 关羽, 刘备] System.out.println(linkedList.getLast()); // 刘备 可以获取最后一位的元素 System.out.println(linkedList.getFirst()); // 张飞 可以获取第一位的元素
3. removeFirst( ):检索并删除此deque的第一个元素。 removeLast( ):检索并删除此deque的最后一个元素。
list.removeFirst(); // 删除第一个元素 list.removeLast(); // 删除最后一个元素 System.out.println(list); // 打印[关羽]
4. element( ):检索但不删除该deque的第一个元素
LinkedList list = new LinkedList(); // 创建LinkedList对象 list.addFirst("关羽"); // 最前面 list.addLast("刘备"); // 最后面 System.out.println(list.element()); // 打印关羽
5. offer(E e):将指定的元素插入在该deque的尾部 offerFirst(E e):在此deque的前面插入指定的元素 offerLast(E e) :在此deque的末尾插入指定的元素
LinkedList list = new LinkedList(); // 创建LinkedList对象 list.offer("关羽"); // 最前面 list.offerFirst("刘备"); // 最后面 list.offerLast("张飞"); // 最后面 System.out.println(linkedList); // 打印 [张飞, 关羽, 刘备]
6. peek( ):检索但不删除此deque的第一个元素 跟element( )方法类似
7. poll( ):检索并删除此deque的第一个元素 pollFirst( ):检索并删除此deque的第一个元素 pollLast( ):检索并删除此deque的最后一个元素
LinkedList list = new LinkedList(); // 创建LinkedList对象 list.offer("关羽"); // 最前面 list.offerFirst("刘备"); // 最后面 list.offerLast("张飞"); // 最后面 list.poll(); list.pollFirst(); list.pollLast(); System.out.println(list); // 打印[]
8. push(e):压栈,将对象放入栈内存 pop():出栈,将栈里面对象取出
LinkedList list = new LinkedList(); // push(e):压栈,将对象放入栈内存 list.push("a"); list.push("b"); list.push("c"); System.out.println(list); // 打印[c, b, a] // pop():出栈,将栈里面对象取出 先进后出 System.out.println(list.pop()); // 将 c 先出栈 所以打印c System.out.println(list); // 打印[b, a] 因为c已经出栈
LinkedList集合的遍历:
LinkedList list = new LinkedList(); // 创建LinkedList对象 Iterator it = list.iterator(); // 获取遍历list集合的迭代器 while (it.hasNext()) { // 判断后面一个元素是否有值 System.out.println(it.next()); } System.out.println("======================"); // 分隔一下 ListIterator li = list.listIterator(list.size()); // 反向迭代 注意:这里必须要加上list.size()或者写一个int类型的参数,否则不会打印值,因为现在是基于链表的集合,默认就是从开头开始遍历的 while (li.hasPrevious()) { System.out.println(li.previous()); }
底层结构不同
ArrayList底层是数组:查改性能好,增删插性能差
LinkedList底层是一个双向链表,增删插性能好,查改性能差 ,底层还是一个队列,还是一个栈
Set集合:无序不重复 无序:存取不一致 不重复:元素放入集合之前或做判断
set接口的实现类
一、HashSet
构造方法:
HashSet() 构造一个新的空集合; 背景
HashMap
实例具有默认初始容量(16)和负载因子(0.75)。HashSet(Collection extends E> c) 构造一个包含指定集合中的元素的新集合。
特点:
底层使用哈希表来实现(底层是一个数组,数组里面保存一个单向链表)的集合
不允许元素重复,元素是无序的
去重机制:
将要添加到HashSet集合元素的hashCode值拿到
在集合去查找是否存在这个hashCode值
不存在,说明这个元素在集合中不存在,可以执行添加
存在,就来让这个元素.equals(hashCode值相同元素),
不相等false,说明这个元素在集合中不存在,执行添加,相等true,说明元素在集合中已经存在了,那就不添加
作用:HashSet的作用就是用来去重
public class SetDemo1 { public static void main(String[] args) { // 创建一个HashSet集合 HashSet set = new HashSet(); // 哈希表(散列表) // 常用方法 set.add("abc"); set.add(true); set.add(123); set.add("波多野"); set.add("波多野"); set.add("波多野"); System.out.println(set); // 打印[波多野, abc, 123, true] 无序的 // 遍历 // for (Object o : set) { // System.out.println(o); // } Iterator it = set.iterator(); // 创建一个单向构造器 while (it.hasNext()) { System.out.println(it.next()); // 遍历 } } }
LinkedHashSet 底层哈希表+单向链表(记录顺序) 等于是有序的HashSet
二、TreeSet
构造方法:
TreeSet() 构造一个新的,空的树组,根据其元素的自然排序进行排序。
TreeSet(Comparator super E> comparator) 构造一个新的,空的树集,根据指定的比较器进行排序。
特点:
无序不可重复
底层使用树结构来实现的集合
树型结构是提高查询效率,将树上的元素按大小排序
在使用TreeSet,该集合中只能放同一种类型的元素,第一个添加的元素类型,就是这个集合中能放的类型
去重机制:
通过两个接口来完成,添加元素到集合,这个元素会和集合里面所有的元素依次调用compareTo()进行比较,返回0,就说明该元素在集合已经存在了,就不添加了
元素会根据类里面写compareTo()的代码来进行排序
作用就是对元素进行排序,注意放入TreeSet的元素必须是同一类型,必须要实现Comparable接口
> 比较对象大小有两种方式 1.定制排序 需要实现Comparator接口 2.自然排序 需要实现Comparable接口 // 以下是第一种 // 需要单独写一个MyCompartor()类 Cat c1 = new Cat("咪咪", "公", 2); Cat c2 = new Cat("哆哆", "母", 5); Cat c3 = new Cat("啦啦", "公", 1); // 比较c1、c2和c3的大小,比较对象的大小 TreeSet set = new TreeSet(new MyCompartor()); // Comparator:定制排序 set.add(c1); set.add(c2); set.add(c3); System.out.println(set); // 以下是第二种 // 使用Comparable:比较对象大小的接口 p1.compareTo(p2) // 1、返回正数说明p1对象大于p2对象 2、返回负数说明p1小于p2 3、返回0说明p1等于p2 // 以下是他需要重写的部分 @Override public int compareTo(Object o) { // 对象进行大小比较其实就是对象的属性值进行比较 Pig p = (Pig)o; if(!this.name.equals(p.name)) { // 两个对象的名字不同,就比较name return p.name.hashCode() - this.name.hashCode(); // 三个之一 0 整数 负数 }else if(this.age != p.age) { // 上面的name相同 这里的age不同,就比较age return p.age - this.age; // 这里如果是p对象在this前面就是逆序 反之就是正序 } return 0; // 属性值都相等返回0 } }
概念:保存一对值
Map:映射(键值对,由一个键指向一个值)
例:成都 四川 :这是两个值,成都称为键(key),四川称为是值(value),key value:称为一对值(键值对)
注意:
Map中键不能重复,值可以重复,一个键只能指向一个值
Map是映射的顶级父类,是一个接口
Map的实现类
一、HashMap:就是一个哈希表(散列表)
构造方法:
HashMap()
构造一个空的
HashMap ,默认初始容量(16)和默认负载系数(0.75)。HashMap(Map extends K,? extends V> m) 构造一个新的
HashMap
与指定的相同的映射Map
。
底层:数组,数组中保存了一个单向链表,链表在满足条件下回转为树结构(红黑树)
在数组保存了哪些信息? 键的hashCode、键、值、下一个对象
// 创建HashMap集合对象 HashMap map = new HashMap(); // 创建长度为16的数组,数组的每一个元素有一个空链表,map保存的是一对值 // put(K key, V value) : 向map集合添加一对值 map.put("3838438", "航空飞机"); // 首次存放值的过程 key:"3838438" 1、获取键的hashCode,19(举例) 2、计算这对值放入数组的下标位置:用键的hash值 % 当前数组的长度,19 % 16,计算的结果是3 那么这对值应该放入下标为3的位置 3、判断下标为3的位置上有没有值: 没有:直接将这对值放入这个位置 有:将这个值接在这个位置上存在值的后面 map.put("3838438", "老王"); // 老王会覆盖掉航空飞机 map中键不允许重复 去重的机制:键的hashCode + equals 1、key:"3838438",将键的哈希值获得 2、去这个map集合中查看有没有相同哈希值的键 没有:说明键不重复 计算放入的位置用键的hash值 % 当前数组的长度 有:将这个键.equals(哈希值相同的键),结果是true,说明键已经存在,然后覆盖值,结果false
常用方法:
put(K key, V value) 将指定的值与此映射中的指定键相关联。
HashMap hm = new HashMap(); // 创建一个HashMap的对象 hm.put(1,"张飞"); hm.put(2,"赵云"); System.out.println(hm);// 打印{1=张飞, 2=赵云}
get(Object key) 返回到指定键所映射的值,或 null
如果此映射包含该键的映射。
System.out.println(hm.get(1)); // 根据传入键获取值 // 打印张飞
entrySet() 返回此地图中包含的映射的Set
视图。
// 遍历map集合,Entry:键值对 // 遍历map的第一种方式:同时获取键和值,entrySet() // 1 Set set = hm.entrySet();// 获取所有的键值对 System.out.println(set); // 打印[1=张飞, 2=赵云] while (it.hasNext()) { Object next = it.next(); Map.Entry entry = (Map.Entry) next; Object key = entry.getKey(); // 获取键 Object value = entry.getValue(); // 获取值 System.out.println(key + ":" + value); } // 遍历map的第二种方式:只获取键keySet() // 2 Set set = hm.keySet(); for (Object key : set) { System.out.println(key + ":" + hm.get(key)); } // 遍历map第三种方法:只获取值,values() // 3 Collection values = hm.values(); Iterator it = values.iterator(); while (it.hasNext()) { Object next = it.next(); System.out.println(next); }
子类LinkedHashMap类:链表 + 哈希表
概念:保证键是有序的
二、Hashtable类 implements Map
概念:
1)、底层和HashMap相似
2)、不允许null为键null为值(空值空键)
Hashtable tab = new Hashtable(); tab.put(null,null); // 报错NullPointerException // HashMap能装3)、线程安全的
子类Properties类(HashTable的子类)
概念:Properties解析(读取)属性文件
作用:为什么要将Entry放到属性文件中? 为了解决硬编码问题,通过修改配置文件可以方便的修改代码中的参数,实现不用改class文件即可灵活变更参数,减少代码的维护成本和提高开发效率。
方法:getProperty(String key)
使用此属性列表中指定的键搜索属性
Properties pp = new Properties(); // load():将属性文件中键值对(Entry)加载到Properties对象里面 pp.load(new FileInputStream("e:/name.properties")); // 取出pp中键值对 System.out.println(pp); //打印你在name中写的键值对 String s = pp.getProperty("110"); // 找到键为110的值 System.out.println(s);
概念:
1)、底层也是一个map集合(保存键值对 Entry)
2)、 用来读取属性文件(什么是属性文件:保存键值对的文件)
3)、以.properties结尾的文件就是属性文件
三、TreeMap类 顶级的父接口都也是Map
概念:底层是一个树型结构,对元素进行排序(针对键来排序),是无序的map集合
TreeMap map = new TreeMap(); // 树型结构的map集合 map.put(1,"abc"); // 键只是同一种类型 map.put(23,"bbc"); map.put(2,"cbc"); System.out.println(map);
小结:Map集合的体系结构
-Map:映射根接口
-HashMap:底层哈希表
-LinkedHashMap:;链表 + 哈希表
-HashTable:线程安全,且不允许空值空键
-Properties:解析属性文件
-TreeMap:底层是树型结构,对键进行排序
概念:在计算存储数据的位置的时候,这个位置上已经有值,这就是哈希碰撞
HashSet的值就是存在HashMap的键上 (HashSet只有一个值,且不重复就是因为如此)
链表转为红黑树的过程 当一个链表上的元素个数(链表的节点数)大于等于8个,看这个集合中元素个是否大于等于64 没有:底层数组做一次扩容:当前长度的2倍 有:这个链表就会形成红黑树 (提高查询效率)
当我们做删除的时候 原来是红黑树的,如果元素个数小于等于6个,那么这个红黑树又会转为链表
1、体系结构 Collection
List:有序可重复 实现类: -ArrayList:底层是一个数组,查改快,增删插慢 -LinkedList:底层是一个双向链表,双向队列,栈 (1)增删插快,查改慢 (2) 对首尾操作快 (3)栈:push(),pop() -Vector:线程安全的动态数组
Set:无序不可重复 -HashSet:底层是一个哈希表,去重的原理:hashCode + equals,作用:用来去重的 -LinkedHashSet:哈希表+单向链表,可以维护顺序 -TreeSet:底层是树结构,作用:用来排序 要求:同一种类型,要使用Comparable或Comparator,排序
2、使用场景: 集合用:ArrayList,需要大频率的增删插,将ArrayList转换为LinkedList 需要去重,将ArrayList转换HashSet,如果需要保证顺序,转换为LinkedHashSet 需要排序,将ArrayList转换为TreeSet
集合:一次保存一个值
Map:一次保存一对值(键值对)