map遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);

for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("key: " + entry.getKey());
Syetem.out.println("value: " + entry.getValue());
}

for (String key : map.keySet()) {
System.out.println("key: " + key);
}

for (Integer value : map.values()) {
System.out.println("value: " + value);
}

Iterator<Map.Entry<String, Integer>> itor = map.entrySet().iterator();
while (itor.hasNext()) {
Map.Entry<String, Integer> entry = itor.next();
System.out.println("key: " + entry.getKey());
System.out.println("value: " + entry.getValue());
}

map.foreach((key, value) -> {
System.out.println("key: " + key);
System.out.println("value: " + value);
});

Set遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("c");

for (String str : set) {
System.out.println(str);
}

Iterator<String> itor = set.iterator();
while (itor.hasNext()) {
String str = itor.next();
System.out.println(str);
}

时间工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class Test {
public static void main(String[] args) {

//北京时间 不再使用Date、Calendar
Date date = new Date();
System.out.println(date); //Sat Sep 25 21:34:03 CST 2021

//世界时间
Instant instant = Instant.now();
System.out.println(instant); //2021-09-25T13:34:03.356715800Z
//获取秒
long currentSecond = instant.getEpochSecond();
//获取毫秒
long currentMilli = instant.toEpochMilli();
long systemCurrent = System.currentTimeMillis();
System.out.println(currentSecond); //1632576843
System.out.println(currentMilli); //1632576843356
System.out.println(systemCurrent); //1632576843360

//北京时间
LocalDateTime localDateTime = LocalDateTime.now();
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
System.out.println(localDateTime); //2021-09-25T21:34:03.365718
System.out.println(localDate); //2021-09-25
System.out.println(localTime); //21:34:03.365718

//创建日期: 2019-09-10 14:46:56
LocalDateTime localDateTime1 = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56);
System.out.println(localDateTime1); //2019-09-10T14:46:56
//增加一年
localDateTime1 = localDateTime1.plus(1, ChronoUnit.YEARS);
System.out.println(localDateTime1); //2020-09-10T14:46:56
//减少一个月
localDateTime1 = localDateTime1.minus(1, ChronoUnit.MONTHS);
System.out.println(localDateTime1); //2020-08-10T14:46:56

//通过with修改
localDateTime1 = localDateTime1.with(ChronoField.YEAR, 2023);
System.out.println(localDateTime1); //2023-08-10T14:46:56

//格式化时间为字符串
LocalDate localDate1 = LocalDate.of(2019,9,10);
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String str = localDate1.format(dateTimeFormatter);
System.out.println(str); //10/09/2019

//字符串解析成时间
LocalDateTime localDateTime2 = LocalDateTime.parse("2017-12-13 10:10:10", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(localDateTime2); //2017-12-13T10:10:10
}
}


//世界标准时间,其中T表示时分秒的开始(或者日期与时间的间隔),Z表示这是一个世界标准时间
//2017-12-13T01:47:07.081Z

//本地时间,也叫不含时区信息的时间,末尾没有Z
//2017-12-13T09:47:07.153

//含有时区信息的时间,+08:00表示该时间是由世界标准时间加了8个小时得到的,[Asia/Shanghai]表示时区
//2017-12-13T09:47:07.153+08:00[Asia/Shanghai]

//jdk1.8后使用:
//Instant、LocalDateTime(LocalDate、LocalTime)、DateTimeFormatter

参考链接1参考链接2

使用Predicate操作集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Test {
public static void main(String[] args) {

var books = new HashSet<>();
books.add("轻量级JAVA EE企业应用实战");
books.add("疯狂JAVA讲义");
books.add("疯狂IOS讲义");
books.add("疯狂Ajax讲义");
books.add("疯狂Android讲义");

//集合过滤
books.removeIf(ele -> ((String) ele).length() < 10);
books.foreach(System.out::println);

var books = new HashSet<>();
books2.add("轻量级JAVA EE企业应用实战");
books2.add("疯狂JAVA讲义");
books2.add("疯狂IOS讲义");
books2.add("疯狂Ajax讲义");
books2.add("疯狂Android讲义");

//统计集合内容
System.out.println(calAll(books2, ele -> ((String) ele).contains("疯狂")));
System.out.println(calAll(books2, ele -> ((String) ele).contains("Java")));
System.out.println(calAll(books2, ele -> ((String) ele).length() > 10));

//或用流处理的方式进行集合统计
System.out.println(books.stream().filter(ele -> ((String) ele).contains("疯狂")).count());
System.out.println(books.stream().filter(ele -> ((String) ele).contains("Java")).count());
System.out.println(books.stream().filter(ele -> ((String) ele).length() > 10).count());
books.stream().mapToInt(ele -> ((String) ele).length()).foreach(System.out::println);
}

private static int calAll(Collection books, Predicate predicate) {
int total = 0;
for (var obj : books) {
if (predicate.test(obj)) {
total++;
}
}
return total;
}
}

/*
* 流操作的中间方法,例如filter方法、mapToInt方法,返回值是一个新的流
* 末端方法,例如count方法,消耗掉该流,流不再可用
*/

内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Cow {
private double weight;

public Cow() {}

public Cow(double weight) {
this.weight = weight;
}

//定义一个非静态内部类
private class CowLeg {
private double length;
private String color;

public CowLeg() {}

public CowLeg(double length, String color) {
this.length = length;
this.color = color;
}

public void info() {
System.out.println(this.color + this.length);
//直接访问外部类的private成员变量
//若未全称指定,则查询变量的优先级为:方法内的局部变量 -> 内部类成员变量 -> 外部类成员变量
System.out.println(Cow.this.weight);
}
}

public static void main(String[] args) {

}
}

/*
* 内部类比外部类可多使用三个修饰符:private、protected、static
* 非静态内部类不能有静态成员
* 外部类访问非静态内部类实例成员,必显式创建非静态内部类对象来调用访问其实例成员。因创建外部类对象的时候不见得创建了内部类对象
* 根据静态成员不能访问非静态成员的规则,外部类静态代码不能访问非静态内部类
* 不允许非静态内部类里面定义静态成员
* 外部类上一级程序单元为包,所以不可使用static修饰;内部类上一级程序单元是外部类,可用static修饰
* 静态内部类无法访问外部类实例变量
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LocalInnerClass {

public static void main(String[] args) {
//局部内部类
class InnerBase {
int a;
}

//局部内部类子类
class InnerSub extends InnerBase {
int b;
}

var is = new InnerSub();
is.a = 5;
is.b = 8;
}
}

/*
* 局部内部类:在方法内定义,在方法内有效,不能在方法外使用,因此不可使用访问控制符和static修饰符
* 局部内部类意义不大,很少有人用
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
interface Product {
double getPrice();
String getName();
}

public class AnonymousTest {

public void test(Product p) {
System.out.println(p.getName() + p.getPrice());
}

public static void main(String[] args) {
var ta = new AnonymousTest();
ta.test(new Product() {
@Override
public double getPrice() {
return 567.8;
}

@Override
public String getName() {
return "ddsd";
}
});
}
}

/*
* 匿名内部类:一次性使用,创建匿名内部类时会立即创建一个该类的实例,这个类定义会立即消失
* 匿名内部类必修继承一个父类,或实现一个接口。最多只能继承或实现一个父类或接口
* 匿名内部类不能是抽象类(立即创建实例); 不能定义构造器(匿名内部类没有类名);但匿名内部类可以定义初始化块
* 最常见的创建匿名内部类的方式是需要创建某个接口类型的对象
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
abstract class Device {
private String name;
public abstract double getPrice();
public Device() {}
public Device(String name) {
this.name = name;
}
}

public class AnonymousInner {

public void test(Device d) {
System.out.println(d.getPrice());
}

public static void main(String[] args) {

var ai = new AnonymousInner();

//调用有参构造器实现内部类
ai.test(new Device("theName") {
@Override
public double getPrice() {
return 67.8;
}
});

//调用无参构造器实现内部类
var d = new Device() {
//初始化代码块
{
System.out.println("我是初始化代码块");
}

@Override
public double getPrice() {
return 56.2;
}
}

ai.test(d);
}
}

/*
* 被局部内部类、匿名内部类访问的局部变量,必须是final修饰
*/

lambda表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public interface Command {
void process(int element);
}

public class ProcessArray {

public void process(int[] target, Command cmd) {
for (var t : target) {
cmd.process(t);
}
}

}

public class CommandTest {

public static void main(String[] args) {
var pa = new ProcessArray();
int[] target = {3, -4, 6, 4};
pa.process(target, element -> System.out.println("数组的平方:" + element * element));
}

}

/*
* lambda表达式运行使用更简洁的代码创建“只有一个抽象方法的接口(函数式接口)”的实例。对于函数式接口,无需匿名内部类,用lambda
* lambda表达式的目标类型必须是函数式接口(只能是接口,不能抽象类),即只包含一个抽象方法的接口。函数式接口可包含多个默认方法、类方法,但只能声明一 * 个抽象方法
* Runnable等接口是函数式接口
* 带有@FunctionalInterface注解的接口为函数式接口
* 与匿名内部类一样,访问的局部变量必须是final修饰
* 与匿名内部类不同的是,匿名内部类可以为任意接口、抽象类、甚至普通类,创建实例。无函数式接口的限制。并且匿名内部类实现的抽象方法允许调用接口中定义的* 默认方法;但lambda表达式不可
*/

final修饰符

可修饰类、变量(类变量、实例变量、局部变量、形参)、方法。

  • final成员变量:

    对于未成功赋值的final成员变量,系统默认分配0、null等。

    归纳起来:

    • 类变量:必须在静态初始化块中指定初始值,或声明该类变量时指定初始值。仅上述两种,且只能选其一。

    • 实例变量:必须在非静态初始化块、声明该实例变量、或构造器中指定初始值。仅上述三种,且只能选其一。

      实例变量不能在静态初始化块中指定初始值,因为静态初始化块是静态成员,不可访问实例变量;类变量不能在普通初始化块中指定初始值,因为类变量在类初始化阶段已被初始化了,普通初始化块不能对其重新赋值。

  • final局部变量:

    只能指定一次(即赋初值)

  • final形参:

    1
    public void test(final int a)

    不可在方法体内再赋值,因调用时已附初值。

final修饰基本类型变量和引用类型变量的区别:

修饰基本类型变量,不可重新赋值;但对于引用类型变量,final仅保证这个引用类型变量所引用的地址不变,但对象是完全可以改变的。

final类型变量功能类似“宏替换”

  • final方法:

    final修饰的方法不可被重写

    1
    2
    3
    4
    5
    6
    7
    8
    public class FinalMethodTest {
    public final void test() {}
    }

    public class Sub extends FinalMethodTest {
    //告知异常
    public void test() {}
    }

    注意是public方法。

    对于private方法,因其仅在本类可见,子类不可见,故子类是不会重写private方法的。因此若子类中定义一个与父类private方法同方法名、同形参列表、同返回值类型的方法,不属方法重写,属定义一个新方法。因此private final方法可在子类“重写”.

    1
    2
    3
    4
    5
    6
    7
    8
    public class FinalMethodTest {
    private final void test() {}
    }

    public class Sub extends FinalMethodTest {
    //正常
    private void test() {}
    }

    final修饰的方法不可重写,但可任意重载。

    重写与重载

  • final修饰类:

    不可有子类。

关于不可变类:

java提供的8个包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的实例变量不可变,java没有提供它们可供修改的方法。

自己创建不可变类,可参考“疯狂java讲义” P188 重点是类中自己使用新的引用对象。

比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CommandTest {

public static int findMax(Comparable[] arr) {
int maxIndex = 0;
for (int i = 0; i < arr.length; ++i) {
if (arr[i].compareTo(arr[maxIndex]) > 0) {
maxIndex = i;
}
}
return maxIndex;
}

public static void main(String[] args) {
String[] str1 = {"Joe", "Bob", "Bill", "Zeke"};
System.out.println(findMax(str1));
}
}

泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Apple<T> {		//注意这里是Apple<T>
private T info;
public Apple() {} //声明构造器的时候只用Apple,不是Apple<T>
public Apple(T info) {
this.info = info;
}
}

public class CommandTest {
public static void main(String[] args) {
Apple<String> a1 = new Apple<>("苹果"); //使用上类似List<String> list = new ArrayList<>()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//子类不能跟泛型形参
public class A extends Apple<T> { //这样会编译失败

}


public class SubA extends Apple<String> {

//正确重写父类方法
@Override
public String getInfo() {
return "子类" + super.getInfo();
}

//错误,重写父类方法时返回值不一致
/* @Override
public Object getInfo() {
return "子类";
} */
}
1
2
3
4
//不指定实际类型继承,此时Apple<T>类中的T系统默认当做Object处理
public class SubA extends Apple {

}

List<String>与List<Integer>虽不同泛型,但仍是同一个class类。因此,在静态方法、静态初始化块、静态变量(都是类相关)它们的声明和初始化中,不允许使用泛型形参。

1
2
3
4
5
6
7
8
9
public class R<T> {
static T info; //错
T age;
public void foo(T msg) {}
public static void bar(T msg) {} //错
}
/*
* 且instanceof运算符后也不能使用泛型
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class CommandTest {

public static void test(List<Object> c) {
for (Object o : c) {
System.out.println(o);
}
}

public static void main(String[] args) {

List<String> strList = new ArrayList<>();

//编译报错,说明List<String>不是List<Object>的子类
//test(strList);

Integer[] ia = new Integer[5];
Number[] na = ia;
//编译正常,但运行报ArrayStoreException,属java潜在风险
na[0] = 0.5;

List<String> iList = new ArrayList<>();
//编译报错,考虑到数组存在的潜在风险,java对泛型做了优化
//List<Object> nList = iList;
}
}

/*
* 如果Foo是Bar的一个子类型,而G是具有泛型声明的类或接口,则G<Foo>不是G<Bar>子类型! 这与正常思维有些不同
* 但数组有所不同,假设Foo是Bar的子类型,那么Foo[]依然是Bar[]的子类型 即数组支持型变,而集合不支持
*/

泛型方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class CommandTest {

//泛型方法,前面<...>为泛型形参声明,以备后面使用
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : c) {
c.add(o);
}
}

static <T> void test1(Collection<T> from, Collection<T> to) {
for (T ele : from) {
to.add(ele);
}
}

//均可
//static <T, S extends T> void test2(Collection<S> from, Collection<T> to)
static <T> void test2(Collection<? extends T> from, Collection<T> to) {
for (T ele : from) {
to.add(ele);
}
}

public static void main(String[] args) {
var oa = new Object[100];
Collection<Object> co = new ArrayList<>();
//编译器可自己判断出T的类型
fromArrayToCollection(oa, co);

List<Object> as = new ArrayList<>();
List<String> ao = new ArrayList<>();
//如果编译器迷惑,将编译失败
//test1(as, ao);

//这个没问题
test2(ao, as);
}
}

使用泛型方法还是使用通配符,随个人喜好

泛型构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Foo<E> {

public E param1;

public <T> Foo(E param1, T param2) {
this.param1 = param1;
System.out.println(param2);
}
}

public class GenericConstructor {
public static void main(String[] args) {

//String对应泛型E,根据输入参数判断,泛型构造器的第二个参数为Integer
Foo<String> mc1 = new Foo<>("abccc", 5);

//String对应泛型E,手动指定泛型构造器的第二个参数为Integer
Foo<String> mc2 = new <Integer> Foo<String>("sadsad", 10);

//手动指定时,菱形语法不可用,即下面会编译不通过
//Foo<String> mc3 = new <Integer> Foo<>("sadsad", 10);
}
}

extends(继承) implements(实现)

​ 一个类只能extends继承一个父类,但可以implements实现多个接口,以此来代替C++中多继承的概念。

​ 一个接口可同时extends多个接口,但不能implements实现任何接口。参考链接

普通for循环与增强型for循环(即foreach,即 for (Integer i : arrayList))

​ 普通for循环获取元素下标,通过下标遍历元素;增强型for循环是java的语法糖,遍历数组时编译后就是普通的fori循环,遍历List集合时编译后是通过迭代器实现遍历。迭代器遍历链表快,遍历数组慢。

​ 所以遍历LinkedList时需选用增强型for循环。

遍历时修改集合对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public static void main(String[] args) {

ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(2);
arrayList.add(4);
arrayList.add(5);

for (int i = 0; i < arrayList.size(); ++i) {
if (ArrayList.get(i) == 2) {

//可运行,但结果[1 2 4 5]
arrayList.remove(i);

//解决方法是加上 i--
i--;
}
}
System.out.println(arrayList);

for (Integer i : arrayList) {
if (i == 2) {

//将运行时异常 ConcurrentModificationException
arrayList.remove(i);

//将运行时异常 ConcurrentModificationException
arrayList.add(9);

//可正常运行,即修改是没有任何问题的
arrayList.set(1, 8);
}
}
System.out.println(arrayList);

//iterator()方法返回的Iterator类型的迭代器没有提供添加的方法,但是listIterator()方法返回的ListIterator类型的迭代器提供了add()方法和set方法,以及向前的previous方法和hasPrevious方法
//Iterator<Integer> iterator = arrayList.iterator();
ListIterator<Integer> listIterator = arrayList.listIterator();
while(listIterator.hasNext()) {
Integer i = (Integer) listIterator.next(); //迭代器向后移一位,并返回移动时所经过的元素值!!
if (i == 2) {

//将运行时异常 ConcurrentModificationException
arrayList.add(6);

//正常运行
listIterator.remove(); //删除迭代器看到的最后一个值
listIterator.add(6); //将一个新元素以当前位置放在集合中 当前项的概念通过把迭代器看做是在对next的调用所给出的项和对previous的调用所给出的项之间而抽象出来的 (迭代器指向的不是元素,而是元素与元素的之间)
listIterator.set(8); //改变迭代器看到的最后一个值
}
}
System.out.println(arrayList);
}

/*
* ConcurrentModificationException
* 因为增强for循环(foreach循环)本质上是隐式的iterator,由于在删除和添加的时候会导致modCount发生变化,但是没有重新设置exceptedModCount,
* 当你使用list.remove()遍历执行iterator.next()时,方法检验modCount的值和exceptedModCount值,
* 如果不相等,就会报ConcurrentModificationException
*
* 迭代器的remove()方法同时维护了modCount和exceptedModCount,所以使用remove()方法可以达到预期的效果
*/

参考链接

ListIterator

List两种实现

  • ArrayList:

    可增长的数组实现。有点在于get、set的调用花费常数时间。缺点是插入和删除代价高,尤其在首端。

  • LinkedList:

    推荐使用LinkedList<Integer> list2 = new LinkedList<>(); 用List<Integer> list2 = new LinkedList<>();好多方法都调不到。

    双向链表,优点是在已知位置时插入删除快。缺点是不易索引,get调用花费大。

    LinkedList常用内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public static void main(String[] args) {

    LinkedList<String> linkedList = new LinkedList<>();
    linkedList.add("1");
    linkedList.add("2");
    linkedList.add("3");
    linkedList.add("4");

    linkedList.getFirst();
    linkedList.getLast();
    linkedList.addFirst("0");
    linkedList.addLast("5");
    linkedList.removeFirst();
    linkedList.removeLast();
    linkedList.clear();

    linkedList.subList(2, 5).clear(); //删除范围内元素
    linkedList.remove("2"); //删除特定元素
    linkedList.remove(2); //删除特定位置元素

    ArrayList<String> arrayList = new ArrayList<>(linkedList);
    String[] my = linkedList.toArray(new String[0]); //转换数组,不指定长度
    String[] my2 = linkedList.toArray(new String[linkedList.size()]); //转换数组,指定长度

    //ArrayList和LinkedList的搜索功能,效率都低效
    linkedList.indexOf("2"); //搜索元素位置
    linkedList.lastIndexOf("2"); //搜索元素位置

    linkedList.set(3, "Replace"); //替换元素
    linkedList.contains("2");

    System.out.println(linkedList);
    }

    参考链接

没有儿子的节点称为树叶; 具有相同父亲的节点称为兄弟。

满二叉树、完全二叉树

二叉树性质

(TreeNode)要自己实现

前序遍历、中序遍历、后序遍历(取决于何时访问根节点)参考链接

二叉搜索树: (没要求完全二叉树)

  • 所有关键字唯一
  • 左子树所有值 < 根节点值 < 右子树值
  • 左右子树分别都是二叉搜索树

平衡搜索树: 树高O(log n)

  • AVL树(平衡二叉搜索树)、红黑树、B+树 AVL树和红黑树适合内存存储应用,B+树适合磁盘存储应用。

  • AVL树和红黑树使用“旋转”保持平衡。AVL树每次插入操作最多需要一次旋转,每次删除操作最多需要O(log n)次旋转。红黑树插入和删除操作都只需一次旋转。一次旋转耗时O(1)。

  • AVL树:左子树和右子树都是AVL树;左右子树高度差小于等于1;旋转见代码

  • B+树:一次硬盘访问的时间,足够处理器执行40万条指令。因此我们往往愿意通过大量的指令计算来节省一次硬盘的访问。

    M阶B+树具有如下性质:

    • 数据项存储在树叶上。
    • 非叶节点存储关键字,以指示搜索的方向;关键字i代表子树i+1的最小关键字。
    • 除树根外,所有非树叶节点的儿子数在[M/2, M]之间。
    • 所有树叶都在相同的深度上,并有[L/2, L]个数据项,L看情况来定。

    新插入数据时,可能出现的情况是:

    ​ 想要插入数据项的那片树叶已经插满了,那就分裂一次上面的父节点;

    ​ 如果分裂上面的父节点时,父节点也超数了,那就继续向上分裂。

    B+树与B树的区别:

    ​ B树每个节点都存储数据,B+树只有叶子节点存储数据,非叶节点起索引作用。因此B+树更扁,I/O次数更少。

    ​ B+树所有叶子节点使用链表相连,便于区间查找和遍历。

Set、Map:

  • ArrayList和LinkedList查找效率都很低(非ArrayList指定索引查找元素的情况,而是直接给定某个元素的值,查看该元素详情的情况)。Collections API提供了Set和Map。
  • Set接口为不允许重复的Collection。保持有序状态的Set实现是TreeSet,底层红黑树。
  • Map同理于Set,TreeMap底层同样红黑树。

堆:

  • 堆是完全二叉树
  • 大根树:每个节点的值都大于等于其子节点
  • 大根堆:大根树 + 完全二叉树
    • 大根堆插入一个新元素,即在尾部插入一个新元素,执行一趟起泡操作即可维护。
    • 大根堆删除一个元素,即删除根节点元素,将尾部元素删除并放置在根节点位置,重新组织维护成大根堆。

PriorityQueue:

  • Java的优先级队列默认是小顶堆
  • 不允许放入null元素
  • 底层实现为数组,因为通过公式可计算出父子节点的对应位置,所以底层可用数组实现(迭代器遍历出来的结果顺序,就是下面数组的顺序)

PriorityQueue

  • 常用方法:

    • add()、offer() 插入元素 前者失败抛异常,后者返false

    • element()、peek() 获取但并不删除首元素 前者失败抛异常,后者返null

    • remove()、poll() 获取并删除首元素 前者失败抛异常,后者返null

    • remove(Object o) 删除o元素

    • iterator() 迭代器

​ 具体的方法实现:参考链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class CommandTest {

//多次使用就写成静态成员 单次使用写成匿名内部类更好
public static final Comparator<Integer> cmp = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//降序
return o2 - o1;
}
}

public static void main(String[] args) {
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(cmp);
priorityQueue.add(3);
priorityQueue.add(5);
priorityQueue.add(10);
priorityQueue.add(7);
priorityQueue.add(9);
priorityQueue.add(15);
priorityQueue.add(11);
priorityQueue.add(13);
priorityQueue.add(20);
priorityQueue.add(12);

while(!priorityQueue.isEmpty()) {
System.out.println(priorityQueue.poll());
}
}
}

Java常见容器

参考链接

iterator、ListIterator

  • List接口:

    • Vector类: 数组实现,支持线程同步,慢
      • Stack类: peek()、pop()、push()、search(Object o)
    • ArrayList类
    • LinkedList类
  • Set接口: 只能有一个null元素; 通过equals方法判断相等

    • HashSet类
    • LinkedHashSet类: 同时使用双向链表维护元素的添加次序,方便遍历
    • SortedSet接口:
      • TreeSet类
    • EnumSet类
  • Queue接口:

    • PriorityQueue类

    • Deque接口:双端队列

      • ArrayDeque类:

        双端队列 底层为循环数组 内部包含头指针和尾指针 线程不安全

        参考链接1 参考链接2

      • LinkedList类

  • Map接口:只能有一个null元素

    • HashMap类
    • LinkedHashMap类:同时使用双向链表维护元素的添加次序,方便遍历
    • HashTable类:古老的Map实现类
      • Properties类
    • SortedMap接口:
      • TreeMap类
    • WeakHashMap类
    • IdentityHashMap类
    • EnumMap类

最短路径算法、广度优先遍历、深度优先遍历