原创声明
本文作者:泉幽
转载请务必在文章开头注明出处和作者。
-
基础环境搭建
作为刚入门的小白如何快速进入java初级开发?我们首先需要通过系统的环境变量搭建以下环境,以及准备以下基础工具(任选),可自行去相关官网下载:
核心:java,jdk,jre(三者必备)
注:jdk包含jre,是开发工具包;jre(Java Runtime Environment),是运行环境。
数据库:MySql/Orcale/PLSQL...
数据库连接工具:MySql Connector Java...
数据库可视化管理工具:Navicat/DataGrip/DBeaver...
开发工具:Idea/Eclipse/MyEclipse...
服务器:Tomcat...
包管理:Maven...
画类的关系图工具:Rational Rose...
构建用户需求工具:UMl(用列图)...
图1.1 Java环境配置
图1.2 JDK环境配置
图1.3 MySql数据环境配置
图1.4 数据库连接java环境配置
图1.5 Tomcat环境配置
图1.6 Maven环境配置
图1.7 Maven配置文件设置仓库地址
图1.8 Idea中设置Maven路径
通过以上的环境搭建与配置,我们已经可以进行初步的java项目开发。当然以上只是后端配置,如果是涉及现在流行的前后端分离机制,前端使用Vue,后端使用SpringBoot,我们还需要在Idea中进行相关的配置。
-
基础语法
标识符 |
数字,字母,下划线,$ 首位不能是数字 。并且 Java关键字不能作为标识符 。 Unicode 字符集中符号大于 0xC0 的所有符号组合构成(各符号之间没有空格) |
变量 |
数据类型、变量名、变量的值;
成员变量:方法体外,类体内; 实例变量:new对象,存在方法区,引用访问。 局部变量:方法体中,方法结束后该变量的内存就释放了; |
数据类型 |
数值型: byte,short,int,long,float,double
类:class |
运算符 | 以下根据优先级自上而下,由左到右排序。
()、【】、++、--、*、/、%、+、-
<、>、<=、=<
<<、>>、>>>(优先级高关系运算符)==、!=、&、^、|、〜、&&、||、?::
*=、/=、%=、+=、-=、<<=、 >>= 、>>>=、 &=、 ^=、 |= |
控制语句 | 循环语句:if else,swith case(使用整数表达式/枚举常量),for,while,do while 结束语句:break,continue,return |
-
基础jvm
-
基本概念:JVM 是可运行 Java 代码的假想计算机 (虚拟机),包括一套字节码指令集、一组寄存器、一个栈、一个堆(垃圾回收)和一个存储方法域。
图3.1 简易JVM图
1, 栈帧 ,随着方法调用而创建,随着方法结束而销毁,即为压栈入栈,出栈。
2, 堆内存 中,存储了new出来的对象,数组,也是垃圾回收(含有新生代,老生代,其中新生代又细分为,Eden区/ServivorForm区/ServivorTo区)的重要区域。最终通过MinorGC 采用复制算法进行垃圾回收(复制—>清空—>互换)。
3, 方法区 中,存储了jvm的类加载机制,编译后的代码数据,常量池(String/class/runtime),静态变量等等。
注: 方法区,堆 是线程共享数据。 栈 是线程私有数据。
-
运行过程:JVM 是运行在操作系统之上的,它与硬件没有直接的交互。java源文件,通过编译生字节码文件(.class),再通过jvm中解释器编译成机器码。
① Java 源文件—-> 编译器—->字节码文件
② 字节码文件—->JVM中 解释器—->机器码
注:每一种平台的 解释器 是不同的,但是实现的虚拟机(jvm)是相同的,这也就是 Java 为什么能够跨平台的原因。
-
基础java
-
基础
-
4.1.1面对对象基本概念
-
面对对象注重的是对象,将现实世界细分为每一个单元,每个单元就是对象,着重与对象与对象之间的互动。例如:java语言
-
面对过程注重的是过程,适用于小型项目,着重实现的每一个步骤,步骤的顺序等。例如:C语言
-
C++属于半面对对象。
-
4.1.2面向对象设计原则
单一职责原则 | 一个对象单封装一个类,功能单一 |
OCP开闭原则 | 对扩展开放,对修改关闭 |
里氏代换原则 | 所有引用基类的地方,必须能够透明,使用其子类对象 |
依赖倒转原则 | 高层模块,不依赖,底层模块,都依赖抽象 |
接口隔离原则 | 客户端,不依赖,不需要的接口 |
合成复用原则 | 优先使用对象,其次继承 |
迪米特法则 | 软件单位,知识局限于相关,软件单位 |
注:面向抽象编程,不要面向具体。
-
4.1.3面对对象三大特征
-
封装(隐藏内部代码):
-
私有化private,提供set/get。
-
继承(复用现有代码):
-
继承类
1)类和类之间只支持单继承。
2)构造方法不能被继承。
3)私有属性不能在子类中直接访问。
-
继承抽象类
1)抽象类中不一定有抽象方法。
2)抽象方法必须出现在抽象类中。
3)抽象类中有构造方法,但只用于初始化变量,而不是创建对象。
-
实现接口
1)类与接口可以多实现(implements),接口与接口可以多继承(extends)。
2)接口是完全抽象的,没有构造方法,无法实例化(只有常量/抽象方法)。
3)接口中所有的数据都是public修饰的(public static final/public abstract可省略)。
4)一个非抽象的类实现接口必须将接口中所有的方法加以实现。
注:事物的本质的时候,用抽象类;事物操作的时候,用接口。
-
多态(改写对象行为):
-
接口通过子类创建对象。
-
向上转型(upcasting):子转换成父,又叫做自动类型转换。
-
向下转型(downcasting):父转换成子,又叫做强制类型转换,需要加强制类型转换符。
注:多态在开发中的作用:降低程序耦合度,提高程序扩展力。
典型的多态代码是父类型引用指向子类型对象,程序分编译阶段和运行阶段,编译阶段绑定的是父类中的方法(父类中没有这个方法,编译器会报错),但运行阶段和堆内存中实际对象有关,运行的时候绑定的是实际对象的方法。
什么情况下必须做向下转型:当访问子类中特有方法的时候。
instanceof运算符:boolean = 引用 instanceof 类名,为ture时候,就进行强转。
-
4.1.4类和类之间的关系
-
泛化关系(A is a B):类之间继承,接口之间继承。
-
实现关系 (A like B):类实现接口。
-
关联关系 (A has B)
-
单向关联:A含有B引用。
-
双向关联:A含有B引用,B含有A引用。
-
聚合关系:特殊的关联关系,整体不决定部分的生命周期(比如班级---->List<学生>)。
-
组合关系:特殊的关联关系,整体决定部分的生命周期(比如人---->List<肢体>)。
-
依赖关系:A类与A方法中局部变量(B类的引用)关系。
-
4.1.5访问控制权限修饰符
public | 任何位置都可访问 |
protected | 其他包子类可访问 |
缺省 | 在同一个包下可以访问 |
private | 只能在本类中访问 |
-
4.1.6方法重载/方法重写
定位 | 方法名 | 参数 | 返回类型 | |
方法重载 | 同一个类中 | 相同 | 不同 | 无关 |
方法重写 | 继承关系中,方法体不同 | 相同 | 相同 | 相同 |
(1)重写的方法不能比被重写的方法拥有更低的访问权限;
(2)重写的方法不能比被重写的方法抛出更宽泛的异常;
(3)私有的方法无法重写;
(4)构造方法无法继承,无法重写;
(5)静态的方法不存在重写,会被隐藏;
注意:返回Object类,重写时可以返回任何属于Object的类型。
-
4.1.7关键字this/super/static
-
this概念:存储在堆内存的对象内部,代表本类对象引用。
-
this的使用:
①this不能使用在静态方法中(静态方法存在方法区中没有对象)。
②通过本类引用调用父类方法(方法没被重写)。
③this()只能出现在构造方法第一行,通过本类引用调取本类中其他构造方法。
-
super概念:不是引用,也不保存内存地址,也不指向任何对象,代表当前this对象的父类特征。
-
super的使用:
①super不能使用在静态方法中(super是this的一部分)。
②super.属性名:通过父类引用调用父类中属性。
③super.方法名():通过父类引用调用父类中被重写的方法。
④super():只能出现在构造方法第一行,通过父类引用调取父类的构造方法。
注意:super运行原理,类加载(静态代码块/静态变量)—>new方法调用(压栈)—>调用构造方法中super()。
-
static关键字:
静态代码块 | static{ } |
静态变量 | 运行时和堆内存中的对象无关,类名访问 |
静态方法 | 运行时和堆内存中的对象无关,类名访问 |
构造代码块 | 每一次new对象都会执行 |
注意:执行顺序:父类静态—子类静态—父类实例—父类构造—子类实例—子类构造。
public class TestStatic {
public static void main(String[] args) {new TestB();}
}
class TestA {
static {System.out.println( "父类--静态代码块" );}
public TestA() {System.out.println( "父类--构造函数" );}
{System.out.println( "父类--非静态代码块" );}
}
class TestB extends TestA {
static {System.out.println( "子类--静态代码块" );}
{System.out.println( "子类--非静态代码块" );}
public TestB() {System.out.println( "子类--构造函数" );}
}
/*输出结果:
父类--静态代码块
子类--静态代码块
父类--非静态代码块
父类--构造函数
子类--非静态代码块
子类--构造函数
-
4.1.8 final、finalize、finally
-
final:是java关键字,被修饰的数据不可变。
(1)final修饰的类无法被继承,无法修饰抽象类。
(2)final修饰的方法无法被覆盖,但可以访问。
(3)final修饰的变量,一旦赋值不可重新赋值(引用变量不能变)。
如:final String a = new String(...);a = new String(...) 输出结果:错误
(4)final修饰的实例变量必须手动赋值。
(5)final修饰的实例变量一般和static联合使用,称为常量。
-
finalize:是Object类中方法名,该方法在对象,被回收时被垃圾回收器调用(通常情况内存足够,不会被调用)。
-
finally:无论是否抛出异常,finally代码块总是会被执行。主要用来释放资源,比如:I/O缓冲区,数据库连接。
-
4.1.9 常用的包
java.awt包 | 提供了绘图和图像类,主要用于编写GUI程序,包括按钮、标签等常用组件以及相应的事件类。 |
java.lang包 | 核心包,默认导入到用户程序,包中有object类,数据类型包装类,数学类,字符串类,系统和运行时类,操作类,线程类,错误和异常处理类,过程类。 |
java.io包 | 包含提供多种输出/输入功能的类。 |
包含执行与网络有关的类,如URL,SCOKET,SEVERSOCKET等。 | |
java.applet包 | 包含java小应用程序的类。 |
java.util包 | 包含集合框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组、日期Date类、堆栈Stack类、向量Vector类等)。集合类、时间处理模式、日期时间工具等各类常用工具包。 |
java.sql包 | 提供使用 JavaTM 编程语言访问并处理存储在数据源(通常是一个关系数据库)中的数据的 API。 |
java.math包 | 提供数学类,例如BigDecimal类(调用add()求和,具有高精度性) |
-
4.1.10 String
String默认对象存储在方法区的String常量池中(不可变),new出来的String对象存储在堆中,详细请看这篇文章:String存储位置。
图7.1 String的原理
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = new String("hello");
int arr1[] = {1,2};
int arr2[] = {1,2};
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
System.out.println(arr1.equals(arr2));
}
//结果
//false(引用类型,故而比较的是地址不相等)
//true(String对equals重写,故而比较的是值相等)
//false(引用类型,数组没有对equals重写,故而比较的是地址不相等)
//问:以上String创建了几个对象?
//答:3个对象,对象S1,S2存储在方法区String常量池中;“hello”被new出来储存在堆中;
-
4.1.11 StringBuilder
动态字符串,基础容量:16;非线程安全
图7.2 StringBuilder源码
-
4.1.12 StringBuffer
动态字符串,基础容量:16;添加了锁synchronized,线程安全(效率不高)
图7.3 StringBuffer源码
-
4.1.13 Integer包装类
-
包装类的值都是final 不可变的,存储在方法区,对应的常量池中。
-
Integer包装类通过IntegerCache类,定义范围,在-128~127范围内则返回cache这个数组中的值,反之创建一个对象。
Integer a1 = 127; Integer a2 = 127; System.out.println(a1 == a2);
Integer b1 = 128; Integer b2 = 128; System.out.println(b1 == b2);
//结果
//true,127在范围内,integer是不变的,故而内存地址,值都一样。
//false,128不在范围内,则创建一个对象,故而内存地址依然不变,但是值不一样。
int——>Integer | 例如:Integer i = 10; | 自动装箱:将基本数据类型自动转换成对应的包装类 |
Integer——>int | 例如:int a = i.intValue(); | 自动拆箱:将包装类自动转换成对应的基本数据类型 |
注:int/Integer转换成String,使用valueOf(),toString()方法;String转换成int/Integer,使用valueOf(),parseInt()方法。
-
4.1.14 java中传递方式
-
值传递(不会改变本身,只是传递拷贝数值):例如基本数据类型,以及String创建字符串等。
-
引用传递(实参引用传给形参,通过形参找地址进行改变):例如数组,对象,除String以外。
public class TestArgs {
public static void main(String[] args) {
int A = 666;
int B[] = {11,22,33};
System.out.println("值传递初始值:"+A+"引用传递初始值:"+B[0]);
m1(A);
m2(B);
System.out.println("值传递,结果:"+A+"引用传递,结果:"+B[0]);
}
public static void m1(int A){
A=999;//赋值
System.out.println("m1执行了,值没有改变");
}
public static void m2(int[] B){
B[0]=100000;//赋值
System.out.println("m2执行了");
}
}
/*输出结果:
值传递初始值:666引用传递初始值:11
m1执行了,值没有改变
m2执行了
值传递,结果:666引用传递,结果:100000
*/
-
4.1.15 java中比较方式
数据类型 | 引用类型 | |
== | 比较值 | 比较地址 |
equals | 无 | 比较地址 |
Comparable | 实现Comparable接口,重写CompareTo/Compara方法,自定义比较 |
注:String底层对equals,toString进行了重写(比较内容)。
-
4.1.16 java中创建对象方式
通过 new对象,调用构造方法。
运用 克隆手段,调用对象的clone()方法。
运用 反射手段,调用java.lang.Class,或者java.lang.reflect.Constructor类中的newInstance()实例方法。
运用 反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。
-
异常
-
Error:java运行时,系统内部错误/内存耗尽;不会抛出类对象,程序安全终止。
-
Exception:
-
方法不能按照正常的途径完成任务,会抛出一个封装了错误信息的对象。此时,方法会立刻退出,且不执行代码,同时不返回任何值。
-
异常处理机制会将代码执行交给异常处理器,在下一个垃圾回收过程中被回收掉。
-
-
RuntimeException(运行时异常)
1.数组索引越界异常: ArrayIndexOutOfBoundsException。
2.空指针异常 : NullPointerException。
3.类型转换异常:ClassCastException。
4.迭代器遍历没有此元素异常:NoSuchElementException。
5.数学操作异常:ArithmeticException。
6.数字转换异常: NumberFormatException。
-
CheckedException(检查时异常)
1、输入输出异常:IOException。
2、文件不存在异常:FileNotFoundException。
3、SQL语句异常:SQLException。
注:空值(NULL)代表了所有未初始化的对象,因此空值可以被分配为任何类型(java不报错,但运行时报错,空指针异常)
throws | 类级别,声明可能发生的异常 |
throw | 成员级别,抛出具体的问题对象(异常) |
try...catch | ———— |
try...finally | ———— |
try...catch...finally | ———— |
注:finally在return语句之后执行,但返回值是在finally执行前return确定的。
-
反射
-
概念:能被任意类,动态获取信息以及动态调用对象方法称为,反射。
-
作用:编译时无法预知对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射。
-
例如:映射class,实例化对象,通过反射访问对象的Field,Method。大多用于反射接口,调用接口实现类的方法。
-
获取class的三种方式
第一种:
Class c = Class.forName("完整类名带包名");
Class c = Class.forName("全限定类名");//c是一个引用,保存内存地址指向方法区中的字节码;
第二种:
Class c = 类型名.class;
第三种:
Class c = 引用.getClass();
-
注解
-
概念:Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation
对象,然后通过该 Annotation 对象来获取注解中的元数据信息。分别为标准注解,元注解。
-
四种标准元注解:
-
@Target:声明对象范围
-
types(包、类、接口、枚举、Annotation 类型);
-
类型成员(方法、构造方法、成员变量、枚举值);
-
方法参数;
-
本地变量(如循环变量、catch 参数);
-
@Retention:定义被保留的时间长短。
-
@Documented:定义为公共API,使其成为javadoc工具文档化。
-
@Inherited:阐述了某个被标注的类型是被继承的(字父同有该注解类)。
-
泛型
-
概念:将类<T>/方法<E>/通配符<?>定义为同一个类型参数变量,本质是参数化类型。
-
作用:能够编译时,具有类型安全检测机制。
-
内部类
定义在类内部的类,称为内部类,根据位置不同,共有四种内部类。
静态内部类 | 概念:定义在类内部的静态类。 作用:①可以访问外部类所有的静态变量和方法,即使是 private 的也一样; ②外部使用时,通过外部类.静态内部类的方式来new实例; 场景:和外部类关系密切的,且不依赖外部类实例 |
成员内部类 | 概念:定义在类内部的非静态类。 |
局部内部类 | 概念:定义在方法中的类。 |
匿名内部类 | 概念:定义在方法中的类,本质是一个new出来的对象(类名省略),不含class,必须继承一个父类或者实现一个接口。 作用:①可以方便直接,作为某个方法的类参数。 ②可以方便直接,作为某个方法的类返回值。 ③可以使用多态,给匿名内部类命名。 |
注:Java集合类HashMap内部就有一个静态内部类Entry(用于存储HashMap的抽象元素)。
注:类初始化的时候先初始化静态成员,故而成员内部类,不允许出现静态任何变量。
-
序列化
-
概述:能够创建可复用的 Java 对象,JVM停止运行之后能够保存(持久化)指定的对象状态(成员变量)到磁盘/内存中,并不保存静态变量。
-
作用:①实现Serializable 接口,那么它就可以被序列化,并由序列化 ID决定版本号。
②Transient 关键字能阻止该变量被序列化到文件中(例如在加密解密过程中使用)。
注:当应用程序,希望把内存对象跨网络传递,到另一台主机或者是持久化到存储的时候,就必须要把对象在内存里面的表示转化成合适的格式。这个过程就叫做Marshalling,反之就是demarshalling。
设计模式
java中常见的设计模式有24种,分别以类,对象为范围,分为三种模型(创建型,结构型,行为型)。
-
目的分类
①创建型模式:创建对象
②结构型模式:处理类或对象的组合
③行为型模式:描述类或对象交互与职责分配
-
范围分类
①类模式:处理类和子类的关系
②对象模式:处理对象之间关系
图5.1 设计模式结构
-
工厂模式
例如Spring中的,BeanFactory就是采用的工厂模式。把@Controller、@Service、@Repository 加载到工厂中,需要的时候由工厂帮忙创建。
-
简单工厂模式
图5.2 简单工厂模式思维导图
-
工厂方法模式
图5.3 工厂方法模式思维导图
-
抽象工厂模式
图5.4 抽象工厂模式思维导图
-
单例模式
例如Spring中的Bean默认为单例模式,也可以用多例(一般存在老代码中)。
图5.5 单例模式思维导图
-
桥接模式
由于是多层次的继承结构,我们可以看SpringBoot与Dubbo(分布式结构)整合,底层采用的就是桥接模式,需要连接多个数据库,并且每次根据需求不同,每次访问的数据库不同。
图5.6 桥接模式思维导图
-
代理模式
代理模式的主要作用一个中介,能在客户端与目标对象之间承上启下,增强访问,增强代码功能。Spring,SpringBoot中采用的为JDK动态代理/CGLIB字节码生成技术。核心是如何创建动态代理,如何使用动态代理。
静态代理 | 手动创建代理类,实现接口方法,目标固定 |
动态代理(JDK) | ①实现InvocationHandler接口,重写invoke()方法; ②使用Proxy.newProxyInstance创建实例; |
动态代理(CGLIB) | 继承的目标类,目标方法不得是final修饰,由第三方工具库,创建代理对象 |
-
算法设计与分析
算法是一种编程思想,解决问题。操作对象是数据结构。
-
算法设计
必须有输入/输出,含有三个特性(有限性(时间),确定性(指令清晰),可行性(正常执行))。
-
算法分析
-
时间复杂度:CPU资源,执行、读取次数。
-
空间复杂度:存储空间,内存。
-
渐进复杂度:T(n)=T‘(n),n->00。
-
上界:f(n)=O(g(n))。
当n>=m,f(n)<=Cg(n)时,g(n)为f(n)上界==f(n)的阶低g(n),f(n)为g(n)的大O;
//常用公式
O(f)+O(g)=O(max(f,g)):f+g的阶之和,等于,f与g中较大的阶;
O(f)+O(g)=O(f+g):f+g的阶之和,等于,f+g的阶;
O(f)*O(g)=O(f*g):f*g的阶之积,等于,f*g的阶;
f=O(f):f上界不高于自身;
如果g(n)=O(f(n)),则O(f)+O(g)=O(f)
如果C为正整数,则O(C*f(n))=O(f(n))
-
下界:f(n)=Ω(g(n))。
当n>=m,f(n)>=Cg(n)时,g(n)为f(n)下界==f(n)的阶高g(n),f(n)为g(n)的大Ω;
-
同阶:f(n)=θ(g(n))。
当n>=m,C2g(n)>=f(n)>=C1g(n)时,g(n)为f(n)同阶,f(n)为g(n)的上界,也为下界;
-
常用算法
-
冒泡排序
原理:从左到右比较大小,得出最大值放置末尾,进行交互(不断重复)。
-
选择排序
原理:从左到右比较大小,得出最小放在左边。
-
二分法查找
原理:由开始位置值,末尾位置值。获得中间位置值。目标<中间,左边查找;目标>中间,右边查找(直到查到为止)。
-
加密算法
-
单钥密码(对称算法)
-
特征:不可抗抵赖
-
明文---加密密钥 --- 密文 --- 解密密钥---明文
图6.1 对称算法加密解密流程
-
双钥密码(RSA算法/非对称算法)
-
公开密文:不可鉴别,不可抗抵赖
-
公开加密密钥/解密密钥:不可机密性
例如:磁盘加密软件(Truecrypt)邮件加密软件PGP(基于RSA算法)。
图6.2 非对称算法加密解密流程
-
DES算法
-
扩散:明文统计,被扩散,消失到密文统计
-
混乱:密文统计,密钥取值---二者关系尽量复杂
-
雪崩效益:明文或密钥变化1bit,密文所有bit都可能变化
-
DES原理:56位密钥--加密--64位明文-->16轮运算--64位密文
图6.3 DES加密流程简图
图6.4 DES解密流程简图
-
java集合
-
Iterable接口/iterator迭代器
- Iterable(接口):
- 是List、Set、Collection的最高父类接口。
- 定义了返回iterator的方法,相当于对iterator(迭代器)的封装,同时实现了iterable接口的类可以支持forEach循环。
- iterator(迭代器):
- 含有 iterator() 方法,能对List、Set 和 Map 等通过for-each() 循环进行遍历,通过hasNext()方法获取下一个元素,通过 remove() 方法来删除迭代器返回最后一个元素 ,不可以修改、添加元素。
- Iterator 只支持从前向后遍历集合。
- Iterator 没有提供获取元素索引的方法。
- Listiterator(迭代器):
- 只能对List集合进行遍历。
- ListIterator 支持从前向后遍历和从后向前遍历两个方向。
- ListIterator 同时支持使用 set() 修改当前元素,以及使用 add() 方法在当前元素之前添加元素
- ListIterator 可以通过 nextIndex() 和 previousIndex() 方法获取下一个元素和上一个元素的索引值
- Iterable(接口):
图8.1 集合整体结构图
-
fail-fast/fail-safe
- 快速失败,集合遍历过程中一旦检测容器数据被修改(modCount的值被改变),直接抛出异常(Concurrent Modification Exception。)。
- 安全失败,通过拷贝原集合的方式进行遍历,即使数据被修改了,也不能被迭代器检测到。
-
List接口
-
数组优点:检索效率高
-
数组缺点:随机增删效率低(末尾添加元素效率高)
-
双向链表优点:随机增删效率高(内存地址不连续)
-
双向链表缺点:检索效率低
-
Set接口
-
HashSet不可重复原理:会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素;如果存在则调用equals进行比较判断是否重复。
图8.2 Conllection集合结构图
-
Map接口
-
哈希表:实际上就是一维数组与单向链表结合体。
-
二叉树(红黑树):可排序二叉树遵循左小右大原则,遍历方式包括前序遍历、中序遍历(TreeMap)、后序遍历;当节点低于6个,数据结构转为单向链表。
-
单向链表:含有Node节点2个属性①存储数据②下一个节点内存地址;当节点数超过8个,数据结构转为二叉树(红黑树)。
图8.3 Map集合结构图
-
扩展:HashMap中put/get实现原理:
Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引
通过key调用 hashCode得到hash值转为数组,再通过 equals进去比较vule,最后 存进去哈希表中,或者取出。
注:存放在HashMap集合key部分的元素需要同时重写 hashCode+equals 方法(重写equals的时候必须同时重写hashCode,重写hashCode的时候不一定重写equals)。
-
扩展:TreMap中key如何进行自动排序的:
创建TreeMap集合时通过构造方法传递一个比较器,存放在key部分的元素实现 Comparable接口。
-
扩展:ConcurrentHashMap与HashMap,HashTable联系/区别
ConcurrentHashMap底层是HashMap,也是 “数组+链表+红黑树”结构。
初始化数组/头节点时没有加锁;插入数据的时候会加锁,锁住链表头节点, 并发性好。
-
多线程并发
-
继承Thread
-
启动:当前线程继承的Thread实例,调用start()。
-
执行:重写父类中的run()方法。
//创建Threadt线程对象
Thread t1 = new MyThreadt();
t1.start();
-
实现Runnable
-
启动:实例化Thread类,参数传入当前线程实现的Runnable实例,调用start()。
-
执行:重写父类中的run()方法。
//创建Runnable线程对象
Thread t2 = new Thread(new MyRunablet());
t2.start();
-
实现Callable
-
启动:实例化FutureTask类,参数传入当前线程实例。
实例化Thread类,参数传入FutureTask实例,调用start()。
-
获取返回值(默认Object):FutureTask实例,调用get()。
-
执行:重写父类中的call()方法。
//创建Callable线程对象
FutureTask ft = new FutureTask(new MyCallable());
Thread t3 = new Thread(ft);
t3.start();
//获取返回值
Object obj = ft.get();
-
线程生命周期
-
新建状态:new创建线程对象。
-
就绪状态:start()方法被调用,此时JVM开出新的栈空间,线程开始抢夺CPU时间片。
-
运行状态:run()方法的开始。
-
阻塞状态(wait/sleep/join/lock):放弃CPU时间片。
-
死亡状态(正常结束/异常/stop):run()方法的结束。
注:调用stop()是线程不安全的,因为同时释放该线程所有的锁,导致不可控。
-
线程常用方法
wait(),Object类 | 当前线程等待,释放锁,让出CPU时间片(阻塞),调用notify()/notifyAll()后才会获取锁。 |
join(),底层是wait() | t合并到当前线程,释放锁,当前线程让出CPU时间片(阻塞),t执行直到终结,当前线程执行。 |
sleep(),Thread类 | 当前线程休眠,不释放锁,让出CPU时间片(阻塞)。 |
yield(),Thread类 | 当前线程让位,不释放锁,回到就绪状态,重新抢夺CPU时间片。 |
interrupt() | 当前线程打上中断标识,当存在标识返回ture,不存在标识返回false(一个线程抛出抛InterruptedException异常前,都会清除中断标识位) |
-
守护线程
-
概念:服务于用户线程(普通线程),权重是比较低;当用户线程结束,守护线程自动结束。
-
用法:使用Thread中的方法setDaemon(true),将该线程设置为守护线程。
-
案例:垃圾回收器GC,当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动结束。
注:线程是JVM级别,例如当WEB停止时,JVM中的线程可能还在运行。
-
三种核心锁
-
多线程三大特性:
-
原子性(存在多线程共享成员变量中,要么执行完毕要么没有发生);
-
可见性(及时同步线程信息,例如私有/公共/内存中的数据);
-
有序性(阻止指令重排,保证指令按序执行);
-
volatile
-
只能修改变量。
-
不会出现阻塞。
-
保证线程间有序性,只保证变量可见性,不保证原子性(例如long/double)。
-
synchronized(同步锁)
-
可以修饰,类,方法(对象锁),静态方法(类锁唯一),以及代码块。
-
会出现阻塞。
-
保证线程间有序性,间接保证可见性,保证原子性。
-
Lock
-
具有synchronized所有功能,需手动释放锁,try..finally连用
-
lock.lock()加锁
-
lock.unlock()释放锁
-
扩展其他的锁
死锁 | 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。 |
乐观锁 | 每次去拿数据的时候都认为别人不会修改,所以每次读写,不会上锁;但在更新的时候,会比较版本号然后加上锁。 |
悲观锁 | 每次去拿数据的时候都认为别人会修改,所以每次读写,都会上锁。 例如Synchronized,AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁。 |
自旋锁 | 当持有锁的线程能快速短时间释放资源,则让当前需要锁的线程等待,而不是进入阻塞。 |
可重入锁 | 又称递归锁,同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受 影响。 例如ReentrantLock 和 synchronized 都是可重入锁 |
公平锁 | 加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得。 |
非公平锁 | 加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。 例如synchronized 是非公平锁,ReentrantLock 默认的 lock()方法采用的是非公平锁 |