Collection
集合概述
集合与数组的对比
- 数组容量固定 --- 集合容量可根据需要动态增减
- 数组可以存基本数据类型(int long bool) --- 集合只能存储其包装类
集合体系结构
Collection-常见成员方法
- boolean add(E e)添加元素
Collection<Integer> collection = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
collection.add(i);
}
- boolean remove(object o)从集 合中移除指定的元素
System.out.println(collection.remove(9)); // true
System.out.println(collection.remove(9)); // false
- boolean removeif(object o)根据条件进行删除
collection.removeIf(v -> {
return v % 2 == 0;
});
- void clear()清空集合
collection.clear();
- boolean contains(object o)判断集合中 是否存在指定的元素
System.out.println(collection.contains(8));
- boolean isEmpty() 判断集合是否为空
System.out.println(collection.isEmpty());
- int size() 集合的长度,也就是集合中元素的个数
System.out.println(collection.size());
Collection
迭代器基本使用
Collection<Integer> collection = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
collection.add(i);
}
Iterator<Integer> iterator = collection.iterator();
while(true)
{
try {
Integer next = iterator.next();
System.out.println(next);
} catch (NoSuchElementException e)
{
break;
}
}
迭代器删除方法
while(true)
{
try {
Integer next = iterator.next();
if(next % 2 == 0)
{
iterator.remove();
}
} catch (NoSuchElementException e)
{
break;
}
}
for (Integer integer : collection) {
System.out.println(integer);
}
增强for
只有实现的Iterable接口的类才可以用增强for。
for (Integer integer : collection) {
System.out.println(integer);
}
- 拿到的是元素的副本,修改副本不会对元素本身有影响
- 数组也可以用增强for
- Map没有实现Iterable接口,所以不能使用增强for
集合的遍历方式:
- 普通for, 如果有用到索引
- 增强for, 只能查看
- 迭代器
Iterator<E> it collection.iteraotr()
List与LinkedList
List特点:
- 有序: 存取顺序一样
- 索引:通过索引访问(读/写)元素
- 可重复
基本用法:
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i);
list.add(i);
}
for (Integer integer : list) {
System.out.println(integer);
}
特有的方法 -- 都是跟索引相关的,索引都是从0开始的
- void add(int index E element)
list.add(1,100);
- E remove(int index)
# 参数和索引都是int,不好区分,所改成了 String
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(i + " + " + i + " = " + (i + i));
}
// IndexOutOfBoundsException : RuntimeException
System.out.println(list.remove(200));
- E set(int index, E element)
System.out.println(list.set(1, "111"));
- E get(int index)
System.out.println(list.get(4));
数据结构
栈和队列
栈:先进后出
队列:先进先出
数组:内存是连续的,可以通过索引定位元素
链表(单向/双向): 内存不连续,只能通过遍历定位元素
ArrayList-源码解析
- 动态增加内存
- 内存连续
LinkedList(双向连表)
- 内存不连续的
- 增删快
LinkedList<String> list = new LinkedList<>();
for (int i = 0; i < 10; i++) {
list.add(i + " + " + i + " = " + (i + i));
}
Iterator<String> it = list.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
特有方法 -- 与 first 和 last 相关的:
- void addFirst(E e)
- void addLast(E e)
- E getFirst()
- E getLast()
- E removeFirst()
- E removeLast()
LinkedList-源码解析
标准的双向链表节点
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
泛型
JDK5引入的特性。
不写泛型的弊端
- 都是object类型,ide不能没有类型方法
好处:
- 把运行时期的问题提前到了编译期间
- 避免了强制类型转换
泛型类的使用
可以使用的地方:
- 类 -- 泛型类
- 方法声明 -- 泛型方法
- 接口之后 -- 泛型方法
使用:
- 一个类后面有<E>表示这是一个泛型类
- 创建对象时,必须给这个类确定具体的类型
自定义泛型类
- <类型>: 指定类型,一般是一个字母
- <类型1, 类型2,...> 指定多个泛型
public class Box<E> {
private E element;
public E getElement() {
return element;
}
public void setElement(E element) {
this.element = element;
}
}
自定义泛型方法
private static <E> List<E> addList(List<E> lst, E hello, E world) {
lst.add(hello);
lst.add(world);
return lst;
}
泛型接口
public interface List<E> extends Collection<E> {
}
通配符
通配符: <?>
- 不能添加
- 取出的也是它的父类
规定: 子类 > 父类
上限:<?extends 类型>
- ArrayList<?extends Number>, 类型 >= Number
下限: <?super 类型>
- ArrayList<?super Number>, 类型 <= Number
Set
概述
- 元素不能重复
- TreeSet, HashSet
- 存取顺序不一致
- 没有索引方法
基本使用:
Set<String> lst = new HashSet<>();
lst.add("aabb");
lst.add("ccdd");
lst.add("eeff");
lst.add("aabb");
for (String s : lst) {
System.out.println(s);
}
TreeSet-基本使用
- 对于元素进行排序(需要定制规则)
指定排序规则:
- 自然排序
TreeSet<Student> students = new TreeSet<>();
public class Student implements Comparable<Student> {
@Override
public int compareTo(Student o) {
/*
return 0 ==
reutrn < 0 <
return > 0 >
*/
return 0;
}
}
- 使用比较器
Set<Student> lst = new TreeSet<>(new Comparator<Student>(){
@Override
public int compare(Student o1, Student o2) {
return 0;
}
});
当自然排序不满足需求时,就使用比较器(特别是对第三方已写好的类)。
数据结构&平衡二叉树
二叉树
节点的度: 子节点的个数
高度: 到叶子节点的层数
平衡二叉树
平衡二叉树: 左右子树的高度差不超过1, 任意子树都是平衡二叉树
添加一个节点之后,不再是平衡二叉树时通过,左右旋重新平衡。
- 左旋 -- 将根节点的右侧往左拉,并将多余的左子节点出让给已经降级的根节点当右子节点。
- 右旋 -- 将根节点的左侧往右拉,并将多余的右子节点出让给已经降级的根节点当左子节点。
平衡二叉树旋转的由种情况
左左和左右
- 左左 -- 当根节左子树的左子树有节点插入,导致树不平衡
- 左右 -- 当根节左子树的右子树有节点插入,导致树不平衡(图中打x的两步是行不通的...)
右右和右左
- 右右 -- 根节点右子树的右子树有节点插入,导致二叉树不平衡
- 右左 -- 根节点右子树的左子树有节点插入,导致二叉树不平衡(图中打x的两步是行不通的...)
红黑树&HashSet
- 使用红黑规则 -- 判断显否平衡
- 红黑节点
红黑规则:
- 每一个节点或是红色,或者是黑色
- 跟必黑
- 没有子节点或父节点(根节点)的节点 -- 叶子节点(Ni)它是黑色的
- 不能出现两个红色节点相连的情况(红节点的子节点必须是黑色的)
- 每一个节点,从该节点到其后代叶节点的简单路径上,匀包含相同数目的黑色节点。
第5条规则的解释:
节点8: 到NIL节点的所有路经黑节点的个数一样都是2
- 8 -> 1 -> NIL
- 8 -> 1 -> 6 -> NIL
- 8 -> 11 -> NIL
添加节点的默认颜色
- 黑色? -- 添加三个元素要调整两次
- 红色? -- 添加三个元素要调整一次
默认颜色为红色是效率更高!
添加节点后如何保证红黑规则 -- 1
- 根节点 -- 红变黑
- 添加的节点,父节点是黑色的,不需要做调整
- 添加的节点,父节点是红色的,要看叔叔节点(父亲的兄弟)
添加节点 22,父节点23,叔叔是 18
如果父亲的叔叔都是红色的,需要做以下3个调整:
1. 将父节点 23 变黑, 将叔叔 18 变黑
2. 将祖父节点 20 变 红
3. 如果祖父是根,则将祖父变黑
添加节点后如何保证红黑规则 -- 2
添加的节点,父节点是红色的,要看叔叔节点(父亲的兄弟)
- 叔叔是红色的
- 叔叔是黑色的(NIL)
1. 将父节点15 --> 黑
2. 祖父节点16--> 红
3. 以祖父节点为支点 -- 旋转(左右看各自的高度)
HashSet
HashSet<String> hashSet = new HashSet<>();
hashSet.add("hello");
hashSet.add("world");
hashSet.add("java");
hashSet.add("java");
hashSet.add("java");
hashSet.add("java");
hashSet.add("java");
Iterator<String> it = hashSet.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
System.out.println("++++++++++++++++++++++++++++");
for (String s : hashSet) {
System.out.println(s);
}
哈希
- 根据地址计算(Object类默认)
- 根据属性计算(自定义类的重载)
- 是一个整数
- Objects.hasCode(); 根据对象地址计算出hash值
- idea中hashCode有模板
JDK1.7底层原理解析
- 数组 + 链表
HashSet<String> hm = new HashSet<>();
// 1. 创建一个默认长度为16, 默认加载 因子 0.75,数组名为table
// 2. 根据元素的hashCode + 数组长度计算出存储位置
// 3. 位置是null? 直接存放,否则
// 4. equals判断是否相等(链表遍历比较)? 相等什么也不做,否则存入链表
// 5. 0.75 * 16 = 12 时 容量 *= 2, ...
JDK1.8底层优化
- 数组 + 链表 + 红黑树
当某个位置的链表长主 > 8 时使用红黑树代替链表!!
HashMap&TreeMap
Map
Map<String, String> map = new HashMap<>();
map.put("san", "张三");
map.put("si", "李四");
System.out.println(Arrays.asList(map));
常用方法
- V put(K key, V value)
- V remove(objectKey)
- void clear()
- boolean containsKey(Object key)
- boolean containsValues(Object value)
- boolean isEmpty()
- int size()
第一种遍历方式(Set<k> keySet(), V get(K key))
Map<String, String> map = new HashMap<>();
map.put("san", "张三");
map.put("si", "李四");
for (String key : map.keySet()) {
System.out.println(map.get(key));
}
第二种遍历方式(键和值一起获取)
Map<String, String> map = new HashMap<>();
map.put("san", "张三");
map.put("si", "李四");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
HashMap-原理解析
与HashSet类似...
TreeMap-原理解析
使用红黑树...
可变参数
JDK5之前使用数组...
public static void main(String[] args) {
System.out.println(getMax(1, 2, 3, 4, 5));
}
static private <E extends Comparable<E>> E getMax(E...arr){
E val = arr[0];
for (int i = 1; i < arr.length; i++) {
if (val.compareTo(arr[i]) < 0) {
val = arr[i];
}
}
return val;
}
创建不可变的集合
批量添加元素...
- List<E> of(E ... e)
- Set<e> of(E ... e) -- > 不能有重复的
- Map<K, V> of(E ...e) -->of(k, v, k, v ,k, v) / ofEntries(...)
java9 之后才有这些方法....
Stream流
/*
体验stream流
创建一个集合, 存储多个字符串元素
"张三丰", "张无忌", "张翠山", "王二麻子", "张良", "谢广坤"
把集合中所有以"张"开头的元素存储到一个新的集合
把"张"开头的集合中的长度为3的元素存储到-一个新的集合
遍历上一步得到的集合
* */
List<String> list = new ArrayList<>(Arrays.asList("张三丰", "张无忌", "张翠山", "王二麻子", "张良", "谢广坤"));
list.stream().filter(s -> s.startsWith("张"))
.filter(s->s.length() == 3)
.forEach(System.out::println);
Stream流-思想特点 -- 流水线:
- 获取
- 中间方法
- 终结方法
Stream流-获取方法
使用前提:
- 所有单列集合 -- Collection.stream()
- 所有双列集合 -- keySet()/entrySet的单列集合 ...
- 数组 -- Arrays的静态方法stream生成流
- 同种类型的多个数据 -- Stream.of(...)
ArrayList<String> list =
new ArrayList<>(Arrays.asList("1", "2", "3"));
list.stream().forEach(System.out::println);
Map<String, String> map = new HashMap<>();
map.put("san", "张三");
map.put("si", "李四");
map.put("wang", "王五");
map.entrySet().stream()
.forEach(stringStringEntry -> System.out.println(
stringStringEntry.getKey() + " -> "
+ stringStringEntry.getValue()));
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
Arrays.stream(arr).forEach(System.out::println);
Stream.of("1", "2", "4").forEach(System.out::println);
中间方法-filter
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
int[] ints = Arrays.stream(arr)
.filter(value -> value % 2 == 0).toArray();
for (int anInt : ints) {
System.out.println(anInt);
}
其他常用中间方法
- limit(size) 截取前 size
- skie(int size) 跳过
- concat(Stream a, Stream b) 合并两个流
- distinct() 去重 依赖 hashCode 和 equals 方法
Stream流-终结方法
- foreach(action)
- count() 元素个数
收集数据
Stream流-不能直接修改数据源中的数据
收集数据:Stream.collection(Collectors..toLis / toSet)
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5,6,7));
List<Integer> collect = list.stream().filter(v -> v % 2 == 0)
.collect(Collectors.toList());
Set<Integer> collect1 = list.stream().filter(v -> v % 2 == 0)
.collect(Collectors.toSet());
收集方法-toMap
List<String> list = new ArrayList<>(Arrays.asList("first,1", "second,1", "third,3"));
Map<String, String> map = list.stream().collect(Collectors.toMap(
s -> s.split(",")[0],
s -> s.split(",")[1]
));
for (Map.Entry<String, String> en : map.entrySet()) {
System.out.println(en.getKey() + "->" + en.getValue());
}
toMap中要有两个方法,第一个产生key, 第二个产生value.