0
点赞
收藏
分享

微信扫一扫

JAVA -- 集合

Collection

集合概述

集合与数组的对比

  • 数组容量固定 --- 集合容量可根据需要动态增减
  • 数组可以存基本数据类型(int long bool) --- 集合只能存储其包装类

集合体系结构

JAVA -- 集合_父节点

Collection-常见成员方法

  1. boolean add(E e)添加元素

Collection<Integer> collection = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
collection.add(i);
}

  1. boolean remove(object o)从集 合中移除指定的元素

System.out.println(collection.remove(9)); // true
System.out.println(collection.remove(9)); // false

  1. boolean removeif(object o)根据条件进行删除

collection.removeIf(v -> {
return v % 2 == 0;
});

  1. void clear()清空集合

collection.clear();

  1. boolean contains(object o)判断集合中 是否存在指定的元素

System.out.println(collection.contains(8));

  1. boolean isEmpty() 判断集合是否为空

System.out.println(collection.isEmpty());

  1. 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开始的

  1. void add(int index E element)

list.add(1,100);

  1. 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));

  1. E set(int index, E element)

System.out.println(list.set(1, "111"));

  1. 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 相关的:

  1. void addFirst(E e)
  2. void addLast(E e)
  3. E getFirst()
  4. E getLast()
  5. E removeFirst()
  6. 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

JAVA -- 集合_子树_02

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, 任意子树都是平衡二叉树

添加一个节点之后,不再是平衡二叉树时通过,左右旋重新平衡。

  • 左旋 -- 将根节点的右侧往左拉,并将多余的左子节点出让给已经降级的根节点当右子节点。

JAVA -- 集合_父节点_03

  • 右旋 -- 将根节点的左侧往右拉,并将多余的右子节点出让给已经降级的根节点当左子节点。


JAVA -- 集合_子树_04


平衡二叉树旋转的由种情况

左左和左右

  • 左左 -- 当根节左子树的左子树有节点插入,导致树不平衡

JAVA -- 集合_子节点_05

  • 左右 -- 当根节左子树的右子树有节点插入,导致树不平衡(图中打x的两步是行不通的...)

JAVA -- 集合_子节点_06



右右和右左

  • 右右 -- 根节点右子树的右子树有节点插入,导致二叉树不平衡

JAVA -- 集合_子节点_07

  • 右左 -- 根节点右子树的左子树有节点插入,导致二叉树不平衡(图中打x的两步是行不通的...)

JAVA -- 集合_父节点_08

红黑树&HashSet

  • 使用红黑规则 -- 判断显否平衡
  • 红黑节点

红黑规则:

  1. 每一个节点或是红色,或者是黑色
  2. 跟必黑
  3. 没有子节点或父节点(根节点)的节点 -- 叶子节点(Ni)它是黑色的
  4. 不能出现两个红色节点相连的情况(红节点的子节点必须是黑色的)
  5. 每一个节点,从该节点到其后代叶节点的简单路径上,匀包含相同数目的黑色节点。

JAVA -- 集合_子节点_09

第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. 如果祖父是根,则将祖父变黑

JAVA -- 集合_子树_10

JAVA -- 集合_父节点_11

添加节点后如何保证红黑规则 -- 2

添加的节点,父节点是红色的,要看叔叔节点(父亲的兄弟)

  • 叔叔是红色的

JAVA -- 集合_子树_12

JAVA -- 集合_子节点_13

  • 叔叔是黑色的(NIL)
    1. 将父节点15 --> 黑
    2. 祖父节点16--> 红
    3. 以祖父节点为支点 -- 旋转(左右看各自的高度)

JAVA -- 集合_子节点_14

JAVA -- 集合_父节点_15

JAVA -- 集合_父节点_16

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));

常用方法

  1. V put(K key, V value)
  2. V remove(objectKey)
  3. void clear()
  4. boolean containsKey(Object key)
  5. boolean containsValues(Object value)
  6. boolean isEmpty()
  7. 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.


举报

相关推荐

0 条评论