JavaSE
2022-02-10 11:31:46 0 举报
AI智能生成
JavaSE
作者其他创作
大纲/内容
前言
Java发展历程
环境搭建
IDEA安装使用
Mavan安装使用
基础程序设计
关键字
数据类型
运算符
流程控制
数组
存储区间是连续,且占用内存严重,空间复杂也很大,时间复杂为O(1)
优点:是随机读取效率很高,原因数组是连续(随机访问性强,查找速度快)
缺点:插入和删除数据效率低,因插入数据,这个位置后面的数据在内存中要往后移的,且大小固定不易动态扩展
面向对象编程
类/对象
“万事万物皆对象”
类的创建
定义类(考虑修饰符、类名)
编写类的属性(考虑修饰符、属性类型、属性名、 初始化值)
编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
对象的创建
内存结构
栈
通常所说的栈(Stack) , 是指虚拟机
栈。 虚拟机栈用于存储局部变量等。
局部变量表存放了编译期可知长度的
各种基本数据类型(boolean、 byte、
char 、 short 、 int 、 float 、 long 、
double) 、 对象引用(reference类型,
它不等同于对象本身, 是对象在堆内
存的首地址) 。 方法执行完, 自动释
放。
栈。 虚拟机栈用于存储局部变量等。
局部变量表存放了编译期可知长度的
各种基本数据类型(boolean、 byte、
char 、 short 、 int 、 float 、 long 、
double) 、 对象引用(reference类型,
它不等同于对象本身, 是对象在堆内
存的首地址) 。 方法执行完, 自动释
放。
堆
堆(Heap) , 此内存区域的唯一目的
就是存放对象实例, 几乎所有的对象
实例都在这里分配内存。 这一点在
Java虚拟机规范中的描述是:所有的
对象实例以及数组都要在堆上分配。
就是存放对象实例, 几乎所有的对象
实例都在这里分配内存。 这一点在
Java虚拟机规范中的描述是:所有的
对象实例以及数组都要在堆上分配。
方法区
方法区(Method Area) , 用于存储已
被虚拟机加载的类信息、 常量、 静态
变量、 即时编译器编译后的代码等数
据。
被虚拟机加载的类信息、 常量、 静态
变量、 即时编译器编译后的代码等数
据。
特殊类
包装类
概念
针对八种基本数据类型定义相应的引用类型—包装类(封装类)
有了类的特点,就可以调用类中的方法, Java才是真正的面向对象
装箱/拆箱
基本数据类型包装成包装类的实例 ---装箱
int i = 500; Integer t = new Integer(i);
获得包装类对象中包装的基本类型变量 ---拆箱
调用包装类的.xxxValue()方法:
boolean b = bObj.booleanValue();
boolean b = bObj.booleanValue();
抽象类
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
特点
比如: public abstract void talk();
- 用abstract关键字来修饰一个类, 这个类叫做抽象类。
- 用abstract来修饰一个方法, 该方法叫做抽象方法。
比如: public abstract void talk();
- 含有抽象方法的类必须被声明为抽象类。
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍为抽象类。
- 不能用abstract修饰变量、代码块、构造器;
- 不能用abstract修饰私有方法、静态方法、 final的方法、 final的类。
内部类
说明
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
Inner class的名字不能与包含它的外部类类名相同
分类
成员内部类
非static成员内部类(普通内部类)
类对象依赖外部类对象而存在,即在创建一个普通内部类对象时首先需要创建其外部类对象
static成员内部类(静态内部类)
一个类的静态成员独立于这个类的任何一个对象存在,只要在具有访问权限的地方,我们就可以通过类名.静态成员名的形式来访问这个静态成员,同样的,静态内部类也是作为一个外部类的静态成员而存在,创建一个类的静态内部类对象不需要依赖其外部类对象
静态内部类就像外部类的一个静态成员一样,创建其对象无需依赖外部类对象(访问一个类的静态成员也无需依赖这个类的对象,因为它是独立于所有类的对象的)。但是于此同时,静态内部类中也无法访问外部类的非静态成员,因为外部类的非静态成员是属于每一个外部类对象的,而本身静态内部类就是独立外部类对象存在的,所以静态内部类不能访问外部类的非静态成员,而外部类依然可以访问静态内部类对象的所有访问权限的成员,这一点和普通内部类无异。
特性
分析角度
成员内部类作为外部类的成员
- 和外部类不同, Inner class还可以声明为private或protected;
- 可以调用外部类的结构
- Inner class 可以声明为static的, 但此时就不能再使用外层类的非static的成员变量;
成员内部类作为类
- 可以在内部定义属性、 方法、 构造器等结构
- 可以声明为abstract类 , 因此可以被其它的内部类继承
- 可以声明为final的
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
要点
非static的成员内部类中的成员不能声明为static的, 只有在外部类或static的成员内部类中才可声明static成员。
外部类访问成员内部类的成员, 需要“内部类.成员”或“内部类对象.成员”的方式
成员内部类可以直接使用外部类的所有成员, 包括私有的数据
当想要在外部类的静态成员部分使用内部类时, 可以考虑内部类声明为静态的
局部内部类
匿名内部类
类的结构
属性
赋值的位置
默认初始化
显式初始化
构造器中初始化
通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序
1 2 3 4
方法
方法的重载
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
与返回值类型无关,只看参数列表,且参数列表必须不同。 (参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
可变形参的方法
声明格式: 方法名(参数的类型名 ...参数名)
可变参数:方法参数部分指定类型的参数个数是可变多个: 0个, 1个或多个
可变个数形参的方法与同名的方法之间,彼此构成重载
可变参数方法的使用与方法参数部分使用数组是一致的
方法的参数部分有可变形参,需要放在形参声明的最后
在一个方法的形参位置,最多只能声明一个可变个数形参
方法参数的值传递机制
Java里方法的参数传递方式只有一种: 值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参
形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参
递归方法
一个方法体内调用它自身
方法的重写
在子类中可以根据需要对从父类中继承来的方法进行改造, 也称为方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。
构造器
类别
隐式无参构造器(系统默认提供)
显式定义一个或多个构造器(无参、有参)
注意事项
Java语言中,每个类都至少有一个构造器
默认构造器的修饰符与所属类的修饰符一致
一旦显式定义了构造器, 则系统不再提供默认构造器
一个类可以创建多个重载的构造器
父类的构造器不可被子类继承
代码块
对Java类或对象进行初始化
分类
静态代码块
静态代码块:用static 修饰的代码块
1. 可以有输出语句。
2. 可以对类的属性、类的声明进行初始化操作。
3. 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
4. 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
5. 静态代码块的执行要先于非静态代码块。
6. 静态代码块随着类的加载而加载,且只执行一次。
1. 可以有输出语句。
2. 可以对类的属性、类的声明进行初始化操作。
3. 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
4. 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
5. 静态代码块的执行要先于非静态代码块。
6. 静态代码块随着类的加载而加载,且只执行一次。
非静态代码块
非静态代码块:没有static修饰的代码块
1. 可以有输出语句。
2. 可以对类的属性、 类的声明进行初始化操作。
3. 除了调用非静态的结构外, 还可以调用静态的变量或方法。
4. 若有多个非静态的代码块, 那么按照从上到下的顺序依次执行。
5. 每次创建对象的时候, 都会执行一次。 且先于构造器执行。
1. 可以有输出语句。
2. 可以对类的属性、 类的声明进行初始化操作。
3. 除了调用非静态的结构外, 还可以调用静态的变量或方法。
4. 若有多个非静态的代码块, 那么按照从上到下的顺序依次执行。
5. 每次创建对象的时候, 都会执行一次。 且先于构造器执行。
三大特性
封装(Encapsulation)
“高内聚,低耦合”
高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
低耦合 : 仅对外暴露少量的方法用于使用。
隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说, 把该隐藏的隐藏起来,该暴露的暴露出来。 这就是封装性的设计思想。
Java中通过将数据声明为私有的(private), 再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作
继承(Inheritance)
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
规则
子类不能直接访问父类中私有的(private)的成员变量和方法。
Java只支持单继承和多层继承, 不允许多重继承
多态(Polymorphism)
父类的引用指向子类的对象
Java引用变量有两个类型: 编译时类型和运行时类型。 编译时类型由声明该变量时使用的类型决定, 运行时类型由实际赋给该变量的对象决定。 简称: 编译时, 看左边;运行时, 看右边。
若编译时类型和运行时类型不一致, 就出现了对象的多态性(Polymorphism)
多态情况下,
- “看左边” : 看的是父类的引用(父类中不具备子类特有的方法)
- “看右边” : 看的是子类的对象(实际运行的是子类重写父类的方法)
对象的多态 —在Java中,子类的对象可以替代父类的对象使用
子类可看做是特殊的父类, 所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
接口
设计思想
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。 继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系。
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
特点
接口(interface)是抽象方法和常量值定义的集合。
- 用interface来定义。
- 接口中的所有成员变量都默认是由public static final修饰的。
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器。
- 接口采用多继承机制
- 定义Java类的语法格式: 先写extends,后写implements
- 一个类可以实现多个接口, 接口也可以继承其它接口。
- 实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
- 接口的主要用途就是被实现类实现。 (面向接口编程)
- 与继承关系类似,接口与实现类之间存在多态性
- 接口和类是并列关系, 或者可以理解为一种特殊的类。 从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前), 而没有变量和方法的实现。
Java 8中关于接口的改进
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
设计模式
代理 VS 装饰者
代理模式
给一个对象提供一个代理对象,并有代理对象来控制对原有对象的引用
装饰者模式
以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案
装饰模式应该为所装饰的对象增强功能;代理模式对代理的对象施加控制,并不提供对象本身的增强功能。二者的实现机制确实是一样的,可以看到他们的实例代码重复是很多的。
单例模式
基础
模型
饿汉式
饿汉式:类一加载,就创建对象
缺陷:
1、如果此类中还有其他占用内存的对象存在,一单此类加载,就会占用大量的内存;
2、高并发下不安全。
缺陷:
1、如果此类中还有其他占用内存的对象存在,一单此类加载,就会占用大量的内存;
2、高并发下不安全。
懒汉式
懒汉式DCL + volatile形成的三级检测锁
枚举 (最安全的)
关键字
this
super
static
设计思想
类属性作为该类各个对象之间共享的变量。 在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应
的方法设置为类方法。
的方法设置为类方法。
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
类变量
存放于「方法区」中
类方法
没有对象的实例时,可以用类名.方法名()的形式访问由static修饰的类方法。
在static方法内部只能访问类的static修饰的属性或方法, 不能访问类的非static的结构。
因为不需要实例就可以访问static方法,因此static方法内部不能有this。 (也不能有super ? YES!)
static修饰的方法不能被重写
final
在Java中声明类、 变量和方法时, 可使用关键字final来修饰,表示“最终的”
final标记的类不能被继承。 提高安全性, 提高程序的可读性。
final标记的方法不能被子类重写。
final标记的变量(成员变量或局部变量)即称为常量。 名称大写, 且只能被赋值一次。
Object类
equals()
= =
基本类型比较值:只要两个变量的值相等, 即为true。
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时, ==才返回true。
所有类都继承了Object, 也就获得了equals()方法。 还可以重写。
- 只能比较引用类型, 其作用与“==”相同,比较是否指向同一个对象。
- 格式:obj1.equals(obj2)
特例:当用equals()方法进行比较时, 对类File、 String、 Date及包装类(Wrapper Class) 来说, 是比较类型及内容而不考虑引用的是否是同一个对象;
- 原因:在这些类中重写了Object类的equals()方法。
toString()
Java特性
泛型
设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象, 所以在JDK1.5之前只能把元素类型设计为Object, JDK1.5之后使用泛型来解决。 因为这个时候除了元素的类型不确定, 其他的部分是确定的, 例如关于这个元素如何保存, 如何管理等是确定的, 因此此时把元素的类型设计成一个参数, 这个类型参数叫做泛型。 Collection<E>, List<E>, ArrayList<E> 这个<E>就是类型参数, 即泛型。
概念
所谓泛型, 就是允许在定义类、 接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。 这个类型参数将在使用时(例如,继承或实现这个接口, 用这个类型声明变量、 创建对象时) 确定(即传入实际的类型参数, 也称为类型实参) 。
通配符
类型通配符:?
ist<?>是List<String>、 List<Object>等各种泛型List的父类
注解
枚举
定义枚举类
JDK1.5之前需要自定义枚举类
私有化类的构造器,保证不能在类的外部创建其对象
在类的内部创建枚举类的实例。声明为: public static final
对象如果有实例变量,应该声明为private final,并在构造器中初始化
JDK 1.5 新增的 enum 关键字用于定义枚举类
可变参数
JDK8
Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以
传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更
灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了
提升。
传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更
灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了
提升。
函数式接口
只包含一个抽象方法的接口,称为函数式接口。
简单的说,在Java8中, Lambda表达式就是一个函数式接口的实例。 这就是
Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口
的实例,那么该对象就可以用Lambda表达式来表示。
Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口
的实例,那么该对象就可以用Lambda表达式来表示。
所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
在java.util.function包下定义了Java 8 的丰富的函数式接口
你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式
抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽
象方法上进行声明)。
抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽
象方法上进行声明)。
方法引用与构造器引用
方法引用
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就
是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向
一个方法,可以认为是Lambda表达式的一个语法糖。
是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向
一个方法,可以认为是Lambda表达式的一个语法糖。
要求: 实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的
方法的参数列表和返回值类型保持一致!
方法的参数列表和返回值类型保持一致!
如下三种主要使用情况:
对象::实例方法名
类::静态方法名
类::实例方法名
当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二
个参数是需要引用方法的参数(或无参数)时: ClassName::methodName
个参数是需要引用方法的参数(或无参数)时: ClassName::methodName
构造器引用
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象
方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象
方法的参数列表一致!且方法的返回值即为构造器对应类的对象。
Stream API
链式编程
说明
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进
行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用
Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。
也可以使用 Stream API 来并行执行操作。简言之, Stream API 提供了一种
高效且易于使用的处理数据的方式。
行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用
Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。
也可以使用 Stream API 来并行执行操作。简言之, Stream API 提供了一种
高效且易于使用的处理数据的方式。
Stream 和 Collection 集合的区别: Collection 是一种静态的内存数据
结构,而 Stream 是有关计算的。 前者是主要面向内存,存储在内存中,
后者主要是面向 CPU,通过 CPU 实现计算。
结构,而 Stream 是有关计算的。 前者是主要面向内存,存储在内存中,
后者主要是面向 CPU,通过 CPU 实现计算。
三个步骤
创建 Stream
Java8 中的 Collection 接口被扩展,提供了两个获取流
的方法:
的方法:
- default Stream<E> stream() : 返回一个顺序流
- default Stream<E> parallelStream() : 返回一个并行流
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
- static <T> Stream<T> stream(T[] array): 返回一个流
可以调用Stream类静态方法 of(), 通过显示值创建一个
流。它可以接收任意数量的参数。
流。它可以接收任意数量的参数。
- public static<T> Stream<T> of(T... values) : 返回一个流
可以使用静态方法 Stream.iterate() 和 Stream.generate(),
创建无限流。
创建无限流。
- 迭代
- 生成
中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止
操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全
部处理,称为“惰性求值” 。
操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全
部处理,称为“惰性求值” 。
类型
筛选与切片
filter(Predicate p) 接收 Lambda , 从流中排除某些元素
映 射
map(Function f)
接收一个函数作为参数,该函数会被应用到每个元
素上,并将其映射成一个新的元素。
接收一个函数作为参数,该函数会被应用到每个元
素上,并将其映射成一个新的元素。
flatMap(Function f)
接收一个函数作为参数,将流中的每个值都换成另
一个流,然后把所有流连接成一个流
接收一个函数作为参数,将流中的每个值都换成另
一个流,然后把所有流连接成一个流
排序
sorted(Comparator com) 产生一个新流,其中按比较器顺序排序
终止操作(终端操作)
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例
如: List、 Integer,甚至是 void 。
如: List、 Integer,甚至是 void 。
类型
匹配与查找
count() 返回流中元素总数
forEach(Consumer c)
内部迭代(使用 Collection 接口需要用户去做迭代,
称为外部迭代。相反, Stream API 使用内部迭
代——它帮你把迭代做了)
内部迭代(使用 Collection 接口需要用户去做迭代,
称为外部迭代。相反, Stream API 使用内部迭
代——它帮你把迭代做了)
归约
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一
个值。返回 T
个值。返回 T
收集
Optional类
Optional<T> 类(java.util.Optional) 是一个容器类, 它可以保存类型T的值, 代表
这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不
存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
这个值存在。或者仅仅保存null,表示这个值不存在。原来用 null 表示一个值不
存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。
Optional类的Javadoc描述如下:这是一个可以为null的容器对象。如果值存在
则isPresent()方法会返回true,调用get()
则isPresent()方法会返回true,调用get()
使用流程
Date/Time API
应用程序开发
网络
URL编程
URL类
URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。
URLConnection类
表示到URL所引用的远程对象的连接。当与一个URL建立连接时,首先要在一个 URL 对象上通过方法 openConnection() 生成对应的 URLConnection对象。如果连接过程失败,将产生IOException.
HttpURLConnection对象不能直接构造,需要通过URL类中的openConnection()方法来获得。
HttpURLConnection的connect()函数,实际上只是建立了一个与服务器的TCP连接,并没有实际发送HTTP请求。HTTP请求实际上直到我们获取服务器响应数据(如调用getInputStream()、getResponseCode()等方法)时才正式发送出去。
对HttpURLConnection对象的配置都需要在connect()方法执行之前完成。
HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情下,可能会导致程序僵死而不继续往下执行。
HTTP正文的内容是通过OutputStream流写入的, 向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待关闭时,根据写入的内容生成HTTP正文。
调用getInputStream()方法时,返回一个输入流,用于从中读取服务器对于HTTP请求的返回信息。
我们可以使用HttpURLConnection.connect()方法手动的发送一个HTTP请求,但是如果要获取HTTP响应的时候,请求就自动的发起,比如我们使用HttpURLConnection.getInputStream()方法的时候,所以完全没有必要调用connect()方法。
HttpClient
在一般情况下,如果只是需要向Web站点的某个简单页面提交请求并获取服务器响应,HttpURLConnection完全可以胜任。但在绝大部分情况下,Web站点的网页可能没这么简单,这些页面并不是通过一个简单的URL就可访问的,可能需要用户登录而且具有相应的权限才可访问该页面。在这种情况下,就需要涉及Session、Cookie的处理了,如果打算使用HttpURLConnection来处理这些细节,当然也是可能实现的,只是处理起来难度就大了。
为了更好地处理向Web站点请求,包括处理Session、Cookie等细节问题,Apache开源组织提供了一个HttpClient项目,看它的名称就知道,它是一个简单的HTTP客户端(并不是浏览器),可以用于发送HTTP请求,接收HTTP响应。但不会缓存服务器的响应,不能执行HTML页面中嵌入的Javascript代码;也不会对页面内容进行任何解析、处理。
简单来说,HttpClient就是一个增强版的HttpURLConnection,HttpURLConnection可以做的事情HttpClient全部可以做;HttpURLConnection没有提供的有些功能,HttpClient也提供了,但它只是关注于如何发送请求、接收响应,以及管理HTTP连接。
为了更好地处理向Web站点请求,包括处理Session、Cookie等细节问题,Apache开源组织提供了一个HttpClient项目,看它的名称就知道,它是一个简单的HTTP客户端(并不是浏览器),可以用于发送HTTP请求,接收HTTP响应。但不会缓存服务器的响应,不能执行HTML页面中嵌入的Javascript代码;也不会对页面内容进行任何解析、处理。
简单来说,HttpClient就是一个增强版的HttpURLConnection,HttpURLConnection可以做的事情HttpClient全部可以做;HttpURLConnection没有提供的有些功能,HttpClient也提供了,但它只是关注于如何发送请求、接收响应,以及管理HTTP连接。
反射
概述
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期
借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内
部属性及方法。
借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内
部属性及方法。
加载完类之后, 在堆内存的方法区中就产生了一个Class类型的对象(一个
类只有一个Class对象) , 这个对象就包含了完整的类的结构信息。 我们可
以通过这个对象看到类的结构。 这个对象就像一面镜子, 透过这个镜子看
到类的结构, 所以, 我们形象的称之为: 反射。
类只有一个Class对象) , 这个对象就包含了完整的类的结构信息。 我们可
以通过这个对象看到类的结构。 这个对象就像一面镜子, 透过这个镜子看
到类的结构, 所以, 我们形象的称之为: 反射。
正常方式:引入需要的“包类”名称 -> 通过new实例化 -> 取得实例化对象
反射方式:实例化对象 -> getClass()方法 -> 得到完整的“包类”名称
Class 类
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接
口。对于每个类而言, JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含
了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
口。对于每个类而言, JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含
了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
- Class本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
常用方法
获取Class类的实例(四种方法)
类的加载与ClassLoader的理解
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过
如下三个步骤来对该类进行初始化。
如下三个步骤来对该类进行初始化。
ClassLoader
引导类加载器:用C++编写的,是JVM自带的类
加载器, 负责Java平台核心库,用来装载核心类
库。该加载器无法直接获取
加载器, 负责Java平台核心库,用来装载核心类
库。该加载器无法直接获取
扩展类加载器:负责jre/lib/ext目录下的jar包或 –
D java.ext.dirs 指定目录下的jar包装入工作库
D java.ext.dirs 指定目录下的jar包装入工作库
系统类加载器:负责java –classpath 或 –D
java.class.path所指的目录下的类与jar包装入工
作 ,是最常用的加载器
java.class.path所指的目录下的类与jar包装入工
作 ,是最常用的加载器
创建运行时类的对象
调用Class对象的newInstance()方法
类必须有一个无参数的构造器。
类的构造器的访问权限需要足够。
有参构造
通过Class类的getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类
型的构造器
型的构造器
通过Constructor的实例创建对应类的对象,并初始化类属性
调用运行时类的指定结构
调用指定方法
通过Class类的getMethod(String name,Class…parameterTypes)方法取得
一个Method对象,并设置此方法操作时所需要的参数类型。
一个Method对象,并设置此方法操作时所需要的参数类型。
之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中
传递要设置的obj对象的参数信息。
传递要设置的obj对象的参数信息。
调用指定属性
public Field getField(String name) 返回此Class对象表示的类或接口的指定的
public的Field。
public的Field。
public Field getDeclaredField(String name)返回此Class对象表示的类或接口的
指定的Field。
指定的Field。
setAccessible
Method和Field、 Constructor对象都有setAccessible()方法
setAccessible启动和禁用访问安全检查的开关。
动态代理与AOP
原理
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
静态代理
代理方和被代理方都实现同一个接口,代理方可以做更多的事情,而被代理方则专注于做自己的事情,达到解耦效果
特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。 最好可以通过一个代理类完成全部的代理功能。
动态代理
动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
优点
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
Proxy类
异常处理
多线程
线程的创建方式
继承
继承Thread并实现Run方法
public
class Thread implements Runnable {
class Thread implements Runnable {
要点:代理模式
实现
实现Runnable接口
实现Callable接口
有返回值
要点:可以通过Lambda表达式进行编写简化
线程的生命周期
状态
New(初始化状态)
当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
Runnable(就绪状态)
当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了start()此线程立即就会执行
Running(运行状态)
当就绪状态中的线程获得了CUP执行资源,执行run()中的代码,这样的线程我们称为运行状态的线程
Blocked(阻塞状态)
处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。线程的阻塞状态分为两种:
第一种,Waiting(无时间限制的等待状态):
这个状态下是不能分配CPU执行的。有三种情况会使得Running状态到waiting状态
调用无参的Object.wait()方法。等到notifyAll()或者notify()唤醒就会回到Runnable状态。
调用无参的Thread.join()方法。也就是比如你在主线程里面建立了一个线程A,调用A.join(),那么你的主线程是得等A执行完了才会继续执行,这是你的主线程就是等待状态。
调用LockSupport.park()方法。LockSupport是Java6引入的一个工具类Java并发包中的锁都是基于它实现的,再调用LocakSupport.unpark(Thread thread),就会回到Runnable状态。
第二种,Timed_Waiting(有时间限制的等待状态):
其实这个状态和Waiting就是有没有超时时间的差别,这个状态下也是不能分配CPU执行的。有五种情况会使得Runnable状态到waiting状态。
Object.wait(long timeout)。
Thread.join(long millis)。
Thread.sleep(long millis)。注意 Thread.sleep(long millis, int nanos) 内部调用的其实也是Thread.sleep(long millis)。
LockSupport.parkNanos(Object blocked,long deadline)。
LockSupport.parkUntil(long deadline)。
第一种,Waiting(无时间限制的等待状态):
这个状态下是不能分配CPU执行的。有三种情况会使得Running状态到waiting状态
调用无参的Object.wait()方法。等到notifyAll()或者notify()唤醒就会回到Runnable状态。
调用无参的Thread.join()方法。也就是比如你在主线程里面建立了一个线程A,调用A.join(),那么你的主线程是得等A执行完了才会继续执行,这是你的主线程就是等待状态。
调用LockSupport.park()方法。LockSupport是Java6引入的一个工具类Java并发包中的锁都是基于它实现的,再调用LocakSupport.unpark(Thread thread),就会回到Runnable状态。
第二种,Timed_Waiting(有时间限制的等待状态):
其实这个状态和Waiting就是有没有超时时间的差别,这个状态下也是不能分配CPU执行的。有五种情况会使得Runnable状态到waiting状态。
Object.wait(long timeout)。
Thread.join(long millis)。
Thread.sleep(long millis)。注意 Thread.sleep(long millis, int nanos) 内部调用的其实也是Thread.sleep(long millis)。
LockSupport.parkNanos(Object blocked,long deadline)。
LockSupport.parkUntil(long deadline)。
Terminated(终止状态)
在我们的线程正常run结束之后或者run一半异常了就是终止状态
方法
join
yield
线程安全
关键字
synchronized
可以作用在「方法」或「代码块」上,通过在对应的修饰「对象」上添加锁来达到同步效果
JUC数据结构
CopyOnWriteArrayList
COW
锁
分类方式
实现机制
自旋锁
自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
队列锁
AbstractQueuedSynchronizer
需求
ReentrantLock可重入锁
ReentrantReadWriteLock读写锁
状态
乐观锁
悲观锁
synchronized
问题
死锁
线程通信
生产者消费者
synchronized
lock
JUC
数据结构
CopyOnWriteArrayList
ConcurrentHashMap
CopyOnWriteArraySet
计数器
CountDownLatch
CyclicBarrier
Semaphore
阻塞队列
概念
队列:排队 特性:先进先出 FIFO
阻塞:必须要阻塞、不得不阻塞
类
有界队列
ArrayBlockingQueue
采用数组的形式实现
LinkedBlockingQueue
采用单链表的形式实现
入队、出队使用两个不同的锁控制,锁分离,提高效率
常用方法
对比
- ArrayBlockingQueue入队出队采用一把锁,导致入队出队相互阻塞,效率低下;
- LinkedBlockingQueue入队出队采用两把锁,入队出队互不干扰,效率较高;
- 二者都是有界队列,如果长度相等且出队速度跟不上入队速度,都会导致大量线程阻塞;
- LinkedBlockingQueue如果初始化不传入初始容量,则使用最大int值,如果出队速度跟不上入队速度,会导致队列特别长,占用大量内存;
无界队列
PriorityQueue
DelayQueue
SynchronousQueue
不存储元素,队列是空的
每一个 put 操作,必须等待一个take。否则无法继续添加元素!
可以将SynchronousQueue理解为只有一个数据大小的ArrayBlockingQueue当中的一直阻塞put和take。
连接池
线程池重点内容:三大方法、7大参数、拒绝策略、优化配置。
线程池原理
程序运行的本质:占用系统资源,CPU/磁盘网络进行使用!我们希望可以高效的使用!池化技术就是演进出来的。
简单的说,池化技术就是:提前准备一些资源、以供使用!
线程池、连接池、内存池、对象池…这些东西都是池化技术。
线程的创建和销毁,数据库的连接和断开都十分浪费资源。
简单的说,池化技术就是:提前准备一些资源、以供使用!
线程池、连接池、内存池、对象池…这些东西都是池化技术。
线程的创建和销毁,数据库的连接和断开都十分浪费资源。
创建线程池的三大方法
1、ExecutorService threadpool1 = Executors.newFixedThreadPool(5); // 固定线程池大小
2、ExecutorService threadpool2 = Executors.newCachedThreadPool(); //可以弹性伸缩的线程池,遇强则强
3、ExecutorService threadpool3 = Executors.newSingleThreadExecutor(); // 只有一个
2、ExecutorService threadpool2 = Executors.newCachedThreadPool(); //可以弹性伸缩的线程池,遇强则强
3、ExecutorService threadpool3 = Executors.newSingleThreadExecutor(); // 只有一个
使用Executors创建的线程池容易发生OOM. 因为它允许的其你去队列大小是integer最大值。
7大参数
最大并发量为:maximumPooolSize+workQueue.size
四种拒绝策略
- AbortPolicy (默认的:队列满了,就丢弃任务抛出异常!);
- CallerRunsPolicy(哪来的回哪去? 谁叫你来的,你就去哪里处理);
- DiscardOldestPolicy (尝试将最早进入队列的任务删除,尝试加入新任务);
- DiscardPolicy (队列满了任务也会丢弃,不抛出异常)。
优化配置
CPU 密集型:最大支持多少个线程同时跑,根据CPU去设置,一般设置成与CPU处理器一样大,每一次都要去写吗? 通过Runtime来获取。
IO 密集型:磁盘读写、 一个线程在IO操作的时候、另外一个线程在CPU中跑,造成CPU空闲。最大线程数应该设置为 IO任务数! 对于大文件的读写非常耗时,我们应该用单独的线程让他慢慢跑。
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务。
4个schedule调度
Java新特性
函数式接口
lambda表达式
stream链接编程
JMM
JMM 是一个抽象的概念!并不真实存在!它是一组规范!
JMM:Java Memory Model ,Java内存模型要求必须满足以下条件:
- 线程解锁前,必须要把共享的变量值刷新回主内存;
- 线程加锁前,必须读取主内存的最新值到自己的工作内存;
- 必须是同一把锁!
Java 内存模型对主内存与工作内存之间的具体交互协议定义了八种操作
- lock(锁定):作用于主内存变量,把一个变量标识为一条线程独占状态。
- unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存变量,把一个变量从主内存传输到线程的工作内存中,以便随后的 load 动作使用。
- load(载入):作用于工作内存变量,把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量值的字节码指令时执行此操作。
- assign(赋值):作用于工作内存变量,把一个从执行引擎接收的值赋值给工作内存的变量,每当虚拟机遇到一个需要给变量进行赋值的字节码指令时执行此操作。
- store(存储):作用于工作内存变量,把工作内存中一个变量的值传递到主内存中,以便后续 write 操作。
- write(写入):作用于主内存变量,把 store 操作从工作内存中得到的值放入主内存变量中。
volatile
可见性
不保证原子性
解决方案
- 使用synchronized
- 使用原子性类工具java.util.concurrent.atomic
禁止指令重排
内存屏障
CAS
CAS 是一个 CPU的 并发原语!它的功能就是判断内存中的某个位置的值是否是预期值,如果是更新为自己指定的新值。这个操作是原子性的,内存级别的,而且是连续的。
ABA问题
解决:AtomicStampedReference
其它常见类
Future
不足
对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。
可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果
对比
Netty框架
扩展了Java的 Future接口,提供了addListener等多个扩展方法
CompletableFuture
实现
CompletionStage
Future
创建CompletableFuture对象
- public static CompletableFuture<Void> runAsync(Runnable runnable)
- public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
- public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
- public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
以Async结尾并且没有指定Executor的方法会使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。
runAsync它以Runnable函数式接口类型为参数,所以CompletableFuture的计算结果为空。
supplyAsync方法以Supplier<U>函数式接口类型为参数,CompletableFuture的计算结果类型为U。
计算结果完成时的处理
- public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
- public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
- public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
- public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
Action的类型是BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
转换
- public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
- public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
- public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
这一组函数的功能是当原来的CompletableFuture计算完后,将结果传递给函数fn,将fn的结果作为新的CompletableFuture计算结果。因此它的功能相当于将CompletableFuture<T>转换成CompletableFuture<U>。
纯消费(执行Action)
- public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
- public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
- public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
函数式接口Consumer,这个接口只有输入,没有返回值。
Fork/Join
并行 && 并发
并发:一个处理器同时处理多个任务。
逻辑上的同时发生(simultaneous)
并行:多个处理器或者是多核的处理器同时处理多个不同的任务。
物理上的同时发生
ForkJoinTask
RecursiveTask
ForkJoinPool
常用类库
字符串
String
String是一个final类,代表不可变的字符序列。
StringBuffer
java.lang.StringBuffer代表可变的字符序列, JDK1.0中声明,可以对字符
串内容进行增删,此时不会产生新的对象。
串内容进行增删,此时不会产生新的对象。
StringBuilder
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列, 而且
提供相关功能的方法也一样
提供相关功能的方法也一样
对比
String(JDK1.0): 不可变字符序列
StringBuffer(JDK1.0): 可变字符序列、效率低、线程安全
StringBuilder(JDK 5.0):可变字符序列、效率高、 线程不安全
注意:作为参数传递的话,方法内部String不会改变其值, StringBuffer和StringBuilder
会改变其值。
会改变其值。
需要了解String在JVM内存结构中的存储方式
日期时间
JDK8之前
java.lang.System类
System类提供的public static long currentTimeMillis()用来返回当前时
间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。
java.util.Date类
表示特定的瞬间,精确到毫秒
java.text.SimpleDateFormat类
是一个不与语言环境有关的方式来格式化和解析日期的具体类
java.util.Calendar(日历)类
是一个抽象基类,主用用于完成日期字段之间相互操作的功能
JDK8
比较器
自然排序: java.lang.Comparable
Comparable接口强行对实现它的每个类的对象进行整体排序。这种排序被称
为类的自然排序。
为类的自然排序。
实现 Comparable 的类必须实现 compareTo(Object obj) 方法,两个对象即
通过 compareTo(Object obj) 方法的返回值来比较大小。 如果当前对象this大
于形参对象obj, 则返回正整数,如果当前对象this小于形参对象obj, 则返回
负整数,如果当前对象this等于形参对象obj, 则返回零。
通过 compareTo(Object obj) 方法的返回值来比较大小。 如果当前对象this大
于形参对象obj, 则返回正整数,如果当前对象this小于形参对象obj, 则返回
负整数,如果当前对象this等于形参对象obj, 则返回零。
实现Comparable接口的对象列表(和数组)可以通过 Collections.sort 或
Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有
序集合中的元素,无需指定比较器。
Arrays.sort进行自动排序。实现此接口的对象可以用作有序映射中的键或有
序集合中的元素,无需指定比较器。
定制排序: java.util.Comparator
当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,
或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那
么可以考虑使用 Comparator 的对象来排序, 强行对多个对象进行整体排
序的比较。
或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那
么可以考虑使用 Comparator 的对象来排序, 强行对多个对象进行整体排
序的比较。
重写compare(Object o1,Object o2)方法,比较o1和o2的大小: 如果方法返
回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示
o1小于o2。
回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示
o1小于o2。
可以将 Comparator 传递给 sort 方法(如 Collections.sort 或 Arrays.sort),
从而允许在排序顺序上实现精确控制。
从而允许在排序顺序上实现精确控制。
System类
Math类
IO/NIO
java.io.File
文件和文件目录路径的抽象表示形式,与平台无关
File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。
如果需要访问文件内容本身,则需要使用输入/输出流。
如果需要访问文件内容本身,则需要使用输入/输出流。
IO
分类
数据单位
字节流(8 bit)
字符流(16 bit)
流向
输入流
InputStream 和 Reader 是所有输入流的基类
InputStream(典型实现: FileInputStream)
Reader(典型实现: FileReader)
输出流
OutputStream & Writer
角色
节点流
直接从数据源或目的地读写数据
处理流
不直接连接到数据源或目的地,而是“连接” 在已存
在的流(节点流或处理流)之上,通过对数据的处理为程序提
供更为强大的读写功能。
在的流(节点流或处理流)之上,通过对数据的处理为程序提
供更为强大的读写功能。
装饰者模式
缓冲流
为了提高数据读写的速度, Java API提供了带缓冲功能的流类,在使用这些流类
时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。
时,会创建一个内部缓冲区数组,缺省使用8192个字节(8Kb)的缓冲区。
缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
- BufferedInputStream 和 BufferedOutputStream
- BufferedReader 和 BufferedWriter
流程图
转换流
转换流提供了在字节流和字符流之间的转换
Java API提供了两个转换流:
- InputStreamReader:将InputStream转换为Reader
- OutputStreamWriter:将Writer转换为OutputStream
字节流中的数据都是字符时,转成字符流操作更高效。
随机存取文件流
RandomAccessFile 类
RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类。 并
且它实现了DataInput、 DataOutput这两个接口,也就意味着这个类既可以读也
可以写。
且它实现了DataInput、 DataOutput这两个接口,也就意味着这个类既可以读也
可以写。
RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意
地方来读、写文件
地方来读、写文件
- 支持只访问文件的部分内容
- 可以向已存在的文件后追加内容
体系图
NIO
三大核心组件
Selector(多路复用器)
Selector 允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用 Selector 就会很方便。要使用 Selector,得向 Selector 注册 Channel,然后调用他的 select 方法,这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件。
Channel(通道)
基本上所有的 IO 在 NIO 中都从一个 Channel 开始。Channel 有点像流,数据可以从 Channel 读到 Buffer,也可以从Buffer 写到 Channel。
Buffer(缓冲区)
缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松的使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变换情况,Channel 提供从文件,网络读取数据的渠道,但是读取或者写入的数据都必须经由 Buffer 。
零拷贝
Reactor模式
Netty
集合
概述
数组在内存存储方面的特点:
- 数组初始化以后,长度就确定了。
- 数组声明的类型,就决定了进行元素初始化时的类型
数组在存储数据方面的弊端:
- 数组初始化以后,长度就不可变了,不便于扩展
- 数组中提供的属性和方法少,不便于进行添加、删除、插入等操作, 且效率不高。同时无法直接获取存储元素的个数
Collection
List
概述
鉴于Java中数组用来存储数据的局限性,我们通常使用List替代数组
List集合类中元素有序、且可重复,集合中的每个元素都有其对应的顺序索引。
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
常用实现类
ArrayList
本质上, ArrayList是对象引用的一个”变长”数组
JDK1.8: ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组
LinkedList
对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
双向链表, 内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。
Vector
Vector 是一个古老的集合, JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList; Vector总是比ArrayList慢,所以尽量避免使用。
Set
HashSet
LinkedHashSet
Map
HashMap
LinkedHashMap
构成
hash算法
哈希碰撞解决方案
开放寻址
链表
结构
数组+链表
默认为该结构,链表数量大于8时会转换为红黑树
数据+红黑树
HashTable
Properties
SortedMap
TreeMap
数据结构与算法
时间复杂度
O(1) < O(logN) < O(N) < O(NlogN) < O(N~2) < O(N~3)
树
二叉树
二叉查找/搜索/排序树(BST树)
特点
- 根节点的值大于其左子树中任意一个节点的值
- 根结点的值小于其右子树中任意一个节点的值
- 这一规则适用于二叉搜索树中的每一个节点
优点
查询的时间复杂度比链表快,链表的查询时间复杂度是O(n),二叉排序树平均是O(logn)。二叉排序树越平衡,越能模拟二分法,所以越能想二分法的查询的时间复杂度O(logn)。
缺点
但是BST树有一个不足的地方,就是如果插入的结点的值的顺序,是越来越小或者越来越大的,那么BST就会退化为一条链表,那么其查询的时间复杂度就会降为O(n),为了避免这种极端情况,后来又设计一种平衡二叉树,平衡二叉查找树的高度接近 logn,所以插入、删除、查找操作的时间复杂度也比较稳定,是 O(logn)。
图例
二叉平衡树(AVL树)
特点
拥有BST树的特点
AVL树上任意结点的左、右子树的高度差最大为1
优缺点
AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,和红黑树相比,AVL树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差的绝对值不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况。
为了避免平衡二叉树在频繁更新过程中,所带来的维持树结构的时间消耗,从而引入了红黑树
旋转
图例
红黑树
介绍
红黑树(RB-Tree)是一种自平衡的二叉查找树,它的节点的颜色为红色和黑色。它不严格控制左、右子树高度或节点数之差小于等于1。也是一种解决二叉查找树极端情况的数据结构。
红黑树能够以O(logN)的时间复杂度进行搜索、插入、删除操作。红黑树在查找方面和AVL树操作几乎相同。但是在插入和删除操作上,AVL树每次插入删除会进行大量的平衡度计算,红黑树是牺牲了严格的高度平衡的优越条件为代价,它只要求部分地达到平衡要求,结合变色,降低了对旋转的要求,从而提高了性能。任何不平衡都会在三次旋转之内解决。
特点
拥有BST树的特点
节点是红色或黑色
根节点是黑色的
每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存数据
每个红色节点的两个子节点都是黑色。也就是说从每个叶子到根的所有路径上不能有两个连续的红色节点
从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
图例
B树
B树(Blance-Tree)平衡树,也叫作B-树。一个m阶的B树规定了:
- 根结点至少有两个子女。
- 每个中间节点都包含k-1个元素和k个孩子,其中 m/2 <= k <= m 。
- 每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m。
- 所有的叶子结点都位于同一层。
- 每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。
时刻保证:左子小于根节点小于右子
裂变
特点
树的高度越低,IO操作足够少,内存交互可以忽略,查找性能越好。
优点
B树的每一个结点都包含key(索引值) 和 value(对应数据),因此方位离根结点近的元素会更快速。(相对于B+树)
缺点
不利于范围查找(区间查找),如果要找 0~100的索引值,那么B树需要多次从根结点开始逐个查找。
图例
查找节点8时,在二叉搜索树中4次IO操作,B树只用了3次。虽然B树节点所含的元素多不过只是多了几次内存交互而已。
B+树
特点
B+树内部有两种结点,一种是索引结点,一种是叶子结点。
B+树的索引结点并不会保存记录,只用于索引,所有的数据都保存在B+树的叶子结点中。而B树则是所有结点都会保存数据。
B+树的叶子结点都会被连成一条链表。叶子本身按索引值的大小从小到大进行排序。即这条链表是从小到大的,多了条链表方便范围查找数据。
B树的所有索引值是不会重复的,而B+树 非叶子结点的索引值最终一定会全部出现在 叶子结点中。
优点
单一节点存储更多的元素,使得查询的IO次数更少。
B+树的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了;
所有查询都要查找到叶子节点,查询性能稳定。
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当;
所有叶子节点形成有序链表,便于范围查询。
B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。
图例
JDBC
JDBC(Java DataBase Connectivity)是Java和数据库(关系型数据库)之间的一个桥梁。
- 是一个规范而不是一个实现,能够执行SQL语句。
- 它由一组用Java语言编写的类和接口组成,各种不同类型的数据库都有相应的实现。
- 它不属于某一个数据库的接口,而是可以用于定义程序与数据库连接规范,通过一整套接口,由各个不同的数据库厂商去完成所对应的实现类,由sun公司提出!
事务
MySQL 事务
ACID
原子性(Atomicity,或称不可分割性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性(Isolation,又称独立性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
核心:解决并发读写的数据一致性问题
问题
脏读
脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。
可重复读
可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。
不可重复读
对比可重复读,不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。
幻读
幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。
隔离级别
SQL 标准定义了四种隔离级别,MySQL 全都支持。这四种隔离级别分别是:
- 读未提交(READ UNCOMMITTED)
- 读提交 (READ COMMITTED)
- 可重复读 (REPEATABLE READ)
- 串行化 (SERIALIZABLE)
事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了 4 种隔离级别对这三个问题的解决程度。只有串行化的隔离级别解决了全部这 3 个问题,其他的 3 个隔离级别都有缺陷。
读提交
解决了脏读(即读取到的数据只能是commit过后的,也就是rollback时之前修改的数据不会被读取到)
由于能读取到commit过后的数据,带来了一个不可重复读的问题(即同一个事务过程中读取的到信息不一至)
可重复读
事务不会读到其他事务对已有数据的修改,及时其他事务已提交,也就是说,事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的
但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题
持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。
要在JDBC中执行事务,本质上就是如何把多条SQL包裹在一个数据库事务中执行。
连接池
为什么会有连接池的存在?
因为建立数据库连接是一个非常耗时、耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去,极大的提高了数据库连接的性能问题,节省了资源和时间。
什么是数据源?
JDBC2.0 提供了javax.sql.DataSource接口,它负责建立与数据库的连接,当在应用程序中访问数据库时 不必编写连接数据库的代码,直接引用DataSource获取数据库的连接对象即可。用于获取操作数据Connection对象。
数据源与数据库连接池组件
数据源建立多个数据库连接,这些数据库连接会保存在数据库连接池中,当需要访问数据库时,只需要从数据库连接池中
获取空闲的数据库连接,当程序访问数据库结束时,数据库连接会放回数据库连接池中。
获取空闲的数据库连接,当程序访问数据库结束时,数据库连接会放回数据库连接池中。
常用的数据库连接池技术:
这些连接技术都是在jdbc的规范之上建立完成的。有如下:
C3P0、DBCP、Proxool和DruidX
C3P0、DBCP、Proxool和DruidX
JVM
JVM的位置
JVM的体系架构
类加载器
虚拟机自带的加载器
Boot: 启动类(根)加载器
Ext: 拓展类加载器
App: 应用程序加载器
双新委派机制:安全
类的查找顺序为:App --> Ext --> Boot
类加载器接收到类加载的请求
将这个请求向上逐级委托父类加载器去完成,一直向上委托,直到启动类加载器
启动加载器检查是否能够加载当前这个类,能则结束使用当前的加载器,否则抛出异常通知子加载器进行加载
重复步骤3
沙箱安全机制
Native方法
凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层的C语文的库
进行本地方法栈
调用本地方法接口JNI
JNI作用拓展Java的使用,可以整合不同语文为Java使用
程序计数器
方法区
栈
数据结构
先进后出,后进先出
StackOverFlowError
无限递归循环调用(最常见)
执行了大量方法,导致线程栈空间耗尽
方法内声明了海量的局部变量
三种JVM
堆
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
OutOfMemoryError
OOM
区域
新生区
养老区
永久区
这个区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行时的一些环境或类信息,这个区域不存在垃圾回收
JVM参数设置、分析
GC
垃圾回收主要集中在新生区和养老区
标记清除算法
标记清除算法的执行过程分为两个阶段:标记阶段、清除阶段。
复制算法
复制算法会将内存空间分为两块,每次只使用其中一块内存。复制算法同样使用可达性分析法标记除垃圾对象,当GC执行时,会将非垃圾对象复制到另一块内存空间中,并且保证内存上的连续性,然后直接清空之前使用的内存空间。然后如此往复。
标记压缩算法
标记压缩算法可以解决标记清除算法的内存碎片问题。
其算法可以看作三步:
其算法可以看作三步:
- 标记垃圾对象
- 清除垃圾对象
- 内存碎片整理
分代算法
分代算法基于复制算法和标记压缩算法。
首先,标记清除算法、复制算法、标记压缩算法都有各自的缺点,如果单独用其中某一算法来做GC,会有很大的问题。
例如,标记清除算法会产生大量的内存碎片,复制算法会损失一半的内存,标记压缩算法的碎片整理会造成较大的消耗。
其次,复制算法和标记压缩算法都有各自适合的使用场景。
复制算法适用于每次回收时,存活对象少的场景,这样就会减少复制量。
标记压缩算法适用于回收时,存活对象多的场景,这样就会减少内存碎片的产生,碎片整理的代价就会小很多。
分代算法将内存区域分为两部分:新生代和老年代。
根据新生代和老年代中对象的不同特点,使用不同的GC算法。
新生代对象的特点是:创建出来没多久就可以被回收(例如虚拟机栈中创建的对象,方法出栈就会销毁)。也就是说,每次回收时,大部分是垃圾对象,所以新生代适用于复制算法。
老年代的特点是:经过多次GC,依然存活。也就是说,每次GC时,大部分是存活对象,所以老年代适用于标记压缩算法。
首先,标记清除算法、复制算法、标记压缩算法都有各自的缺点,如果单独用其中某一算法来做GC,会有很大的问题。
例如,标记清除算法会产生大量的内存碎片,复制算法会损失一半的内存,标记压缩算法的碎片整理会造成较大的消耗。
其次,复制算法和标记压缩算法都有各自适合的使用场景。
复制算法适用于每次回收时,存活对象少的场景,这样就会减少复制量。
标记压缩算法适用于回收时,存活对象多的场景,这样就会减少内存碎片的产生,碎片整理的代价就会小很多。
分代算法将内存区域分为两部分:新生代和老年代。
根据新生代和老年代中对象的不同特点,使用不同的GC算法。
新生代对象的特点是:创建出来没多久就可以被回收(例如虚拟机栈中创建的对象,方法出栈就会销毁)。也就是说,每次回收时,大部分是垃圾对象,所以新生代适用于复制算法。
老年代的特点是:经过多次GC,依然存活。也就是说,每次GC时,大部分是存活对象,所以老年代适用于标记压缩算法。
0 条评论
下一页