1、迭代器
1、什么是迭代:
迭代是取数据的过程.先判断集合中是否有数据,如果有,就取出一个数据,接着再判断集合中是否有数据,
如果有再接着取出一个数据,这样往复循环直到所有数据都取出来了。
2、为什么要迭代器?
背景:List接口有索引,我们可以通过for循环+get方法来获取数据,但是Set接口这边没有索引,不能通过for循环+get方式获取数据。
所以,Collection接口就创建了一种通用的方式方便所有的集合来获取数据。就是迭代器(Iterator)
3、迭代器和集合的关系:
首先,Collection接口中有一个方法可以得到迭代器
interface Collection {
Iterator<E> iterator() 返回此集合中元素的迭代器
}
其次,举个例子:List接口继承了Collection集合,ArrayList类实现了List接口,也就也拥有了获取迭代器的方法
interface List extends Collection {
}
class ArrayList implements List {
pulic Iterator<E> iterator() {
return new 迭代器();
}
}
故,每个集合都实现iterator方法,都能获取到迭代器
4、如何获取迭代器?
Iterator <E> itr = 集合.iterator();
5、迭代器的常用方法:
(1)boolean hasNext()
判断是否有下一个元素,如果有返回true
(2) E next()
1.取出下一个元素
2.将指针移动到下一个位置
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("马蓉");
list.add("李小璐");
list.add("白百合");
list.add("张柏芝");
Iterator<String> itr = list.iterator();
// 2.循环判断是否有下一个元素
while (itr.hasNext()) {
// 3.如果有,就取出下一个元素
System.out.println(itr.next());
}
2、for循环的增强型写法(foreach)
1、格式:
for (数据类型 变量名 : 数组或集合) {
}
PS:常常快捷键:数组or集合名.for
2、优点:
代码简单
3、缺点:
没有索引
4、应用场合
如果不关心索引就可以使用增强for(如Set集合)
5、增强for循环的底层
通过反编译器可知,
1.遍历数组,底层是普通for循环
2.遍历集合,底层是迭代器
Collection集合分布:
1、List接口特点:
1.有索引,存储和取出有顺序
2.元素可以重复
2、List接口中的常用方法:
增 void add(int index, E element) 将指定的元素插入此列表中的指定位置(可选操作)。
**删 ** E remove(int index) 删除该列表中指定位置的元素(可选操作)。
改 E set(int index, E element) 用指定的元素(可选操作)替换此列表中指定位置的元素。
查 E get(int index) 返回此列表中指定位置的元素。
运用多态使用这些方法
public static void main(String[] args) {
// 使用List接口的途径(多态)
List<String> list = new ArrayList<>();
list.add("钱大妈");
list.add("老干妈");
list.add("大姨妈");
list.add("大姨妈");
list.add("大姨妈");
list.add("大姨妈");
// void add(int index, E element) 将指定的元素插入此列表中的指定位置(可选操作)。
list.add(0, "钱打野");
System.out.println(list); // [钱打野, 钱大妈, 老干妈, 大姨妈]
// E get(int index) 返回此列表中指定位置的元素。
System.out.println(list.get(2));
// E remove(int index) 删除该列表中指定位置的元素(可选操作)。
System.out.println(list.remove(1)); // 被删除的元素
System.out.println("删除后:" + list); // 删除后:[钱打野, 老干妈, 大姨妈]
// E set(int index, E element) 用指定的元素(可选操作)替换此列表中指定位置的元素。
list.set(1, "老干爹");
System.out.println("修改后: " + list); // 修改后: [钱打野, 老干爹, 大姨妈]
}
3、List的其中一个子类:LinkedList集合
1、特点:
因为底层结构是链表,所以查询慢,增删快
class LinkedList {
Node<E> first; 第一个节点
Node<E> last; 最后一个节点
}
//节点,双向链表
class Node<E> {
E item; // 数据域
Node<E> next; // 下一个节点
Node<E> prev; // 上一个节点
}
2、LinkedList特有方法:
(1) void addFirst(E e) 在该列表开头插入指定的元素。
(2)void addLast(E e) 将指定的元素追加到此列表的末尾。
(3)E getFirst() 返回此列表中的第一个元素。
(4)E getLast() 返回此列表中的最后一个元素。
(5) E removeFirst() 从此列表中删除并返回第一个元素。
(6)E removeLast() 从此列表中删除并返回最后一个元素。
public static void main(String[] args) {
LinkedList<String> linked = new LinkedList<>();
linked.add("长得帅");
linked.add("张帅");
linked.add("郝帅");
linked.add("都狠帅");
// void addFirst(E e) 在该列表开头插入指定的元素。
linked.addFirst("赵德住");
// void addLast(E e) 将指定的元素追加到此列表的末尾。
linked.addLast("随便");
System.out.println(linked); // [赵德住, 长得帅, 张帅, 郝帅, 都狠帅, 随便]
// E getFirst() 返回此列表中的第一个元素。
System.out.println(linked.getFirst()); // 赵德住
// E getLast() 返回此列表中的最后一个元素。
System.out.println(linked.getLast()); // 随便
// E removeFirst() 从此列表中删除并返回第一个元素。
linked.removeFirst();
// E removeLast() 从此列表中删除并返回最后一个元素。
linked.removeLast();
System.out.println(linked);
}
4、List的其中一个子类:ArrayList集合
1、特点:
因为底层结构是数组,所以查询块,增删慢
2、底层原理:
通过“看源码”,可以知道:(详细deBug见代码)
1.ArrayList内部有一个成员变量Object[] elementData;,ArrayList存储数据就是存到了这个数组中.
2.在构造方法中,初始化elementData为没有内容的数组
3.在第一次调用add方法时,数组会初始化为长度10的数组
4.调用add方法,当数组的容量不够的时候会进行扩容,新的容量是当前的1.5倍,10 -> 15 -> 22 -> 33(初始容量为0的时候扩容规则是0 ->10)。
public static void main(String[] args) {
/*
public class ArrayList {
//本句的英文注释是:
// The array buffer into which the elements of the ArrayList are stored.
//(ArrayList内部有一个成员变量Object[] elementData;,ArrayList存储数据就是存到了这个数组中)
transient Object[] elementData;//源码语句一
//这句话是ArrayList的构造方法
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;;//源码语句二
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//源码语句三
//(this.element指的是源码语句一中的数组。
//等号右边表示是一个常量,源码中该常量等于{}.
}
}
1.ArrayList内部有一个成员变量Object[] elementData;,ArrayList存储数据就是存到了这个数组中.
2.在构造方法中,初始化elementData为没有内容的数组
3.在第一次调用add方法时,数组会初始化为10个长度的数组
4.调用add方法,当数组的容量不够的时候会进行扩容,新的容量是当前的1.5倍,10 -> 15 -> 22 -> 33
*/
// 看源码:ctrl + 鼠标左键
//点击等号左边的ArrayList,会到达该集合类的定义的地方;
//点击等号左边的ArrayList,会到达该集合类的构造方法的地方
//等号左边是数组变量;等号右边是创建对象
ArrayList<Integer> list = new ArrayList<>();
list.add(1);//打断点deBug看执行过程;forceInto键,首先进入的是Integer的valueOf方法中(先把基本数据类型1转化为Integer乐星
//故要按 stepOut键退出该方法,再按stepInto键进入add方法
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
list.add(11);
}
5、补充:Vector集合及Enumeration
1、特点:
java.util.Vector集合数和ArrayList一样底层使用数组结构。Vector底层使用的是数组,被同步,效率低,所以被淘汰了
2、Enumeration
Enumeration:英文翻译过来就是“枚举”,作用和迭代器是一样的(可以理解为迭代器的前身),可以获取集合的元素
vector这个类中有一个方法:elements(),调用该方法可以得到Enumeration,代码如下:
Vector<String> vec = new Vector<>();
Enumeration<String> enu = vec.elements();
3、Enumeration的2个方法?
(1)hasMoreElements: 判断是否有下一个元素
(2)nextElement(): 获取下一个元素
public static void main(String[] args) {
Vector<String> vec = new Vector<>();
vec.add("刘德华");
vec.add("张学友");
vec.add("黎明");
vec.add("郭富城");
// 迭代器
Iterator<String> itr = vec.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
// Enumeration:英文翻译过来就是“枚举”,作用和迭代器是一样的,可以获取集合的元素
// hasMoreElements: 判断是否有下一个元素
// nextElement(): 获取下一个元素
// elements()得到Enumeration
Enumeration<String> enu = vec.elements();
while (enu.hasMoreElements()) {
System.out.println(enu.nextElement());
}
}
4、Set接口及其子类集合
1、Set接口特点:
(1)没有索引,存取无序
(2)元素不可重复
2、Set接口的常用方法
增 boolean add(E e) 添加一个元素
删 boolean remove(Object o) 删除一个元素
PS:没有修改元素的方法(改),也没有获取一个元素的方法(查),只能通过迭代器来获取数据
3、Set接口的其中一个实现类:HashSet
特点:(1)底层是哈希表
(2)a.没有索引,存取无序
b.元素不可重复
public static void main(String[] args) {
HashSet<String> hs = new HashSet<>();
hs.add("王昭君");
hs.add("杨玉环");
hs.add("貂蝉");
hs.add("西施");
hs.add("西施");
hs.add("西施");
hs.add("西施");
// hs.remove("杨玉环");
// 使用迭代器获取数据
Iterator<String> itr = hs.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
//也可以使用增强for
for (String el : hs) {
System.out.println(el);
}
}
/*
输出结果:(因为元素不可重复,所以没有多个西施)
貂蝉
王昭君
杨玉环
西施
*/
4、HashSet的其中一个子类:LinkedHashSet
1、特点:
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类 java.util.LinkedHashSet ,它是链表和哈希表组合的一个数据存储结构。
(1)无索引
(2)存取有序
(3)元素不可重复
public class Demo11 {
public static void main(String[] args) {
LinkedHashSet<String> lhs = new LinkedHashSet<>();
lhs.add("cc");
lhs.add("bb");
lhs.add("aa");
lhs.add("dd");
// 取出来
Iterator<String> itr = lhs.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
// 增强for
for (String el : lhs) {
System.out.println(el);
}
}
5、哈希表
1、hashCode方法
(1)public native int hashCode(); 返回对象的哈希码值。
(2)被native修饰,是属于Object类的方法
(3)对象的hashCode相当于人的身份证的身份证作用,用于辨别。默认情况对象的hashCode就是对象的内存地址。
大多数时候,我们希望让hashCode的值反应的不是内存地址,而是反应对象的内容,所以我们会重写hashCode方法。,让hashCode值和对象的内容相关
(hashCode可能会重复,我们要尽量避免hashCode相同(系统有默认重写方案,已尽量避免)))
public static void main(String[] args) {
// Person p1 = new Person("凤姐", 18);
// System.out.println(p1); // 打印对象的地址: 50cbc42f
// System.out.println(p1.hashCode()); // 打印对象的hashCode: 1355531311
// System.out.println(Integer.toHexString(p1.hashCode())); // 打印对象的hashCode的十六进制: 50cbc42f
//经过上述三行代码可知,hashCode方法默认返回的是对象的内存地址
Person p2 = new Person("a", 18);
Person p3 = new Person("b", 17);
//重写了hashCode方法
System.out.println(p2.hashCode());
System.out.println(p3.hashCode());
// String类已经重写了hashCode
System.out.println("abc".hashCode());
System.out.println("cba".hashCode());
//即使重写了hashCode方法,也有可能出现hashCode值相同的情况
System.out.println("通话".hashCode()); // 1179395
System.out.println("重地".hashCode()); // 1179395
}
2、哈希表存储元素的过程(使用HashSet集合存储自定义元素)
1、哈希表的组成:
由数组和链表组成。人们一般以水平是数组,垂直是链表的图像表示哈希表。如下图:
哈希表图示(此处模拟下哈希算法为: 位置 = 元素的hashCode % 10;)
2、说出哈希表存储元素的过程(哈希表判断元素唯一(元素不重复)的原理)
1.如果hashCode不相同,直接存储
2.如果hashCode相同,调用equals()方法
2.1 equals()返回false,存储
2.2 equals()返回true,不存储
结论:只有满足两个条件:(1)hascCode相同,(2)equals()方法返回true值,才认为是相同的元素,才会存如哈希表。
注意:
只有底层使用哈希表结构的集合,才需要重写hashCode和equals以确保元素不重复(如集合HashSet/LinkedHashSet才需要重写)。
但是如果元素是存储到ArrayList,LinkedList,则不需要重写.(因为这两个集合允许元素重复,没有必要使用hashCode。
3、哈希表的扩容介绍:
我们知道,哈希表的数组存储的是索引值(即使用数组处理冲突),而链表才是真正存储元素数据的地方(同一hashCode值的元素都存储在同一链表当中)。所以当hashCode值相等的元素数据很多的时候,链表的长度可能会变得很长,这样的话通过key值依次查找的查询效率将会变得很低。故jdk1.8的时候,哈希表采用数组+链表+红黑树实现。
当链表长度(这里指的是和)超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
通过Api文档,查看到hashSet集合的构造方法,可以看到hashSet集合初始容量为16(数组的长度为16),负载因子为0.75.(简单理解负载因子:就是触发集合扩容时的集合已存储元素的比例)。即当我们哈希表中的元素数量超过容量的75%,也就是容量超过16*75%=12个元素(不是链表长度超过12个,而是总的哈希表存储的元素个数超过12个),就会触发扩容机制。
这样的扩容机制会导致一个现象:部分链表会有“空”的现象,有些链表只有几个元素。这样的存储现象叫做哈希散列(通过哈希算法把元素数据散列在数组、链表上。
6、如何选择集合
到目前为止我们学习了常用的集合:
ArrayList,LinkedList,Vector,HashSet和LinkedHashSet
如何选择集合?
1.如果元素不能重复:
HashSet或者LinkedHashSet
2.如果元素可以重复:
2.1 如果查询多使用: ArrayList
2.2 如果增删多使用: LinkedList
3.若没有目的意图,就默认使用ArrayList
7、红黑树
1、概念:
趋近于平衡树,查询的速度非常快。**查找d的最大路径不能超过最小路径的两倍(以次尽量保证红黑树的平衡)。
2、特点:
(1)根节点是黑色的
(2)称空结点(实际上不存在的节点)为叶子节点,是长方形且黑色的。
(3)每个红色节点的子节点(不包括叶子节点)都是黑色的
(4)任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同。