javaSE复习回顾(每日回顾)

1.面向对象基础

1.1 方法

1.1.1 方法重载:

  • 同一个类中,多个方法的方法名相同,参数列表不同,根据调用方法时的传参确定具体调用的哪个方法,构成方法重载。
    在这里插入图片描述

1.1.2 方法重写:

  • 在子类继承父类之后,对于父类中已有的方法(原方法不满足功能时)可以进行重写(覆盖),重写后的方法必须与父类中的方法的方法名返回值类型参数类型保持一致,只可以改变方法中的逻辑代码

1.2 类和对象

1.2.1 面向对象和面向过程

面向对象:虚拟世界模拟现实生活
面向过程:按照顺序逐步完成

1.2.2 类与对象关系:抽象与具体

  • : 同一类事物抽象描述
  • 对象:具体实例
  • 属性/成员变量:对象的特征
  • 方法:对象所做的事情
    具有相同属性和方法的对象组合成一类。比如定义实体类的时候,set/get
  1. 对象的内存分析在这里插入图片描述
    一次编写类、属性、方法、测试类(含有main方法的类,然后实例化一个Student类的对象。
步骤: main方法进栈,测试类StudentTest在方法区,执行new一个对象zs,这个对象存在堆中,栈中存放这个对象的地址。
  1. java内存划分在这里插入图片描述

1.2.3 成员变量和局部变量

  • 定义位置不同
  • 作用范围不同
  • 默认值不同(局部变量没有默认值)

1.2.4 封装

  • 把一段处理逻辑的代码封装成一个方法,调用时只需注意输入和输出,不用管如何实现
  • private加在实体类的对象属性前面,在该类外部,属性不能直接"."得到,必须使用setter/getter方法获取,可以在setter方法中写if判断,使其赋值合理,比如年龄不能赋值为负数

1.3 继承

1.3.1 参数传递

传递的参数为:

  • 基础数据类型:每次获取参数都是本身的值,改变之后不会对其他方法的引用产生影响
  • 引用数据类型:传递的只是这个参数的地址,改变属性值之后,其他方法引用时,属性值也发生改变,会有影响

1.3.2 static关键字

  1. 修饰成员变量:经过static修饰过的静态属性,被存在方法区中,被堆中的对象共享。
  • 好处:只需要开辟一片空间
static String name;
Student s1 = new Student();
Student s2 = new Student();
//s1、s2中的 name 是相同的,都是取的方法区中的


String name;
Student s1 = new Student();
Student s2 = new Student();
//s1、s2中的 name 不一定相同,取堆中相应对象中的属性值

1.3.3 继承

  1. 子类继承父类的属性和方法,然后再写自己独有的属性和方法。
  2. 减少代码的冗余性
  3. class A extends B
  4. 子类不能继承父类的构造方法
  5. new一个对象时,可以使用无参构造方法,然后setter给属性赋值(常用);也可以使用有参构造方法赋值(少用)
  • 例子:
    实体类:
public class Exer01 {

    String name;
    int age;

    //无参构造
    public Exer01() {
    }

    //有参构造
    public Exer01(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //setter/getter
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    //重写toString()方法---方便打印出对象详细内容,而不是只显示一个地址
    public String toString(){
        return "name:" + this.name + "  age:" + this.age;
    }
}

测试类:

public class ExerTest01 {

    public static void main(String[] args) {
        Exer01 e1 = new Exer01("xiaoming",18);	//使用有参构造方法赋值
        e1.setAge(19);
        System.out.println(e1.toString());	//name:xiaoming  age:19


        Exer01 e2 = new Exer01();
        e2.setName("xiaoguang");
        e2.setAge(30);
        System.out.println(e2.toString());	//name:xiaoguang  age:30
    }
}

1.3.4 this 和 super

  • this可以访问本类中的属性和方法,也可以访问其父类中的属性和方法
  • super只能在子类中使用,用来访问其父类中的属性和方法

1.3.5 实例化子类时,父类做了什么

  • 执行类的构造方法,执行类相匹配的构造方法
    1.子类构造方法中没有指定,先执行父类无参构造方法
    2.子类构造方法中指定了,先执行父类相应的构造方法
    子类、父类中构造方法的执行顺序

1.4 多态

1.4.1 定义

Person p = new Student();

1.父类引用指向子类对象
2. 优先调用子类重写以后的方法(子类必须重写父类方法)
3.方法调用总是作用于运行期对象的实际类型,允许添加更多类型的子类实现功能扩展,却不需要修改基于父类的代码。

1.4.2 好处

减少代码冗余性

1.4.3 条件

  1. 子类继承父类
  2. 子类重写父类方法
  3. 父类引用指向子类对象

1.4.4 多态中两种类型转换

  1. 向上转型–自动类型转换
//Dog转Animal
Animal animal = new Dog();

//或者
Dog dog = new Dog();
Animal animal = dog;
  1. 向下转型–强制类型转换
  • 先使用instanceof关键字判断
Animal animal = new Dog();
//Animal animal = new Cat();
if(animal instanceof Dog){
	Dog dog = (Dog)animal
	dog.play();
}
if(animal instanceof Cat){
	Cat cat = (Cat)animal
	cat.catching();
}

1.4.5 final 关键字

  1. final修饰的类不能被继承
  2. final修饰的方法不能重写
  3. final和static同时修饰的字段为常量,一般大写
    private static final String ENTITY_NAME = "phoneSalePoints";
    
  4. final修饰成员变量、局部变量初始化后不能修改。
  5. final修饰的形参不能在方法中赋值

1.5 抽象类和接口

1.5.1 abstract 抽象类

  1. 抽象方法:使用abstract修饰的方法, 只有方法的声明部分,没有方法体
  2. 子类继承了抽象类, 子类需要重写抽象类所有的抽象方法
  3. ==面向抽象编程:==上层代码只定义规范(例如:abstract class Person),具体的业务逻辑由不同的子类实现,调用者并不关心
public class Main {
    public static void main(String[] args) {
        Person p = new Student();
        p.run();
    }
}

abstract class Person {
    public abstract void run();	//抽象方法
}

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

1.5.2 interface 接口

抽象类与接口比较

abstract class interface
继承 只能extends一个class 可以implements多个interface
属性 可以定义成员变量 不能定义成员变量
抽象方法 可以定义 可以定义
非抽象方法 不能定义 可以定义default方法

default方法:实现这个接口的类不必须重写的方法,需要再重写

  • 接口和接口中的方法前面都省略了abstract

2. java核心类

2.1 字符串

  • Java字符串String是不可变对象,字符串操作不改变原字符串内容,而是返回新字符串;

2.1.1 比较使用==和equals

  • 在编译期会把所有相同的字符串当作一个对象放在常量池中
  • == 是直接比较的两个对象的堆内存地址(比较的是两个引用类型的变量是否是同一个对象)
  • equalsequalsIgnoreCase方法-忽略大小写比较)判断的是具体内容
//编译期"hello"和"HELLO"都在常量池中,"HELLO".toLowerCase()之后返回的新字符串"hello"放在堆中,地址不同
String s1 = "hello";
String s2 = "HELLO".toLowerCase();
System.out.println(s1 == s2);	//false
System.out.println(s1.equals(s2));	//true

2.1.2 String类提供的常用方法

// 是否包含子串:	contains()
"Hello".contains("ll"); // true

//搜索子串
"Hello".indexOf("l"); // 2
"Hello".lastIndexOf("l"); // 3
"Hello".startsWith("He"); // true
"Hello".endsWith("lo"); // true

//提取子串	substring()
"Hello".substring(2); // "llo"
"Hello".substring(2, 4); "ll"

//去除首尾空白	trim() 和 strip()
"  \tHello\r\n ".trim(); // "Hello"			不可去除中文空格字符\u3000
"\u3000Hello\u3000".strip(); // "Hello"		可去除中文空格字符\u3000
" Hello ".stripLeading(); // "Hello "
" Hello ".stripTrailing(); // " Hello"

//是否空	isEmpty() 和 isBlank()
"".isEmpty(); // true,因为字符串长度为0	
"  ".isEmpty(); // false,因为字符串长度不为0	是否为空
"  \n".isBlank(); // true,因为只包含空白字符	是否为空白字符串
" Hello ".isBlank(); // false,因为包含非空白字符

//替换子串	replace()
"Hello".replace('l', 'w'); // "hewwo",所有字符'l'被替换为'w'
"Hello".replace("ll", "~~"); // "he~~o",所有子串"ll"被替换为"~~"

//分割(结果是数组)	split()
String s = "A,B,C,D";
String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}

String s = "A^B@C#D";
String[] ss = s.split("\\^|@|#"); // {"A", "B", "C", "D"}

//拼接字符串	join()
String[] arr = {"A", "B", "C"};
String s = String.join("***", arr); // "A***B***C"

//类型转换
//别类型转字符串
String.valueOf(123); // "123"
String.valueOf(45.67); // "45.67"
String.valueOf(true); // "true"
String.valueOf(new Object()); // 类似java.lang.Object@636be97c
//字符串转int
int n1 = Integer.parseInt("123"); // 123
int n2 = Integer.parseInt("ff", 16); // 按十六进制转换,255
//字符串转Boolean
boolean b1 = Boolean.parseBoolean("true"); // true
boolean b2 = Boolean.parseBoolean("FALSE"); // false

2.1.3 StringBuilder

  • 在拼接字符串时,每次循环都会创建新字符串对象,扔掉旧的字符串对象,很多都是临时对象,浪费内存。使用StringBuilder会分配缓冲区,不会创建临时对象,只输出最终的
StringBuilder sb = new StringBuilder(1024);
sb.append("Mr ")
   .append("Bob")
   .append("!")
   .insert(0, "Hello, ");
System.out.println(sb.toString());
  • StringBuilder是可变对象,用来拼接字符串。支持链式操作,每次返回本身this,然后接着往后追加。
  • StringBuffer线程更安全,但效率低,极少用。

2.1.4 StringJoiner

  • 用分隔符拼接数组成字符串,可添加首尾
String[] names = {"Bob", "Alice", "Grace"};
StringJoiner s = new StringJoiner(",","Hello ","!");
for (String name : names) {
s.add(name);
}
System.out.println(s.toString());	//Hello Bob,Alice,Grace!
  • 若不用添加首尾,直接使用String.join()
String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(", ", names);	//Bob,Alice,Grace

2.1.5 包装类型

  • int 基本类型数据转成引用类型数据IntegerInteger类就可以视为 int 的包装类。
  1. java核心库为每种基本类型都提供了对应的包装类型:
基本类型 对应的引用类型
boolean java.lang.Boolean
byte java.lang.Byte
short java.lang.Short
int java.lang.Integer
long java.lang.Long
float java.lang.Float
double java.lang.Double
char java.lang.Character
  1. 装箱、拆箱 和 自动装箱、自动拆箱
  • 都发生在编译阶段
int i = 100;
Integer n = Integer.valueOf(i);	//装箱
int x = n.intValue();	//拆箱

------------------------------------------------------

Integer n = 100; // 自动装箱:编译器自动使用Integer.valueOf(int)
int x = n; // 自动拆箱:编译器自动使用Integer.intValue()
  1. 自动拆箱可能会报空指针异常NullPointerException
Integer n = null;
int i = n;
  1. 包装类型是引用数据类型,比较必须使用equals()
  2. 整数和浮点数的包装类型都继承自Number
// 向上转型为Number:
Number num = new Integer(999);
// 获取byte, int, long, float, double:
byte b = num.byteValue();
int n = num.intValue();
long ln = num.longValue();
float f = num.floatValue();
double d = num.doubleValue();

2.1.6 JavaBean

  • JavaBean是一种符合命名规范的class,它通过gettersetter来定义属性
  • 规范:
    若干private实例字段;
    通过public方法来读写实例字段
public class Person {
    private String name;
    private int age;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return this.age; }
    public void setAge(int age) { this.age = age; }
}

2.1.7 枚举类

public class Main {
    public static void main(String[] args) {
        Weekday day = Weekday.SUN;
        if (day == Weekday.SAT || day == Weekday.SUN) {
            System.out.println("Work at home!");
        } else {
            System.out.println("Work at office!");
        }
    }
}

enum Weekday {
    SUN, MON, TUE, WED, THU, FRI, SAT;
}
  • 编译之后
//编译之后
public final class Weekday extends Enum { // 继承自Enum,标记为final class
    // 每个实例均为全局唯一:
    public static final Weekday SUN = new Weekday ();
    public static final Weekday MON = new Weekday ();
    public static final Weekday TUE = new Weekday ();
    // private构造方法,确保外部无法调用new操作符:
    private Weekday () {}
}
  • 使用 name() 返回常量名
String s = Weekday.SUN.name(); // "SUN"

2.1.8 Math类

Math类就是用来进行数学计算的,比如:绝对值、最大值、最小值、三角函数、对数等

  • 其中生成一个0 <= x < 1的随机数
Math.random(); // 0.53907... 每次都不一样

3. 集合

  • 存放对象的容器
  • 集合分为单列集合(Collection)和双列集合(Map)

3.1 集合与数组对比

数组 集合
长度固定 长度可变
存放基本类型数据和对象 存放对象,基本类型数据会自动装箱
{1, 2, 3} [1, 2, 3]
  • 字符串与对象区别:
String x =  “abcd”
String y = new String(“zbcd”);
X 指向的是方法区中的字符串常量对象。
Y 指向的是堆中的字符串对象,在堆中所产生的字符串对象,必须以方法区中的字符串常量对象为模板,将字符串常量对象的内容复制到堆中。

所以 new String(“abcd”) 有可能产生两个对象,一个在堆中,一个在方法区中。

3.2 集合类的继承关系

  • list: 有序、可重复、有索引
  • set: LinkedHashSet有序、不重复、无索引
  • 以下图片均为转载
    在这里插入图片描述
    在这里插入图片描述

3.3 Collection接口 和 Collections类

  • Collection接口是单列集合最顶层的接口,定义了所有单列集合共性的方法,任意单列集合都可以使用
      最前面是返回值类型
  ●   boolean add(Object e) 	把给定的对象添加到当前集合中
  ●   void clear() 				清空集合中所有的元素 
  ●   boolean remove(Object o) 	把给定的对象在当前集合中删除 
  ●   boolean contains(Object o) 判断当前集合中是否包含给定的对象 
  ●   boolean isEmpty() 	判断当前集合是否为空 
  ●   Iterator iterator() 	迭代器,用来遍历集合中的元素的 
  ●   int size() 			返回集合中元素的个数 
  ●   Object[] toArray() 	把集合中的元素,存储到数组中 
  ●   Iterator :  			迭代器 
  ●   Object next()			返回迭代的下一个元素 
  ●   boolean hasNext()		如果仍有元素可以迭代,则返回 true。

ArrayList有的:
  ●   boolean addAll(Collection) 	把一个集合元素都添加到当前集合中  
  ●   Object get()		获取指定位置的对象
  ●   void set(index,Object)	替换
  ●   int indexOf()		获取对象所处的位置
  • Collections类是容器的工具类,就如同Arrays是数组的工具类。
方法名称 实例
反转 Collections.reverse(numbers);
混淆 Collections.shuffle(numbers);
排序 Collections.sort(numbers);
交换 Collections.swap(numbers,0,5);
滚动 Collections.rotate(numbers,2); 例:[1, 2, 3, 4, 5]–>[4, 5, 1, 2, 3 ]
非线程安全转线程安全 Collections.synchronizedList(numbers);

3.4 ArrayList和List

  • 源码
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList实现了java.util.List接口

List heros = new ArrayList();	//多态,接口引用指向子类对象

3.5 泛型 Generic

  • 不指定泛型的容器,可以存放任何类型的元素
  • 指定了泛型的容器,只能存放指定类型的元素以及其子类
List genericheros = new ArrayList();
List<Hero> genericheros = new ArrayList<Hero>();
  • 使用泛型,可以在一个ArrayList中放两种不同的对象,其余对象都不能放(题目)。
创建一个父类,让两个类都继承,然后ArrayList指定泛型为这个父类
//让 Student类、Teacher类都继承 Person类
List<Person> people = new ArrayList<Person>();
people.add(new Student("zhangsan",18));
people.add(new Teacher("wanglaoshi"));
for (Person everyone : people) {
    System.out.println(everyone );
}

3.6 三种循环

3.6.1 迭代器 Iterator

  • 原理:Collection集合中元素的通用获取方式。取元素前先判断有没有,有则取;再判断,再取;直到把所有元素取出来
Collection<String> list = new ArrayList<>(); //创建集合
Iterator<String> it = list.iterator();  //使用iterator()方法获取迭代器对象.
while(it.hasNext()){  // hasNext()判断是否有下一个元素
     String str = it.next();  // 取出集合中下一个元素.
}
  • 使用迭代器 remove集合中的对象
//迭代器删除Integer
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);

Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    Integer a = it.next();
    if (a > 3) {
        it.remove();
    }
}

for (Integer i : list) {
    System.out.print(i);
}
// 结果:1 2 3

3.6.2 for 循环

  • 注意:
  1. 数组有length属性
  2. String有 length( ) 方法
  3. 集合使用 size( ) 方法
//1.
int[] a={1,2,3};
System.out.println(a.length);

//2.
String s="hahah";
System.out.println(s.length());

//3.
for (int i = 0; i < heros.size(); i++) {
   Hero h = heros.get(i);
    System.out.println(h);
}

3.6.3 增强型 for 循环

  • 不体现索引
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
for (Integer i : list) {
	System.out.println(i);
}

3.7 LinkedList

3.7.1 概念 FIFO 和 FILO

FIFO:先进先出,在Java中又叫 Queue 队列 
FILO:先进后出,在Java中又叫 Stack 栈

3.7.2 双向链表( 双端队列 ) Deque

  • LinkedList除了实现List接口外,还实现了双向链表结构Deque,方便在头尾插入和删除数据
  • 源码:
public class LinkedList<E>
     extends AbstractSequentialList<E>
     implements List<E>, Deque<E>, Cloneable, java.io.Serializable
public interface Deque<E> extends Queue<E> {
  • 链表结构: 与数组结构相比较,数组结构,就好像是电影院,每个位置都有标示,每个位置之间的间隔都是一样的。 而链表就相当于佛珠,每个珠子,只连接前一个和后一个,不用关心除此之外的其他佛珠在哪里。
  • 举例:
    注意:双向链表主要是使用Deque接口,所以创建对象的时候可以使用Deque接口引用指向子类LinkedList对象。不能使用List接口,否则需要强转。
//链表结构
LinkedList<Arms> ll = new LinkedList<Arms>();
 //Deque可以
 //Deque<Arms> ll = new LinkedList<Arms>();
 
 //List需强转
 //List<Arms> ll1 = new LinkedList<Arms>();
 //((LinkedList<Arms>) ll).addLast(new Arms("duolanjian"));

 ll.addLast(new Arms("duolanjian"));
 ll.addLast(new Arms("duolandun"));
 ll.addFirst(new Arms("duolanjie"));
 for (Arms everyArms : ll) {
     System.out.println(everyArms);
 }
 // name:duolanjie
 // name:duolanjian
 // name:duolandun
 
 ll.removeFirst();
 // name:duolanjian
 // name:duolandun
 ll.removeLast();
 // name:duolanjian

3.7.3 队列 Queue

  • 常用方法
    offer 在最后添加元素
    poll 取出第一个元素
    peek 查看第一个元素
//队列
//offer 在最后添加元素
//poll 取出第一个元素
//peek 查看第一个元素
Queue<Arms> q = new LinkedList<Arms>();
q.offer(new Arms("pobai"));
q.offer(new Arms("lvcha"));
q.offer(new Arms("huopao"));
Arms p = q.peek();  //查看	name:pobai
q.poll();   //取	[name:lvcha, name:huopao]
System.out.println(p);
System.out.println(q);

3.7.4 队列 Queue 和 双端队列 Deque 对比

队列 双端队列
Queue Deque
add addFirst / addLast (下同)
offer offerFirst 添加
remove removeFirst 删除首位(集合为空抛异常)
poll pollFirst 取出首位(集合为空为null)
element getFirst
peek peekFirst 查看
  • 使用LinkedList实现Stack栈(先入后出)
/**
 * ClassName:MyStack
 * Description:使用LinkedList实现Stack栈
 */
public class MyStack {
    private static LinkedList<Arms> list = new LinkedList<>();

    //进栈方法
    public void push(Arms a){
        list.offerLast(a);
    }

    //出栈方法
    public void pull(){
        list.pollLast();
    }

    public static void main(String[] args) {

        MyStack stack = new MyStack();
        System.out.println("当前栈内元素:"+ list);
        for (int i = 0; i < 5; i++) {
            stack.push(new Arms("arms" + i));
            System.out.println("入栈" + list);
        }

        for (int i = 0; i < 5; i++) {
            stack.pull();
            System.out.println("出栈" + list);
        }
    }
}
运行结果:
当前栈内元素:[]
入栈[name:arms0]
入栈[name:arms0, name:arms1]
入栈[name:arms0, name:arms1, name:arms2]
入栈[name:arms0, name:arms1, name:arms2, name:arms3]
入栈[name:arms0, name:arms1, name:arms2, name:arms3, name:arms4]
出栈[name:arms0, name:arms1, name:arms2, name:arms3]
出栈[name:arms0, name:arms1, name:arms2]
出栈[name:arms0, name:arms1]
出栈[name:arms0]
出栈[]

3.7.5 ArrayList 和 LinkedList 的对比

ArrayList LinkedList
定位(查)
插入删除数据(增删)
底层 数组 双向链表
  • ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。
  • LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢
比较 ArrayList和LinkedList, 在最后面插入100000条数据,谁快谁慢?
答:一样快。因为是插入在最后面,所以对于数组而言,并没有一个移动很多数据的需要,所以也非常的快
在List的中间位置,插入10000条数据,比较ArrayList快,还是LinkedList快,并解释为什么?
答:在这个位置插入数据时
	数组定位很快,插入数据比较慢
	链表定位很慢,插入数据比较快
	最后发现,当总数是10000条的时候,链表定位的总开支要比数组插入的总开支更多,所以最后整体表现,数组会更好。

3.8 二叉树

3.8.1 概念

二叉树由各种节点组成

  • 特点:
    每个节点都可以有左子节点,右子节点
    每一个节点都有一个值

3.8.2 排序插入数据

  • 示意图来自:how2j.cn
使用二叉树对 {67,7,30,73,10,0,78,81,10,74} 排序
  • 原理:小、相同的放左边,大的放右边
1. 67 放在根节点
2. 7 比 67小,放在67的左节点
3. 30 比67 小,找到67的左节点7,30比7大,就放在7的右节点
4. 73 比67大, 放在67的右节点

在这里插入图片描述

  • 使用二叉树排序,组装成List形式输出从小到大的一组数据
public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;

    // 值
    public Object value;

    // 插入 数据
    public void add(Object v) {
        // 如果当前节点没有值,就把数据放在当前节点上
        if (null == value) {
            value = v;
		}
        // 如果当前节点有值,就进行判断,新增的值与当前值的大小关系
        else {
            // 新增的值,比当前值小或者相同
            if ((Integer) v -((Integer)value) <= 0) {
                if (null == leftNode) {
                    leftNode = new Node();
                }
                leftNode.add(v);
            }
            // 新增的值,比当前值大
            else {
                if (null == rightNode) {
                    rightNode = new Node();
                }
                rightNode.add(v);
            }
        }
    }

    // 中序遍历所有的节点组成常见的List形式
    public List<Object> values() {
        List<Object> values = new ArrayList<>();
        // 右节点的遍历结果
        if (null != rightNode) {
            values.addAll(rightNode.values());
		}
        // 当前节点
        values.add(value);
        // 左节点的遍历结果
        if (null != leftNode) {
            values.addAll(leftNode.values());
        }
        return values;
    }

    public static void main(String[] args) {
        int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
        Node roots = new Node();
        for (int number : randoms) {
            roots.add(number);
        }
        System.out.println(roots.values());
        //[0, 7, 10, 10, 30, 67, 73, 74, 78, 81]  先遍历左节点为顺序
        //[81, 78, 74, 73, 67, 30, 10, 10, 7, 0]  先遍历右节点为倒序
    }
}
  • 二叉树刚插完数据的结构
    在这里插入图片描述

3.8.3 选择法、冒泡法、二叉树 三种排序性能区别

选择法:	第一个与之后每一个比较,比其小交换位置
冒泡法:	两个相邻的比较,左大右小时,交换位置
二叉树:	第一个数放中间,小、相同的放左边,大的放右边
  • 结论:二叉树最快
初始化一个长度是40000的随机数字的数组
初始化完毕
接下来分别用3种算法进行排序
选择法排序,一共耗时 6865 毫秒
冒泡法排序,一共耗时 4388 毫秒
二叉树排序,一共耗时 84 毫秒

3.9 HashMap、Hashtable、HashSet 和hashcode

3.9.1 HashMap

  • HashMap属于双列集合,储存数据的方式是:键值对,键不能重复,值可重复。(若键重复,后面的值会覆盖前面的值)可以存放null,线程不安全。

3.9.2 Hashtable

  • HashtableHashMap,但不能存放null,线程安全。

3.9.3 HashSet

  • HashSet同属于单列集合,无序不可重复。
HashSet自身并没有独立的实现,而是在里面封装了一个Map.
HashSet是作为Map的key而存在的
而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。
private static final Object PRESENT = new Object();

3.9.4 hashcode

  • hashcode相当于字典的页码,先确定大概位置,再详细对比。
    比如有一个对象,hashcode值为1001,保存时,存在一个数组1001的索引位置上,如果该位置没有其他对象,直接保存;如果已经有其他对象,就创建一个链表往后追加。
    查询时,直接用hashcode定位到该索引位置,然后依次使用equals对比才位置对象。比起list全部循环一遍查找,大大提高了查询效率。
    示意图来自:how2j
    在这里插入图片描述

3.10 HashSet、LinkedHashSet 、TreeSet

  • HashSet: 无序
  • LinkedHashSet: 按照插入顺序
  • TreeSet: 从小到大排序
  • 都不重复
发布了23 篇原创文章 · 获赞 0 · 访问量 2659

猜你喜欢

转载自blog.csdn.net/xy405580364/article/details/103113791