发展:

1.SE 标准版。
2.EE 企业版,基于SE。
3.ME 已经淘汰。

JDK、JRE和JVM:

JDK:

称为Java开发工具包( Java Development Kit)。Java开发人士需要下载和安装JDK,目前的主流版本为JDK11。

JRE:

称之为Java SE运行时环境(Java SE Runtime Environment),提供了运行Java应用程序所必须的软件环境等。无论是开发还是运行Java应用都必须安装。

区别:

JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。
JRE包含JVM和类库
JVM多个系统上都有实现,使得跨平台能够实现。

javac和java:

javac.exe编译器,主要用于将高级Java源代码翻译成字节码文件。生成对应的xxx.class,即可通过java xxx执行
java.exe解释器,主要用于启动JVM对字节码文件进行解释并执行。新特性:java xxx.java可以直接在版本11运行,如果当前路径存在编译后的class,会报错。

常见操作:

1.注释

单行用//,多行用/* */,文档注释/** */可以提取

2.规范

xxx.java开头用注释声明项目名称、功能、作者、版本、备注

3.xxx.java里面要有这个类xxx

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

4.变量的声明方式

数据类型 变量名 = 初始值; //其中=初始值可以省略,但;不可以省略。
变量使用之前需要声明和初始化。
不能重复声明

5.标识符的命名法则

• 由数字、字母、下划线以及$等组成,其中数字不能开头(不能区分字面值)。
• 不能使用Java语言的关键字,所谓关键字就是Java语言用于表示特殊含义
的单词。
• 区分大小写,长度没有限制但不宜过长。
• 尽量做到见名知意,支持中文但不推荐使用。
• 标识符可以给类/变量/属性/方法/包 起名字。

6.变量的输入

Scanner scanner = new Scanner(System.in);
api的使用可以参考手册index.html

基本数据类型:

概述:

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

1. byte(Byte)

byte 数据类型是8位、有符号的,以二进制补码表示的整数;
最小值是 -128(-2^7),最大值是 127(2^7-1,最高位为0表示非负),默认值是 0;用在大型数组中节约空间。

2. short(Short)

short 数据类型是 16 位、有符号的以二进制补码表示的整数
最小值是 -32768(-2^15);最大值是 32767(2^15 - 1);默认值是 0;

3. int(Integer)

int 数据类型是32位、有符号的以二进制补码表示的整数;
最小值是 -2,147,483,648(-2^31);最大值是 2,147,483,647(2^31 - 1);一般地整型变量默认为 int 类型;默认值是 0 ;
正无穷为∞(字节'\u221e'),负无穷为-∞。print输出为Infinity。

4. long(Long)

long 数据类型是 64 位、有符号的以二进制补码表示的整数
默认值是 0L;long a = 100000L,"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。默认的数字字面值解释器声明为int类型,然后转换为定义的byte、short,int。但int a=25;byte b=a;解释器会报错,因为a是变量,存储的值还不固定,和数字字面值不同,所以解释器直接报错了。
如果超过int范围,会报错整数太大。需要显式加上l或L。
更大的数可以用java.math.BigIntegerBigInteger a = new BigInteger("25025050060000000000000000");

5. float(Float)

float 数据类型是单精度、4个字节32位、符合IEEE 754标准的浮点数,可以表示7位有效数字;范围:-3.403E38~3.403E38。(因为有些位数用来表示指数,所以范围更大了)
float 在储存大型浮点数组的时候可节省内存空间;默认值是 0.0f;浮点数不能用来表示精确的值,如货币;
例子:float f1 = 234.5f/F(不加f的话double转float,报错)。会自动忽略超过位数。

6. double(Double)(推荐,有效位数更多)

double 数据类型是双精度、8个字节64 位、符合IEEE 754标准的浮点数,可以表示15位有效数字;范围:-1.798E308~1.798E308。
浮点数的默认类型为double类型;double类型同样不能表示精确的值,如货币;默认值是 0.0d;运算不精确。比如0.1+0.2=0.3000004。
精确可以用java.math.BigDecimal。System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2")));
例子:double d1 = 123.4。

7. boolean

boolean数据类型表示一位的信息;只有两个取值:true 和 false;默认值是 false;内存空间中所占大小没有明确的规定,可以认为是1个字节。

8. char(Character)

char类型是一个单一的 2个字节 16 位(没有符号位) Unicode 字符;
单引号。
最小值是 \u0000(即为0);最大值是 \uffff(即为65,535,UCS-2统一码版本,其中汉字一共2w多个,基本满足各种语言的使用,最新版本UCS-4是31位字符集);char 数据类型可以储存任何字符;扩展:有两个实现utf-8采用变长字节,1-4位字节变化,由于额外的标志信息,4个字节一共64029个(3个字节一共52156个,汉字常用);utf-16两个字节
例子:char letter = 'A'。 char d=1会自动转换为ascii码对应的字符,计算机的底层只识别0和1组成的二进制序列。(int)d即可打印对应的数字。

引用数据类型:

数组、类、接口、枚举、标注

进制转换:

10转2:

二进制(ob/oB开头)中的最高位(最左边)用于代表符号位,若该位是0则表示非负数, 若该位是1则表示负数。
1.除2取余法,使用十进制整数不断地除以2取出余数,直到商为0时将余数逆序排序。
2.拆分法,将十进制整数拆分为若干个二进制权重(1,2,4,8)的和,有该权重下面 写1,否则写0。

2转10:

1.加权法,使用二进制中的每个数字乘以当前位的权重再累加起来。

负十进制转换为二进制的方式:

先将十进制的绝对值转换为二进制,然后进行按位取反再加1。(涉及到补码的概念,正负相加为0,溢出丢弃)

负二进制转换为十进制的方式:

先减1再按位取反,合并为十进制整数后添加负号。

16进制:

以0x开头

8进制:

以0开头

字节与bit:

公式:

1B(byte,字节)= 8 bit(位)

为什么?

一个字节定义为可容纳单个字符的最小bits
它并不总是8,有时是7或9。这取决于平台。
一个说法是使用8个字符(方便的2乘幂),有时他们将第8位用于奇偶校验,有时又将其用于ASCII标准的扩展。有时他们只是将第8位设置为零。
26个英文字母大小写52个, 加上10个数字,还有一些特殊符号超过了64个,所以ascll128个, 共计需要连续七组信号值, 但是早期的传输不可靠, 于是预留了一位作为奇偶校验, 共八组信号值, 一字节8位

类型转换:

自动类型转换:

从小类型到大类型之间的转换
整型、实型(常量)、字符型数据可以混合运算。运算中,不同类型的数据先转化为同一类型,然后进行运算。byte,short,char—> int —> long—> float —> double 

强制类型转换:

概述:

从大类型到小类型之间的转换

格式:

变量名 = (目标类型)源类型变量名;

数据类型转换必须满足如下规则:

// 子类也可以通过这种强制类型转换 变为父类
1. 不能对boolean类型进行类型转换。
2. 不能把对象类型转换成不相关类的对象。
3. 在把容量大的类型转换为容量小的类型时必须使用强制类型转换。
4. 转换过程中可能导致溢出或损失精度,例如:int i =128;   byte b = (byte)i;因为 byte 类型是 8 位,最大值为127,所以当 int 强制转换为 byte 类型时,值 128 时候就会导致溢出。
5. 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入,例如:(int)23.7 == 23;        (int)-45.89f == -45

运算符:

算术运算符:

+ 表示加法运算符,同时可以实现字符串(至少一边即可)与其他数据类型“相连”。(表明java为弱类型语言)
- 表示减法运算符
* 表示乘法运算符
/ 表示除法运算符,只保留整数部分,要想保留小数需要其中一个为double,或者乘以0.1。不能除以0,除以0.0得到infinity,0/0得到NaN
% 表示取模/取余运算符

自增减运算符:

只能用于变量,常数不可以
a++:是一个表达式,先让a的数值作为整个表达式的最终结果,然后再让a变量加1
++:a让a变量加1,再让a的数值作为整个表达式的最终结果。少了一步声明赋值,效率更高。
a--
--a

逻辑运算符:

&& 表示逻辑与运算符,相当于"并且",同真为真,一假为假。
|| 表示逻辑或运算符,相当于"或者",一真为真,同假为假。
! 表示逻辑非运算符,相当于"取反",真为假,假为真。
逻辑运算符的操作数均为boolean表达式。短路特性
对于逻辑与运算符来说,若第一个表达式为假则结果为假,此时跳过第 二个表达式;
对于逻辑或运算符来说,若第一个表达式为真则结果为真,此时跳过第 二个表达式;

条件/三目运算符:

条件表达式? 表达式1: 表达式2
判断条件表达式是否成立,若成立则执行表达式1,否则执行表达式2 。

赋值运算符:

= 表示赋值运算符,用于将=右边的数据赋值给=左边的变量,覆盖变量 原来的数值。
赋值表达式本身也有值,其本身之值即为所赋之值。
+=、 -=、 *=、 /=、 ...注意:byte a=10;a = a+10;会报错,编译器做了优化,将a和10都变为int类型然后相加,结果为int类型。需要强转换。而a+=10等价于a=(byte)(a+10),所以没有报错。规范:a==22==a两者的区别是少写一个等号的时候,能在编译阶段报错。

移位运算符:

<< 左移运算符,用于将数据的二进制位向左移动,右边使用0补充左移1位通常结果*2,在没超出范围的情况下。
>> 右移运算符,用于将数据的二进制位向右移动,左边使用符号位补充右移1位相当于除2
>>> 表示逻辑右移运算符,用于将数据的二进制位向右移动,左边使用0 补充。非负时和右移一样

位运算符:

& 表示按位与运算符,按照二进制位进行与运算,同1为1,一0为0.
| 表示按位或运算符,按照二进制位进行或运算,一1为1,同0为0.
~ 表示按位取反运算符,按照二进制位进行取反,1为0,0为1.
^ 表示按位异或运算符,按照二进制位进行异或运算,同为0,不同为1.

优先级:

[]()(方法调用)               从左到右
!~++--+(一元运算)-(一元运算) 从右到左
*/%                         从左到右
+-                         从左到右
<< >> >>>                  从左到右
== !=                        从左到右
&                           从左到右
^                           从左到右
|                           从左到右
&&                          从左到右
||                          从左到右
?:                          从右到左
=                          从右到左

流程控制:

if语句:

if(){}
if else
if else if else

switch case分支结构:

从上到下执行
switch(变量/表达式) {case 字面值1: 语句块1; break; case 字面值2: 语句块2; break; ...default:语句块n;
}
switch()中支持的数据类型有:byte、short、char以及int类型,从jdk1.5 开始支持枚举类型,从jdk1.7开始支持String类型。
case穿透:如果执行了一个case没有break,会执行下一行直到break;
default和位置顺序无关,总是在匹配不到的时候才执行。

for循环:

for(初始化表达式; 条件表达式; 修改初始值表达式) { 循环体;
}
for(;;) - 这种没有循环条件的循环叫做 无限循环,俗称“死循环”。
如果要退出外层循环体,需要使用标号的方式。outer: for (...) {for(...) {break outer;}}

while循环:

while(条件表达式) { 循环体;
}
注意:while(条件表达式);{}相当于while(条件表达式){:}{},这会导致i不会增加, 空语句,用于延时。

do while循环(熟悉):

do { 循环体;
} while(条件表达式);     //这里有个分号

数组:

概述:

Java 语言中提供的数组是用来存储固定大小的同类型元素。

声明:

首先必须声明数组变量,才能在程序中使用数组。
dataType[] arrayRefVar;   // 首选的方法
dataType arrayRefVar[];  // 效果相同,但不是首选方法,dataType arrayRefVar[] 风格是来自 C/C++ 语言 ,在Java中采用是为了让 C/C++ 程序员能够快速理解java语言。

创建:

dataType[] arrayRefVar = new dataType[arraySize];
dataType[] arrayRefVar = {value0, value1, ..., valuek};
dataType[] arrayRefVar = new dataType[]{value0, value1, ..., valuek};
示例:int[] arr = new int[2];默认值填充

索引:

数组的元素是通过索引访问的。数组索引从 0 开始,所以索引值从 0 到 arrayRefVar.length-1。

遍历:

for (int i = 1; i < myList.length; i++)
for(type element: array)

内存结构分析:

栈区栈用于存放程序运行过程当中所有的局部变量。一个运行的Java程序从开 始到结束会有多次变量的声明。
堆区JVM会在其内存空间中开辟一个称为“堆”的存储空间,这部分空间用于存 储使用new关键字创建的数组和对象。示例:int[] a = new int[2];这句话,先在栈区声明一个变量,然后在堆区初始化数组,最后赋值操作是在栈区存储堆区的内存地址(引用类型)

增删改查:

数组赋值arr=brr,会把brr的内存地址赋值给arr,arr的长度元素都变为brr。
拷贝:System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

优缺点:

可以直接通过下标(或索引)的方式访问指定位置的元素,速度很快。
数组要求所有元素的类型相同。
数组要求内存空间连续,并且长度一旦确定就不能修改。
增加和删除元素时可能移动大量元素,效率低。

Arrays 类:

java.util.Arrays 类能方便地操作数组(遍历、查找、排序),它提供的所有方法都是静态的。
方法:static String toString(int[] a)public static void fill(int[] a, int val)  //将指定的 int 值分配给指定 int 型数组指定范围中的每个元素。示例:Arrays.fill(a, 1,3,10);   将1到3不含3的位置的元素填充为10,注意不能超索引public static void sort(Object[] a)        //对指定对象数组根据其元素的自然顺序进行升序排列。public static boolean equals(long[] a, long[] a2) //如果两个数组以相同顺序包含相同的元素,则两个数组是相等的。返回truepublic static int binarySearch(Object[] a, Object key)  //用二分查找算法在给定数组中搜索给定值的对象(Byte,Int,double等)。//数组在调用前必须排序好的(二分查找)。如果查找值包含在数组中,则返回搜索键的索引;否则返回 (-(插入点) - 1)。copyOfRange(li,start,end)切片复制,生成另一个数组

二维数组:

声明和初始化:

数据类型[][] 数组名称 = new 数据类型[行数][列数];       其中列数可以忽略。表示不定长。
数据类型[][] 数组名称 = {{元素1, 元素2,...}, ...};

面向对象编程:

概念:

面向对象指以属性和行为的观点去分析现实生活中的事物。
面向对象编程指先以面向对象的思想进行分析,然后使用面向对象的编程语言进行表达的过程。
面向过程指侧重具体的步骤,面向对象则是针对能实现步骤的对象。理解面向对象的思想精髓(封装、继承、多态),至少掌握一种编程语言。

类和对象的概念:

类简单来就是“分类”,是对具有相同特征和行为的多个对象共性的抽象描述,在Java语言中体现为一种引用数据类型,里面包含了描述特征/属性的成员变量以及描述行为的成员方法。
类是用于构建对象的模板,对象的数据结构由定义它的类来决定。

类和成员变量的定义:

class 类名{        通常情况下,当类名由多个单词组成时,要求每个单词首字母都要大写。类体;数据类型成员变量名= 初始值;       当成员变量由多个单词组成时,通常要求从第二个单词起每个单词的首字母大写。 初始值一般不写
}

对象的创建:

a.当一个类定义完毕后,可以使用new关键字来创建该类的对象,这个过程叫做类的实例化。
b.创建对象的本质就是在内存空间的堆区申请一块存储区域,用于存放该对象独有特征信息。
成员变量的默认值: 当变量作为作为类成员使用时,java才确保给定其初始值,防止程序运行时错误。byte short int long float double char  0/'/uoooo'boolean false 引用类型 null

引用的定义:

基本概念:

a.使用引用数据类型定义的变量叫做引用型变量,简称为"引用"。
b.引用变量主要用于记录对象在堆区中的内存地址信息,便于下次访问。

语法格式:

类名引用变量名;
引用变量名.成员变量名;

方法:

定义:

class 类名{返回值类型 成员方法名(形参列表) {     当成员方法名由多个单词组成时,要求从第二个单词起每个单词的首字母大写成员方法体;}
}
方法内访问成员可以直接访问。
数据类型形参变量名1, 数据类型形参变量名2, ...

调用:

引用变量名.成员方法名(实参列表);

可变长参数:

返回值类型方法名(参数的类型... 参数名) 类型固定,看作数组即可。
方法参数部分指定类型的参数个数是可以改变的,也就是0~n个。
一个方法的形参列表中最多只能声明一个可变长形参,并且需要放到参数列表的末尾。
基于数组的实现的语法糖

参数传递:

值传递。把实参传递给形参,方法内部其实是在使用形参。
基本数据类型的变量作为方法的参数传递时,形参变量数值的改变通常不会影响到实参变量的数值,因为两个变量有各自独立的内存空间;
引用数据类型的变量作为方法的参数传递时,形参变量指向内容的改变会影响到实参变量指向内容的数值,因为两个变量指向同一块内存空间当引用数据类型的变量作为方法的参数传递时,若形参变量改变指向后再改变指定的内容,则通常不会影响到实参变量指向内容的改变,因为两个变量指向不同的内存空间。
形参和实参不能同名,否则当作形参了。

内存结构:

栈用于存放程序运行过程当中所有的局部变量。一个运行的Java程序从开始到结束会有多次方法的调用。
JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间称为该方法的栈帧。一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据。
当某一个方法调用完成后,其对应的栈帧将被清除。

构造方法:

class 类名{类名(形参列表) {构造方法体;}
}
构造方法名与类名完全相同并且没有返回值类型,连void都不许有。
支持重载。
使用new关键字创建对象时会自动调用构造方法实现成员变量初始化工作。
this可以在构造方法里调用其他构造方法
默认构造方法:当一个类中没有定义任何构造方法时,编译器会自动添加一个无参空构造构造方法,叫做默认/缺省构造方法,如:Person(){}若类中出现了构造方法,则编译器不再提供任何形式的构造方法。

方法引用:

方法引用通过方法的名字来指向一个方法,可以使语言的构造更紧凑简洁,减少冗余代码。
使用一对冒号 :: 1. 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:final Car car = Car.create( Car::new );final List< Car > cars = Arrays.asList( car );2. 静态方法引用:它的语法是Class::static_method,实例如下:cars.forEach( Car::collide );3. 特定类的任意对象的方法引用:它的语法是Class::method实例如下:cars.forEach( Car::repair );4. 特定对象的方法引用:它的语法是instance::method实例如下:final Car police = Car.create( Car::new );cars.forEach( police::follow );

方法重载:

若方法名称相同,参数列表(类型、个数、顺序)不同,这样的方法之间构成重载关系(Overload)。
和返回类型无关。
实际意义调用者只需要记住一个方法名就可以调用各种不同的版本,来实现各种不同的功能。

this关键字:

若在构造方法中出现了this关键字,则代表当前正在构造的对象。可以print验证。
若在成员方法中出现了this关键字,则代表当前正在调用的对象。
this关键字本质上就是当前类类型的引用变量。
工作原理:在构造方法中和成员方法中访问成员变量时,编译器会加上this.的前缀,而this.相当于汉语中"我的",当不同的对象调用同一个方法时,由于调用方法的对象不同导致this关键字不同,从而this.方式访问的结果也就随之不同。
使用方式:1.当局部变量名与成员变量名相同时,在方法体中会优先使用局部变量(就近原则),若希望使用成员变量,则需要在成员变量的前面加上this.的前缀,明确要求该变量是成员变量(重中之重)。2.this关键字除了可以通过this.的方式调用成员变量和成员方法外,还可以作为方法的返回值(重点)。3.在构造方法的第一行可以使用this()的方式来调用本类中的其它构造方法(了解)。

递归:

使用递归必须有递归的规律以及退出条件。
使用递归必须使得问题简单化而不是复杂化。
若递归影响到程序的执行性能,则使用递推取代之。比如费氏数列

代码拆分:

默认导入同级的class,main方法单独放到xxxTest.java当中。

封装:

私有化成员变量,使用private关键字修饰。
提供公有的get和set方法,并在方法体中进行合理值的判断。
在构造方法中调用set方法进行合理值的判断。
JavaBean一种Java语言写成的可重用组件,其它Java 类可以通过反射机制发现和操作这些JavaBean 的属性。JavaBean本质上就是符合以下标准的Java类:类是公共的有一个无参的公共的构造器有属性,且有对应的get、set方法

static关键字:

基本概念:

使用static关键字修饰成员变量表示静态的含义,此时成员变量由对象层级提升为类层级,也就是整个类只有一份并被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关。
static关键字修饰的成员可以使用引用.的方式访问,但推荐类名.的方式。

使用方式:

在非静态成员方法中既能访问非静态的成员又能访问静态的成员。(成员:成员变量+ 成员方法,静态成员被所有对象共享)
在静态成员方法中只能访问静态成员不能访问非静态成员。(成员:成员变量+ 成员方法,因为此时可能还没有创建对象)静态变量可以通过静态方法修改访问
在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用static关键字修饰。(不能滥用static关键字)

内存结构:

静态变量位于方法区(代码区)。指向可能是堆区(单例模式的对象)。

单例设计模式:

流程:
1.私有化构造方法,使用private关键字修饰。
2.声明本类类型的引用指向本类类型的对象,并使用private static关键字共同修饰。
3.提供公有的get方法负责将对象返回出去,并使用public static关键字共同修饰。
实现方式:
饿汉式和懒汉式(if null==xxx再创建),在以后的开发中推荐饿汉式(避免多线程问题)。
双重检查锁问题:并发场景下,初始化Singleton 和 将对象地址写到instance字段 的顺序是不确定的。在某个线程new Singleton()时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了。然而该对象可能还没有初始化(初始化赋值的业务逻辑)。此时若另外一个线程来调用getInstance,取到的就是状态不正确的对象。(比如一些变量还没赋值完成。))
双重检查锁 + volatile:禁止未初始化完成时,就将内存地址赋值给instance字段。

构造块和静态代码块:

构造块:

在类体中直接使用{}括起来的代码块。每创建一个对象都会执行一次构造块。

静态代码块:

使用static关键字修饰的构造块。静态代码块随着类加载时执行一次。先于构造块执行(加载阶段),比如加载数据库的驱动包等。

子类构造时执行顺序:

先加载父类,再加载子类,在执行父类的无参构造方法(会先执行构造块),再执行子类的构造方法

继承:

概念:

当多个类之间有相同的特征和行为时,可以将相同的内容提取出来组成一个公共类,让多个类吸收公共类中已有特征和行为而在多个类型只需要编写自己独有特征和行为的机制,叫做继承。

格式:

在Java语言中使用extends(扩展)关键字来表示继承关系。
public class Worker extends Person{} -表示Worker类继承自Person类其中Person类叫做超类、父类、基类。其中Worker类叫做派生类、子类、孩子类。
使用继承提高了代码的复用性,可维护性及扩展性,是多态的前提条件。

特点:

子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承只是不能直接访问(可以通过public方法来访问)。
无论使用何种方式构造子类的对象时,都会首先自动调用父类的无参构造方法(没有super的情况下),来初始化从父类中继承的成员变量以及自己的单独成员变量(super()方法做的事情)。
相当于在构造方法的第一行增加代码super()的效果。可以手动加super()则解释器不再加(不是第一行会报错)。也可以super(实参列表),这样就不再调用父类的无参构造方法如果子类不定义任何构造方法,如果父类定义有参,需要父类定义无参构造方法以便super调用。如果子类定义了无参构造方法,也需要父类定义无参构造方法以便super隐式或者显式调用。如果子类定义了有参构造方法,可以不定义无参,需要父类定义有参或者无参构造方法,以便super隐式或者显式调用。总结:一般都定义比较好。
Java语言中只支持单继承不支持多继承,也就是说一个子类只能有一个父类,但一个父类可以有多个子类。

方法重写:

从父类中继承下来的方法不满足子类的需求时,就需要在子类中重新写一个和父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做方法的重写(Override)
调用父类方法:super.function()
示例: @override           # 说明重写,若没有重写则编译报错。public void show(){super.function()xxx}
原则:要求方法名相同、参数列表相同以及返回值类型相同,从Java5开始允许返回类型为子类类型。要求方法的访问权限不能变小,可以相同或者变大。(继承本身是扩展,不能变小)要求方法不能抛出更大的异常(异常机制)。
静态方法: 不能标注@override,但可以起同名的。

访问控制符:

public : 对所有类可见。使用对象:类、接口、变量、方法
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
default (即默认): 在同一包内可见(不同包内的子类不行),不使用任何修饰符。使用对象:类、接口、变量、方法。
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

package包:

格式:

package 包名;
package 包名1.包名2.包名3...包名n;

规范:

org.apache.commons.lang.StringUtil
其中StringUtils是类名而org.apache.commons.lang是多层包名,其含义如下:
org.apache表示公司或组织的信息(是这个公司(或组织)域名的反写);common 表示项目的名称信息;lang 表示模块的名称信息。

导入:

使用import关键字导入包。
使用import关键字导入一个包的静态成员,从Java5.0开始支持。

final关键字:

final关键字修饰类体现在该类不能被继承。-主要用于防止滥用继承,如:java.lang.String类等。
final关键字修饰成员方法体现在该方法不能被重写但可以被继承(即只能调用)。-主要用于防止不经意间造成重写,如:java.text.Dateformat类中format方法等。
final关键字修饰成员变量体现在该变量必须初始化(一开始显式初始化或者延迟在构造块/方法中初始化)且不能改变。-主要用于防止不经意间造成改变,如:java.lang.Thread类中MAX_PRIORITY等。

常量:

在以后的开发中很少单独使用final关键字来修饰成员变量
通常使用public static final关键字共同修饰成员变量来表达常量的含义,常量的命名规范要求是所有字母都要大写,不同的单词之间采用下划线连。public static 修饰的常量作用域是全局的,不需要创建对象就可以访问它

多态:

格式:

父类类型引用变量名= new 子类类型();
如Shape sr= new Rect();

特点:

当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法,不可以直接调用子类独有的方法(编译会报错,需要强转)。
对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本(动态绑定)(如果子类没有则调用父类)。
对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本。

引用数据类型之间的转换:

引用数据类型之间的转换方式有两种:自动类型转换和强制类型转换。自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也叫做向上转型。(多态调用)强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也叫做向下转型或显式类型转换。
条件:1.引用数据类型之间的转换必须发生在父子类之间,否则编译报错。2.如果转换发生在父级和兄弟子类或者孙类这种不是父子关系时,编译阶段不报错,但运行阶段会报ClassCastException。若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运行阶段发生类型转换异常。
使用:为了避免上述错误的发生,应该在强转之前进行判断,格式如下:if(引用变量instanceof数据类型)判断引用变量指向的对象是否为后面的数据类型

实际意义:

屏蔽不同子类的差异性实现通用的编程带来不同的效果。

多态存在的三个必要条件:

1.继承
2.重写
3.父类引用指向子类对象 //Parent p = new Child();

多态的实现方式:

方式一:重写
方式二:接口
方式三:抽象类和抽象方法 

多态的使用场景:

1.通过方法的参数传递形成多态
2.抽象类或接口类的引用指向子类,在方法体中直接使用多态的语法格式
3.通过方法的返回值类型形成多态

虚函数:

虚函数的存在是为了多态。
Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。
如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。
重写: 当子类对象调用重写的方法时,调用的是子类的方法,而不是父类中被重写的方法。要想调用父类中被重写的方法,则必须使用关键字 super。
示例:  Salary s = new Salary("员工 A", "北京", 3, 3600.00);Employee e = new Salary("员工 B", "上海", 2, 2400.00);在编译的时候,编译器使用 Employee 类中的 mailCheck() 方法验证该语句, 但是在运行的时候,Java虚拟机(JVM)调用的是 Salary 类中的 mailCheck() 方法以上整个过程被称为虚拟方法调用,该方法被称为虚拟方法。Java中所有的方法都能以这种方式表现,因此,重写的方法能在运行时调用,不管编译的时候源代码中引用变量是什么数据类型。   

抽象方法:

概述:

主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体。

格式:

访问权限 abstract 返回值类型 方法名(形参列表);

兼容的访问权限:

不能private,私有方法不能继承
不能final,需要修改。
不能static,抽象方法不能提升为类层级
default需要同一包内。

抽象类:

概念:

主要指不能具体实例化的类并且使用abstract关键字修饰,也就是不能创建对象。
和普通类的区别:不能具体实例化声明了抽象方法。

特点:

抽象类中可以有成员变量、构造方法、成员方法(供子类super调用)
抽象类中可以没有抽象方法,也可以有抽象方法(抽象类不能实例化,防止误调用抽象方法);
拥有抽象方法的类必须是抽象类
因此真正意义上的抽象类应该是具有抽象方法(不能实例化的意义所在)并且使用abstract关键字修饰的类。

实际意义:

抽象类的实际意义不在于创建对象而在于被继承。
当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。

格式:

public abstract class Employee
{public abstract double computePay();//其余代码
}

接口:

概述:

一种比抽象类还抽象的类,体现在所有方法都为抽象方法。

定义:

定义类的关键字是class,而定义接口的关键字是interface。
里面只能声明常量,可以忽略public static final
从jdk1.9开始允许接口出现私有方法。
方法可以忽略public abstract

支持的修饰符:

public
private

实际意义:

类可以实现多个接口,达到多继承的效果

格式:

Interface关键字用来声明一个接口。
[可见度] interface 接口名称 [extends 其他的接口名] {// 声明变量// 抽象方法
}

使用:

implements A,B 支持多实现,接口之间可以继承。
然后A xxx = new yyy();可以调用A接口的方法B xxx = new yyy();可以调用B接口的方法

标记接口:

最常用的继承接口是没有包含任何方法的接口。
标记接口是没有任何方法和属性的接口.它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。
标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。public interface EventListener{}
没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:1. 建立一个公共的父接口:正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。2. 向一个类添加数据类型:这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。

类和接口之间的关系:

名称                           关键字                         关系
类和类之间的关系        使用extends关键字表达继承关系          支持单继承
类和接口之间的关系       使用implements关键字表达实现关系   支持多实现
接口和接口之间的关系  使用extends关键字表达继承关系      支持多继承

抽象类和接口的主要区别:

定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
继承抽象类的关键字是extends,而实现接口的关键字是implements。
继承抽象类支持单继承,而实现接口支持多实现。(区别3)
抽象类中可以有构造方法,而接口中不可以有构造方法。(区别1)
抽象类中可以有成员变量,而接口中只可以有常量。(区别2)
抽象类中可以有成员方法,而接口中只可以有抽象方法。
抽象类中增加方法时子类可以不用重写,而接口中增加方法时实现类需要重写(Java8以前的版本)。
从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法(工具类功能),但非抽象方法需要使用default关键字修饰(表示接口的默认功能方法,子类可以选择性重写)。
从Java9开始增加新特性,接口中允许出现私有方法。(一般用于定义一些基础方法,供default和static方法调用)

函数式接口:

一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。
如:@FunctionalInterfaceinterface GreetingService {void sayMessage(String message);}
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):GreetingService greetService1 = message -> System.out.println("Hello " + message);

内部类:

基本概念:

当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类(Inner),而这个内部类所在的类叫做外部类(Outer)。
# 类中的内容:成员变量、成员方法、构造方法、静态成员、构造块和静态代码块、内部类。

实际作用:

当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,这样可以隐藏该类的实现细节并且可以方便的访问外部类的私有成员而不再需要提供公有的get和set方法。

分类:

普通内部类-直接将一个类的定义放在另外一个类的类体中。
静态内部类-使用static关键字修饰的内部类,隶属于类层级。
局部内部类-直接将一个类的定义放在方法体的内部时。
匿名内部类-就是指没有名字的内部类。

普通(成员)内部类:

定义:
属于外部类的一个成员,能直接调用外部类的其他成员
特点:
普通内部类和普通类一样可以定义成员变量、成员方法以及构造方法等。
普通内部类和普通类一样可以使用final(限制内部类的继承)或者abstract关键字修饰。
普通内部类还可以使用private(不能用于实例化)或protected关键字进行修饰。
普通内部类需要使用外部类对象来创建对象。Outer.Inner in = ot.new Inner();
如果内部类访问外部类中与本类内部同名的成员变量或方法时,需要使用this关键字。(遵循局部优先原则,this/Outer.this分别访问内部和外部对象)

静态内部类:

格式:
访问修饰符staticclass 内部类的类名
使用方式:
静态内部类不能直接访问外部类的非静态成员,可以直接访问静态成员。
静态内部类可以直接创建对象。Outer.Inner in = new Outer.Inner();
如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要使用类名.的方式访问。

局部(方法)内部类:

格式:
class 内部类的类名
没有修饰符(类似局部变量)
使用方式:
局部内部类只能在该方法的内部可以使用。
局部内部类可以在方法体内部直接创建对象。
局部内部类不能使用访问控制符和static关键字修饰符。
局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。默认的局部变量从java8开始默认理解为final关键字修饰的变量,如果修改了,会报错。原因是防止局部变量拷贝到内部类中修改了,造成内外不一致。(防止修改)

回调模式:

如果一个方法的参数是接口类型,则在调用该方法时,需要创建并传递一个实现此接口类型的对象;而该方法在运行时会调用到参数对象中所实现的方法(接口中定义的)。
使用: 自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递;使用上述匿名内部类的语法格式得到接口/类类型的引用即可;

匿名内部类:

格式:
接口/父类类型 引用变量名= new 接口/父类类型() {方法的重写};
# lambda表达式更简单()->{}
优点:
省去了为类起名字的烦恼

lambda表达式:

概述:

声明一个函数
一种没有声明的方法,即没有访问修饰符,返回值声明和名称。(但是可以返回,类型推断机制)

用途:

1.简写函数式接口(Functional Interface)
2.声明函数,用于其他高阶函数接收,比如列表foreach,字典map

限制:

不能使用闭包
在lambda表达式中对变量的操作都是基于原变量的副本,不会影响到原变量的值。
只能引用标记了 final 的外层局部变量,否则会误以为外部变量的值能够在lambda表达式中被改变。

更先进的写法:

双冒号操作符

枚举:

概述:

在日常生活中这些事物的取值只有明确的几个固定值,此时描述这些事物的所有值都可以一一列举出来,而这个列举出来的类型就叫做枚举类型。

之前的实现方法:

在class中定义多个常量,给外部访问。
缺点:重复声明比较多

定义:

•使用public static final表示的常量描述较为繁琐,使用enum关键字来定义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型。
•枚举值就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此采用枚举类型.的方式调用。
•枚举类可以自定义构造方法,但是构造方法的修饰符必须是private,默认也是私有的。

示例:

public enum DirectionEnum {UP("up"),DOWN("down");private final String desc;private DirectionEnum(String desc) {this.desc = desc;}
}

switch使用:

function(Enum de){switch(de){case UP:case DOWN:}
}
更简洁,对输入类型更限制

Enum类:

概念:
所有的枚举类都继承自java.lang.Enum类
方法:
static T[] values()返回当前枚举类中的所有对象
String toString()返回当前枚举类对象的名称
intordinal()获取枚举对象在枚举类中的索引位置
static T valueOf(String str)将参数指定的字符串名转为当前枚举类的对象,要求字符串必须对应存在
intcompareTo (E o)比较两个枚举对象在定义时的顺序,结果为1,0,-1,-2等,当调用对象在参数对象之后时,获取到的比较结果为正数。

实现接口:

不能继承,可以实现。
枚举类实现接口后需要重写抽象方法,而重写方法的方式有两种:重写一个,或者每个对象都重写。
每个对象都重写:UP("up"){重写方法}本质上是匿名内部类

注解:

基本概念:

注解(Annotation)又叫标注,是从Java5开始增加的一种引用数据类型。
注解本质上就是代码中的特殊标记,通过这些标记可以在编译、类加载、以及运行时执行指定的处理。

语法格式:

访问修饰符@interface 注解名称{注解成员;
}
自定义注解自动继承java.lang.annotation.Annotation接口。
若一个注解中没有任何的成员,则这样的注解叫做标注注解/标识注解。
注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。
如果注解只有一个参数成员,建议使用参数名为value,而类型只能是八种基本数据类型、String类型、Class类型、enum类型及Annotation类型。
成员变量可以有默认值:private String value() default "";
如: public String value();

使用:

通过@注解名称的方式可以修饰包、类、成员方法、成员变量、构造方法、参数、局部变量的声明等。
@MyAnnotation(value=xxx,value2=yyy)

元注解:

概述:
元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面。
分类:
元注解主要有@Retention、@Documented、@Target、@Inherited、@Repeatable。
元注解@Retention:
•@Retention 应用到一个注解上用于说明该注解的的生命周期,取值如下:
•RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
•RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到JVM 中,默认方式。
•RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到JVM 中,所以在程序运行时可以获取到它们。
元注解@Documented
•使用javadoc工具可以从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档,而该工具抽取时默认不包括注解内容。
•@Documented用于指定被该注解将被javadoc工具提取成文档。
•定义为@Documented的注解必须设置Retention值为RUNTIME。
元注解@Target
用于指定被修饰的注解能用于哪些元素的修饰
ElementType.ANNOTATION_TYPE可以给一个注解进行注解
ElementType.CONSTRUCTOR可以给构造方法进行注解
ElementType.FIELD可以给属性进行注解
ElementType.LOCAL_VARIABLE可以给局部变量进行注解
ElementType.METHOD可以给方法进行注解
ElementType.PACKAGE可以给一个包进行注解
ElementType.PARAMETER可以给一个方法内的参数进行注解
ElementType.TYPE可以给类型进行注解,比如类、接口、枚举
从Java8开始对元注解@Target的参数类型ElementType枚举值增加了两个:
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中,如:泛型。
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。场景更广泛。
元注解@Inherited:
@Inherited并不是说注解本身可以继承,而是说如果一个超类被该注解标记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继承超类的注解。
元注解@Repeatable:
表示自然可重复的含义,从Java8开始增加的新特性。
示例: @Repeatable(value=ManTypes.class)
默认不能重复。java8之前可以通过注解数组的方式@manTypes({@ManType(value=""),@ManType(value="")})

常见的预制注解:

@author标明开发该类模块的作者,多个作者之间使用,分割
@version标明该类模块的版本
@see参考转向,也就是相关主题
@since从哪个版本开始增加的
@param对方法中某参数的说明,如果没有参数就不能写
@return对方法返回值的说明,如果方法的返回值类型是void就不能写
@exception对方法可能抛出的异常进行说明
@Override限定重写父类方法, 该注解只能用于方法
@Deprecated用于表示所修饰的元素(类, 方法等)已过时
@SuppressWarnings抑制编译器警告

Object类:

基本概念:

java.lang.Object类是Java语言中类层次结构的根类,也就是说任何一个类都是该类的直接或者间接子类。
如果定义一个Java类时没有使用extends关键字声明其父类,则其父类为 java.lang.Object 类。
Object类定义了“对象”的基本行为, 被子类默认继承。

常用的方法:

Object() 使用无参方式构造对象
boolean equals(Object obj)用于判断调用对象是否与参数对象相等。该方法默认比较两个对象的地址是否相等,与 == 运算符的结果一致若希望比较两个对象的内容,则需要重写该方法。若该方法被重写后,则应该重写hashCode方法来保证结果的一致性(不然放到字典中时,两个相等的对象存的位置不同)。需要强转来调用子类的独有方法。自反性、对称的、传递性、一致的、对于任何非空的参考值x , x.equals(null)应该返回false。 优化:需要判断是否地址相同,是否为null
int hashCode()用于获取调用对象的哈希码值(内存地址的编号)。若两个对象调用equals方法相等,则各自调用该方法的结果必须相同。若两个调用对象equals方法不相等,则各自调用该方法的结果应该不相同。为了使得该方法与equals方法保持一致,需要重写该方法。一般加个固定值。不然很可能会因为存储了两个equals相等的数据而导致存储数据的不唯一性,导致内存泄露。非基本类型可以用对应的引用类型的hashCode方法
String toString()用于获取调用对象的字符串形式。该方法默认返回的字符串为:包名.类名@哈希码值的十六进制。为了返回更有意义的数据,需要重写该方法。使用print或println打印引用或字符串拼接+引用都会自动调用该方法
Class<?> getClass() 用于返回调用对象执行时的Class实例,反射机制使用

包装类:

概念:

通常情况下基本数据类型的变量不是对象,为了满足万物皆对象的理念就需要对基本数据类型的变量进行打包封装处理变成对象,而负责将这些变量声明为成员变量进行对象化处理的相关类,叫做包装类。

内置:

java.lang.Byte           byte
java.lang.Short         short
java.lang.Integer       int
java.lang.Long          long
java.lang.Float         float
java.lang.Double        double      // java.lang.Number类是个抽象类,是上述类的父类来描述所有类共有的成员。
java.lang.Boolean       boolean
java.lang.Character     char

Integer类:

基本概念:

java.lang.Integer类内部包装了一个int类型的变量作为成员变量,主要用于实现对int类型的包装并提供int类型到String类之间的转换等方法。

常用的常量:

public static final int MAX_VALUE 表示int类型可以描述的最大值,即2^31-1
public static final int MIN_VALUE 表示int类型可以描述的最小值,即-2^31
public static final int SIZE 表示int类型采用二进制补码形式的位数
public static final int BYTES 表示int类型所占的字节个数
public static final Class TYPE 表示int类型的Class实例

常用的方法:

Integer(int value) 根据参数指定的整数来构造对象(已过时)
Integer(String s) 根据参数指定的字符串来构造对象 (已过时)
int intValue() 获取调用对象中的整数值并返回
static Integer valueOf(int i) 根据参数指定整数值得到Integer类型对象,也可以传string
boolean equals(Object obj) 比较调用对象与参数指定的对象是否相等
String toString() 返回描述调用对象数值的字符串形式
static int parseInt(String s) 将字符串类型转换为int类型并返回
static String toString(int i) 获取参数指定整数的十进制字符串形式
static String toBinaryString(int i) 获取参数指定整数的二进制字符串形式
static String toHexString(int i) 获取参数指定整数的十六进制字符串形式
static String toOctalString(int i) 获取参数指定整数的八进制字符串形式

装箱和拆箱的概念:

在Java5发布之前使用包装类对象进行运算时,需要较为繁琐的“拆箱”和“装箱”操作;即运算前先将包装类对象拆分为基本类型数据,运算后再将结果封装成包装类对象。
从Java5开始增加了自动拆箱和自动装箱的功能。

自动装箱池:

在Integer类的内部提供了自动装箱池技术(vm cache,可以调优),将-128到127之间的整数已经装箱完毕,当程序中使用该范围之间的整数时,无需装箱直接取用自动装箱池中的对象即可,从而提高效率。Interger it = 100;   注意会自动装箱,超过128的会导致it1==it2为false,但数值相等。小于128的内存地址会一致。int ib = it;

Double类:

概述:

java.lang.Double类型内部包装了一个double类型的变量作为成员变量,主要用于实现对double类型的包装并提供double类型到String类之间的转换等方法。

常用的方法:

double doubleValue() 获取调用对象中的浮点数据并返回
static Double valueOf(double d) 根据参数指定浮点数据得到Double类型对象
static double parseDouble(String s) 将字符串类型转换为double类型并返回
boolean isNaN() 判断调用对象的数值是否为非数字。0/0.0为nan

装箱和拆箱:

Double d = 3.14;
double d2 = d;
没有装箱池

Boolean类:

概述;java.lang.Boolean类型内部包装了一个boolean类型的变量作为成员变量,主要用于实现对boolean类型的包装并提供boolean类型到String类之间的转换等方法。
常用的方法:boolean booleanValue() 获取调用对象中的布尔数值并返回static Boolean valueOf(boolean b) 根据参数指定布尔数值得到Boolean类型对象,内部调用parseBoolean方法static boolean parseBoolean(String s) 将字符串类型转换为boolean类型并返回,内部用的是"true".equalsIgnoreCase(s),其他情况都返回false
装箱和拆箱:类似

Character类:

概述:

主要用于实现对char类型的包装并提供字符类别的判断和转换等方法。

常用的方法:

char charValue() 获取调用对象中的字符数据并返回
static Character valueOf(char c) 根据参数指定字符数据得到Character类型对象
boolean equals(Object obj) 比较调用对象与参数指定的对象是否相等
String toString() 返回描述调用对象数值的字符串形式
static boolean isUpperCase(char ch) 判断参数指定字符是否为大写字符
static boolean isLowerCase(char ch) 判断参数指定字符是否为小写字符
static boolean isDigit(char ch) 判断参数指定字符是否为数字字符
static char toUpperCase(char ch) 将参数指定的字符转换为大写字符
static char toLowerCase(char ch) 将参数指定的字符转换为小写字符

数学处理类:

math类:

java.lang.Math类主要用于提供执行数学运算的方法,如:对数,平方根。
常用的方法:static int max(int a, int b) 返回两个参数中的最大值static int min(int a, int b) 返回两个参数中的最小值static double pow(double a, double b) 返回第一个参数的幂static int abs(int a) 返回参数指定数值的绝对值static long round(double a) 返回参数四舍五入的结果static double sqrt(double a) 返回参数的平方根static double random() 返回0.0到1.0的随机数

BigDecimal类:

概念:

由于float类型和double类型在运算时可能会有误差,若希望实现精确运算则借助java.math.BigDecimal类型加以描述。
常用的方法
BigDecimal(String val) 根据参数指定的字符串来构造对象
BigDecimal add(BigDecimal augend) 用于实现加法运算
BigDecimal subtract(BigDecimal subtrahend) 用于实现减法运算
BigDecimal multiply(BigDecimal multiplicand) 用于实现乘法运算
BigDecimal divide(BigDecimal divisor) 用于实现除法运算,注意:必须要除尽。否则抛出ArithmeticException。
BigDecimal divide​(BigDecimal divisor, int roundingMode)常用RoundingMode.HALF_UP,需要import

BigInteger类:

概念:

若希望表示比long类型范围还大的整数数据,则需要借助java.math.BigInteger类型描述。

常用的方法:

BigInteger(String val) 根据参数指定的字符串来构造对象
BigInteger add(BigInteger val) 用于实现加法运算
BigInteger subtract(BigInteger val) 用于实现减法运算
BigInteger multiply(BigInteger val) 用于实现乘法运算
BigInteger divide(BigInteger val) 用于实现除法运算
BigInteger remainder(BigInteger val) 用于实现取余运算
BigInteger[] divideAndRemainder(BigInteger val) 用于实现取商和余数的运算

String类:

概念(重点):

1.java.lang.String类用于描述字符串,Java程序中所有的字符串字面值都可以使用该类的对象加以描述,如:"abc"。只有String可以new + 字面值
2.该类由final关键字修饰,表示该类不能被继承。
3.从jdk1.9开始该类的底层不使用char[]来存储数据,而是改成 byte[](1个字节)加上编码标记,从而节约了一些空间。
4.该类描述的字符串内容是个常量不可更改,因此可以被共享使用。如:String str1 = “abc”; - 其中"abc"这个字符串是个常量不可改变。在方法区(常量区)声明这个常量后,将地址返回给变量。str1 = “123”; - 将“123”字符串的地址赋值给变量str1。- 改变str1的指向并没有改变指向的内容

常量池的概念(原理):

由于String类型描述的字符串内容是常量不可改变,因此Java虚拟机将首次出现的字符串放入常量池中,若后续代码中出现了相同字符串内容则直接使用池中已有的字符串对象而无需申请内存及创建对象,
从而提高了性能。
特殊情况:"ab"和"a"+"b"是同一个,常量优化机制。常量是固定的。如果是其中一个是变量,则不是同一个地址。即String s1 = "abc";      String s2 = "a" + "bc";       s1==s2;而String s11 = "a";      String s22 = "bc";       String s6 = s11 + s22;    s1!=s6;
验证: 声明两个相同的字符串,比较两个变量的内存地址即可。
intern()方法:首先检查字符串池中是否有”abc”这个字符串,如果存在则返回这个字符串的引用,否则就将这个字符串添加到字符串池中,然会返回这个字符串的引用。

常用的构造方法:

String() 使用无参方式构造对象得到空字符序列
String(byte[] bytes, int offset, int length)使用bytes数组(ascii码)中下标从offset位置开始的length个字节来构造对象。原理:逐个翻译字节数字。
String(byte[] bytes) 使用bytes数组中的所有内容构造对象
String(char[] value, int offset, int count)使用value数组中下标从offset位置开始的count个字符来构造对象
String(char[] value) 使用value数组中的所有内容构造对象
String(String original)根据参数指定的字符串内容来构造对象,新创建对象为参数对象的副本。两个对象,一个在常量池,一个在堆区(new)。

常用的成员方法:

String toString() 返回字符串本身
byte[] getBytes() 将当前字符串内容转换为byte数组并返回      思路:拆分字符串为多个字符,然后转换为数字
char[] toCharArray() 用于将当前字符串内容转换为char数组并返回 char charAt(int index) 方法charAt用于返回字符串指定位置的字符。
int length() 返回字符串字符序列的长度。
boolean isEmpty() 判断字符串是否为空,底层判断长度。int compareTo(String anotherString) 用于比较调用对象和参数对象的大小关系。逐字符比较,返回的一个不同字符的ascii码数值相差。如果是长度不同,返回的一个长度减去第二个长度。
int compareToIgnoreCase(String str) 不考虑大小写,也就是'a'和'A'是相等的关系String concat(String str) 用于实现字符串的拼接。更简单用+
boolean contains(CharSequence s) 用于判断当前字符串是否包含参数指定的内容,CharSequence是接口,String实现了该接口
String toLowerCase() 返回字符串的小写形式
String toUpperCase() 返回字符串的大写形式
String trim() 返回去掉前导和后继空白的字符串
boolean startsWith(String prefix) 判断字符串是否以参数字符串开头
boolean startsWith(String prefix, int toffset) 从指定位置开始是否以参数字符串开头
boolean endsWith(String suffix) 判断字符串是否以参数字符串结尾boolean equals(Object anObject) 用于比较字符串内容是否相等并返回,逐字符比较。
int hashCode() 获取调用对象的哈希码值
boolean equalsIgnoreCase(String anotherString)用于比较字符串内容是否相等并返回,不考虑大小写,如:'A'和'a'是相等int indexOf(int ch)用于返回当前字符串中参数ch指定的字符第一次出现的下标,失败返回-1
int indexOf(int ch, int fromIndex) 用于从fromIndex位置开始查找ch指定的字符
int indexOf(String str)在字符串中检索str返回其第一次出现的位置(第一个字符的下标),若找不到返回-1
int indexOf(String str, int fromIndex)表示从字符串的fromIndex位置开始检索str第一次出现的位置
int lastIndexOf(int ch) 用于返回参数ch指定的字符最后一次出现的下标
int lastIndexOf(int ch, int fromIndex)用于从fromIndex位置开始反向查找ch指定字符出现的下标
int lastIndexOf(String str) 返回str指定字符串最后一次出现的下标
int lastIndexOf(String str, int fromIndex)用于从fromIndex位置开始反向搜索的第一次出现的下标。String substring(int beginIndex, int endIndex)返回字符串中从下标beginIndex(包括)开始到endIndex(不包括)结束的子字符串
String substring(int beginIndex)返回字符串中从下标beginIndex(包括)开始到字符串结尾的子字符串boolean matches(String regex)判断当前正在调用的字符串是否匹配参数指定的正则表达式规则String[] split(String regex)参数regex为正则表达式,以regex所表示的字符串为分隔符,将字符串拆分成字符串数组
String replace(char oldChar, char newChar)使用参数newChar替换此字符串中出现的所有参数oldChar
String replaceFirst(String regex,String replacement)替换此字符串匹配给定的正则表达式的第一个子字符串
String replaceAll(String regex,String replacement)将字符串中匹配正则表达式regex的字符串替换成replacement

字符串数字转整数:

int ib=0;
for(int i=0;i<str.length();i++){ib = ib*10+str2.charAt(i)-'0';
}
Interger.parseInt(string)也可以

整数转字符串:

String.valueof()
""+123

可变字符串类:

基本概念:

由于String类描述的字符串内容是个常量不可改变,当需要在Java代码中描述大量类似的字符串时,只能单独申请和存储,此时会造成内存空间的浪费。
为了解决上述问题,可以使用java.lang.StringBuilder类和java.lang.StringBuffer类来描述字符序列可以改变的字符串,如:"ab"。
StringBuffer类是从jdk1.0开始存在,属于线程安全的类,因此效率比较低。
StringBuilder类是从jdk1.5开始存在,属于非线程安全的类,效率比较高。
作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。
为什么修改本身,还有返回本身?为了连续调用。
如何在StringBuilder和String之间互转?toString方法构造方法
性能排行?string < stringBuffer < stringBuilder

常用的构造方法:

StringBuilder() 使用无参方式构造对象,容量为16
StringBuilder(int capacity) 根据参数指定的容量来构造对象,容量为参数指定大小
StringBuilder(String str) 根据参数指定的字符串来构造对象,容量为:16+字符串长度

常用的成员方法:

int capacity() 用于返回调用对象的容量
int length() 用于返回字符串的长度,也就是字符的个数
StringBuilder insert(int offset, String str)插入字符串并返回调用对象的引用,就是自己。原值也改变了(不同于String)。
StringBuilder append(String str) 追加字符串,还可以是CharSequence、StringBuffer等形参。
StringBuilder deleteCharAt(int index)将当前字符串中下标为index位置的单个字符删除
StringBuilder delete(int start,int end) 删除字符串
StringBuilder replace(int start,int end,String str)替换字符串
StringBuilder reverse() 字符串反转
还有charAt查找、indexOf、LastIndexOf、subString、setCharAt等。

容量的扩容算法:

底层采用byte[]来存放
ensureCapacityInternal(count + len);   确保容量往后满足。putStringAt(count, str);       放到count的位置。coutn表示当前char用了多少private void ensureCapacityInternal(int minimumCapacity) {int oldCapacity = value.length >> coder;       # value是底层byte数组。coder是编码相关,在utf16是1,在LATIN1是0if (minimumCapacity - oldCapacity > 0) {     # 容量不满足时,发生扩容value = Arrays.copyOf(value,newCapacity(minimumCapacity) << coder);      # 翻倍}
}
newCapacity方法具体算出新容量,utf16为最低长度的2倍或者2*旧容量+4,LATIN1是2*旧容量+2或者最低长度,具体配置通过COMPACT_STRINGS

时间:

System类:

基本概念:

Java.lang.System类中提供了一些有用的类字段和方法。

常用的方法:

static long currentTimeMillis()  返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差

Date类的概述:

基本概念:

java.util.Date类主要用于描述特定的瞬间,也就是年月日时分秒,可以精确到毫秒。

常用的方法:

Date() 使用无参的方式构造对象,也就是当前系统时间
Date(long date)根据参数指定毫秒数构造对象, 参数为距离1970年1月1日0时0分0秒的毫秒数
long getTime() 获取调用对象距离1970年1月1日0时0分0秒的毫秒数
void setTime(long time)设置调用对象为距离基准时间time毫秒的时间点

SimpleDateFormat类:

基本概念:

java.text.SimpleDateFormat类主要用于实现日期和文本之间的转换。

常用的方法:

SimpleDateFormat() 使用无参方式构造对象
SimpleDateFormat(String pattern)根据参数指定的模式来构造对象,模式主要有: y-年 M-月 d-日 H-时 m-分 s-秒
final String format(Date date)用于将日期类型转换为文本类型
Date parse(String source) 用于将文本类型转换为日期类型

Calendar类:

基本概念:

java.util.Calender类主要用于描述特定的瞬间,取代Date类中的过时方法实现全球化。
该类是个抽象类,因此不能实例化对象,其具体子类针对不同国家的日历系统,其中应用最广泛的是GregorianCalendar(格里高利历),对应世界上绝大多数国家/地区使用的标准日历系统。

常用的方法:

static Calendar getInstance()用于获取Calendar类型的引用
void set(int year, int month, int date, int hourOfDay, int minute, int second)用于设置年月日时分秒信息,month从0开始,需要减1
void setTime(Date date)
Date getTime()用于将Calendar类型转换为Date类型
void set(int field, int value) 设置指定字段的数值,Calendar.YEAR
void add(int field, int amount) 向指定字段增加数值

注意:

Calendar本身是abstract(有构造方法但只能super调用),为啥可以返回类型的对象?
该静态方法返回的是子类。
多态的场景之三:返回类型形成多态

Java8日期类:

由来:

JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar并不比Date好多少。
它们面临的问题是:Date类中的年份是从1900开始的,而月份都从0开始。格式化只对Date类有用,对Calendar类则不能使用。非线程安全等。

概述:

Java 8通过发布新的Date-Time API来进一步加强对 日期与时间的处理。
java.time包:该包日期/时间API的基础包。
java.time.chrono包:该包提供对不同日历系统的访问。
java.time.format包:该包能够格式化和解析日期时间对象。
java.time.temporal包:该包包含底层框架和扩展特性。
java.time.zone包:该包支持不同时区以及相关规则的类。

LocalDate类:

基本概念:

java.time.LocalDate类主要用于描述年-月-日格式的日期信息,该类不表示时间和时区信息。

常用的方法:

static LocalDate now() 在默认时区中从系统时钟获取当前日期

LocalTime类:

基本概念:

java.time.LocalTime 类主要用于描述时间信息,可以描述时分秒以及纳秒。

常用的方法:

static LocalTime now() 从默认时区的系统时间中获取当前时间
static LocalTime now(ZoneId zone) 获取指定时区的当前时间

LocalDateTime类:

基本概念:

java.time.LocalDateTime类主要用于描述ISO-8601日历系统中没有时区的日期时间,如2007-12-03T10:15:30。

常用的方法:

static LocalDateTime now()从默认时区的系统时间中获取当前日期时间
static LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second)根据参数指定的年月日时分秒信息来设置日期时间
static LocalDateTime LocalDateTime.ofInstant(Instant instant, ZoneId zone)根据Instant来设置日期时间
int getYear() 获取年份字段的数值
int getMonthValue() 获取1到12之间的月份字段
int getDayOfMonth() 获取日期字段
int getHour() 获取小时数
int getMinute() 获取分钟数
int getSecond() 获取秒数
LocalDateTime withYear(int year) 设置为参数指定的年,返回的是同类型新对象,和String类型相似
LocalDateTime withSecond(int second) 设置为参数指定的秒
LocalDateTime plusYears(long years) 加上参数指定的年
LocalDateTime plusSeconds(long seconds) 加上参数指定的秒
LocalDateTime minusYears(long years) 减去参数指定的年
LocalDateTime minusSeconds(long seconds) 减去参数指定的秒

Instant类:

基本概念:

java.time.Instant类主要用于描述瞬间的时间点信息。

常用的方法:

static Instant now() 从系统时钟上获取当前时间,utc时区
OffsetDateTime atOffset(ZoneOffset offset)将此瞬间与偏移量组合以创建偏移日期时间,ZoneOffset.ofHours(8)
static Instant ofEpochMilli(long epochMilli)根据参数指定的毫秒数来构造对象,参数为距离1970年1月1日0时0分0秒的毫秒数
long toEpochMilli() 获取距离1970年1月1日0时0分0秒的毫秒数

DateTimeFormatter类:

基本概念:

java.time.format.DateTimeFormatter类主要用于格式化和解析日期。

常用的方法:

static DateTimeFormatter ofPattern(String pattern) 根据参数指定的模式来获取对象
String format(TemporalAccessor temporal) 将参数指定日期时间转换为字符串,TemporalAccessor是接口,可以传Instant、LocalDateTime
TemporalAccessor parse(CharSequence text) 将参数指定字符串转换为日期时间

集合类库:

集合:

由来:

当需要在Java程序中记录单个数据内容时,则声明一个变量。
当需要在Java程序中记录多个类型相同的数据内容时,声明一个一维数组。
当需要在Java程序中记录多个类型不同的数据内容时,则创建一个对象。
当需要在Java程序中记录多个类型相同的对象数据时,创建一个对象数组。
当需要在Java程序中记录多个类型不同的对象数据时,则准备一个集合。

集合的框架结构:

Java中集合框架顶层框架是:java.util.Collection集合 和 java.util.Map集合。
其中Collection集合中存取元素的基本单位是:单个元素。
其中Map集合中存取元素的基本单位是:单对元素。

关系图:

                     collection                                                             Mapset                               List          queue                                                      SortedMapSortedSet
HashSet LinkedSet TreeSet    ArrayList Vector LinkedList  PriorityQueue         HashMap  LinkedHashMap  HashMap  TreeMap

Collection集合:

基本概念:

java.util.Collection接口是List接口、Queue 接口以及Set接口的父接口,因此该接口里定义的方法
既可用于操作List集合,也可用于操作Queue集合和Set集合。

常用的方法:

boolean add(E e); 向集合中添加对象
boolean addAll(Collection<? extends E> c)用于将参数指定集合c中的所有元素添加到当前集合中
boolean contains(Object o); 判断是否包含指定对象,原理是Objects.equals(o, e),这句话相当于1.判断null,2.判断自身,3.该类型的方法e.equals(o)
boolean containsAll(Collection<?> c) 判断是否包含参数指定的所有对象
boolean retainAll(Collection<?> c) 保留当前集合中存在且参数集合中存在的所有对象,交集。如果此集合因调用而更改就返回true。取代原集合。
boolean remove(Object o); 从集合中删除对象,成功返回true。多个只删除一个,原理是删除元素e ,使其为Objects.equals(o, e)
boolean removeAll(Collection<?> c) 从集合中删除参数指定的所有对象,改变了就返回true
void clear(); 清空集合
int size(); 返回包含对象的个数
boolean isEmpty(); 判断是否为空,底层是判断size==0
boolean equals(Object o) 判断是否相等
int hashCode() 获取当前集合的哈希码值
Object[] toArray() 将集合转换为数组,Arrays.asList(object数组)可以逆转
Iterator iterator() 获取当前集合的迭代器

Iterator接口:

基本概念:

java.util.Iterator接口主要用于描述迭代器对象,可以遍历Collection集合中的所有元素。
java.util.Collection接口继承Iterator接口,因此所有实现Collection接口的实现类都可以使用该迭代器对象。

常用的方法:

boolean hasNext() 判断集合中是否有可以迭代/访问的元素,
E next() 用于取出一个元素并指向下一个元素,位置会变化,不能多次遍历,可以再次调用iterator方法
void remove() 用于删除最后一个访问到的元素,这样可以防止超删。collection.remove(next获取到的obj)方法会发生并发修改异常。

应用:

官方的toString方法就是迭代拼接print

for each循环:

基本概念:

Java5推出了增强型for循环语句,可以应用数组和集合的遍历。是经典迭代的“简化版”。

底层原理:

迭代器

语法格式:

for(元素类型 变量名 : 数组/集合名称) {循环体;
}

执行流程:

不断地从数组/集合中取出一个元素赋值给变量名并执行循环体,直到取完所有元素为止。

List集合:

基本概念:

java.util.List集合是Collection集合的子集合,该集合中允许有重复的元素并且有先后放入次序。
该集合的主要实现类有:ArrayList类、LinkedList类、Stack类、Vector类。
ArrayList类的底层是采用动态数组进行数据管理的,支持下标访问,增删元素不方便。
LinkedList类的底层是采用双向链表进行数据管理的,访问不方便(内存不连续),增删元素方便。可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别,ArrayList更适合于随机访问而LinkedList更适合于插入和删除;在性能要求不是特别苛刻的情形下可以忽略这个差别。
Stack类的底层是采用动态数组进行数据管理的,该类主要用于描述一种具有后进先出特征的数据结构,叫做栈(last in first out LIFO)。
Vector类的底层是采用动态数组进行数据管理的,该类与ArrayList类相比属于线程安全的类,效率比较低,以后开发中基本不用。扩容一般为2倍类似StringBuilder和StringBuffer的区别。

ArrayList类:

概念:
底层是数组,但可以动态修改(有个临时变量)
遍历中删除:
1.使用for index++的方式,list.remove(e)会更新索引下标。解决:index要不增。或者倒序遍历。
2.使用高级for循环(增强for循环)遍历删除/增加操作for (type e : es ){// 如果break不会报错}                 }可能会报ConcurrentModificationException异常。
源码分析扩容:
1.声明new ArrayList();构造方法里
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
其中transient Object[] elementData; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 是一个长度为0的数组
2.add方法添加元素时,

首先:

判断长度是否满了s == elementData.length。

满了的情况下发生扩容:

判断是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是,先申请大小为10的object数组
否则,取1.5倍*旧容量或旧容量+1的最大值。// addAll的情况下就不是1了,而是参数集合的size。
线程不安全:
1.判断容量时,size可能都判断满足,但写入就越界了。elementData[size++] = e
2.有可能元素会被覆盖。elementData[size] = e;     //多个线程执行了这步。size = size + 1;
解决:
CopyOnWriteArrayList,写时,加锁,复制原数组,写入,再将原容器的引用指向新的setArray(newElements)。
适合读多写少的场景,缺点时是每次写时需要复制,容易GC。

LinkedList类:

常用方法:
void addFirst(E e)
void addLast(E e)
E removeFirst()
E removeLast()
源码分析add过程:
1.new时声明first和last变量
transient Node<E> first;
2.add方法会判断修改first和last,将旧的node.next指向新的last
常用的方法void add(int index, E element) 向集合中指定位置添加元素,与collect不同。boolean addAll(int index, Collection<? extends E> c) 向集合中添加所有元素E get(int index) 从集合中获取指定位置元素,返回类型为object,需要强转(但可能不是父子类型,发生类型转换异常)int indexOf(Object o) 查找参数指定的对象int lastIndexOf(Object o) 反向查找参数指定的对象E set(int index, E element) 修改指定位置的元素,返回原有元素E remove(int index) 删除指定位置的元素。注意for循环,size每次会减小(倒着删也可以);元素自动填充,后面的元素会补位。注意,本身还有remove(Object e)的方法,重载了。List subList(int fromIndex, int toIndex) 用于获取子List,共享内存空间。

Stack类:

常用方法:
boolean empty() 测试此堆栈是否为空。
E peek() 查看此堆栈顶部的对象,而不将其从堆栈中删除。
E pop() 移除此堆栈顶部的对象,并将该对象作为此函数的值返回。
E push​(E item) 将项目推到此堆栈的顶部。
int search​(Object o) 返回对象在此堆栈上的从1开始的位置。 

Queue集合:

基本概念:

java.util.Queue集合是Collection集合的子集合,与List集合属于平级关系。
该集合的主要用于描述具有先进先出特征的数据结构,叫做队列(first in first out FIFO)。
该集合的主要实现类是LinkedList类,因为该类在增删方面比较有优势。

常用的方法:

boolean offer(E e) 将一个对象添加至队尾,若添加成功则返回true
E poll() 从队首删除并返回一个元素
E peek() 返回队首的元素(但并不删除)

Set集合:

基本概念:

java.util.Set集合是Collection集合的子集合,与List集合平级。
该集合中元素没有先后放入次序,且不允许重复。
该集合的主要实现类是:HashSet类 和 TreeSet类以及LinkedHashSet类。
其中HashSet类的底层是采用哈希表进行数据管理的。
其中TreeSet类的底层是采用红黑树进行数据管理的。
其中LinkedHashSet类与HashSet类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。

常用的方法:

参考Collection集合中的方法即可!

元素放入HashSet集合的原理:

使用元素调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算出该元素在数组中的索引位置。
若该位置没有元素,则将该元素直接放入即可。
若该位置有元素,则使用新元素与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。若新元素与已有元素的哈希值相同,则使用新元素调用equals方法与已有元素依次比较。若相等则添加元素失败,否则将元素直接放入即可。

TreeSet集合:

概念:
由于TreeSet集合的底层采用红黑树进行数据的管理,当有新元素插入到TreeSet集合时,需要使用新元素与集合中已有的元素依次比较来确定新元素的合理位置。
元素默认从小到大排序了。
比较元素大小的规则有两种方式:使用元素的自然(自身)排序规则进行比较并排序,让元素类型实现java.lang.Comparable接口;(至少实现这个,不然报错ClassCastException)使用比较器规则进行比较并排序,构造TreeSet集合时传入java.util.Comparator接口;
自然排序的规则比较单一,而比较器的规则比较多元化,而且比较器优先于自然排序;
自然排序:
负数表示新对象小于参数对象。0表示不放入。
implements Comparable<Person>{}@Override
public int compareTo(Person o) {return this.getName().compareTo(o.getName());
}
比较器规则:
实现接口,选择匿名类(lambda表达式也可以)、实现接口的子类都可以。负数表示o1小于o2
Comparator<Person> comparator = new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return 0;}
};

Map集合:

基本概念:

java.util.Map<K,V>集合中存取元素的基本单位是:单对元素,其中类型参数如下:
K - 此映射所维护的键(Key)的类型,相当于目录。
V - 映射值(Value)的类型,相当于内容。
该集合中key是不允许重复的,而且一个key只能对应一个value。
该集合的主要实现类有:HashMap类、TreeMap类、LinkedHashMap类、Hashtable类、Properties类。
其中HashMap类的底层是采用哈希表进行数据管理的。
其中TreeMap类的底层是采用红黑树进行数据管理的。
其中LinkedHashMap类与HashMap类的不同之处在于内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代。
其中Hashtable类是古老的Map实现类,与HashMap类相比属于线程安全的类,且不允许null作为key或者value的数值。
其中Properties类是Hashtable类的子类,该对象用于处理属性文件,key和value都是String类型的。
Map集合是面向查询优化的数据结构, 在大数据量情况下有着优良的查询性能。经常用于根据key检索value的业务场景。

常用的方法:

V put(K key, V value)将Key-Value对存入Map,若集合中已经包含该Key,则替换该Key所对应的Value,返回值为该Key原来所对应的Value,若没有则返回null
V get(Object key) 返回与参数Key所对应的Value对象,如果不存在则返回null
boolean containsKey(Object key);判断集合中是否包含指定的Key
boolean containsValue(Object value);判断集合中是否包含指定的Value
V remove(Object key) 根据参数指定的key进行删除
Set keySet() 返回此映射中包含的键的Set视图
Collection values() 返回此映射中包含的值的Set视图
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射的Set视图,每个元素是Map.Entry<K,V>

HashMap集合:

java7和java8的变化:
链表数组 -> 链表超过一定长度后变为红黑树    TREEIFY_THRESHOLD
源码分析:
1.new声明,只是this.loadFactor = DEFAULT_LOAD_FACTOR。
2.put过程:先hash键,通过(h = key.hashCode()) ^ (h >>> 16)和(n - 1) & hash得到数组的索引位置i判断数组容量是否为0或者null,发生扩容。根据索引位置i找到元素:若该位置没有元素,则将该键值对直接放入即可。然后++modCount,超过thr时调用resize。若该位置有元素p,则使用key与已有元素依次比较key的哈希值若p.key调用equals方法与p相同,则将该元素的value直接赋值给该Node。若元素是TreeNode,调用putTreeVal方法若key不相同判断p.next是否为空,如果为空直接放到next,然后分析是否超过TREEIFY_THRESHOLD(默认是8),再决定是否建树treeifyBin(里面会先判断链表长度是否超过MIN_TREEIFY_CAPACITY,64,如果没超过就resize)。判断当前链表的Node的key是否相等,相等就更新值。
3.扩容过程:一开始容量和thr都为0,赋值为DEFAULT_INITIAL_CAPACITY=16,thr为16*0.75=12。初始化一个数组Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];容量和thr不为0的情况下,newCap = oldCap << 1和newThr = oldThr << 1都翻倍了。遍历旧数组得到e,先置空旧位置看e是否有next,没有通过e.hash & (newCap - 1)放到新位置如果是TreeNode,((TreeNode<K,V>)e).split(this, newTab, j, oldCap)否则,e有next,桶的情况,顺序放到j或者j + oldCap的位置一次转移完旧数据。
遍历:
EntrySet,只需要取一次。
缺点:
1.并发情况下扩容会出现死循环,因为要遍历链表将新元素加入新数组,遍历过程中不断将变量e.next指向newTable[i]。并发情况下有可能死循环。int i = indexFor(e.hash, newCapacity);e.next = newTable[i];   // 将当前元素指向上一个元素,相当于将原链表倒序了。newTable[i] = e;比如:线程A的e=1,next=3。还没执行e.next时挂起,线程B的e=3,next=5,newTable[i]=1,执行e.next=newTable[i],即3.next=1,newTable[i]=3,next=5这个时候线程A再执行,就会变为1.next=3,newTable[i]=1。形成死循环。解决:1.8通过head和tail,每次tail.next=e来保证顺序遍历链表,修复了。保证每个node永远执行下一个next,那么无论多线程执行在哪个环节,都不会出现问题。与运算解析:新数组下标:newTab[e.hash & (newCap - 1)] = e;  新容量为2倍,那么二进制最高位刚好多了一个1。hash在这个最高位有两种情况,1或者0。if ((e.hash & oldCap) == 0) 判断hash后的值是否在最高位有1。如果有1,说明新下标为j + oldCap。没有1,说明新下标还是j。
2.仍然存在并发数据丢失的问题(比如应该hash冲突的,但现在只有一个key了)。
ConcurrentHashMap:
1.7采用了分段锁技术,其中 Segment 继承于 ReentrantLock。能支持 N 个 Segment 这么多次数的并发。
1.8使用 如果为空CAS尝试插入(尝试需要依赖底层硬件来判断是否成功) + 不然synchronized。因为 jdk对 synchronized 优化是很到位的。

Collections类:

基本概念:

java.util.Collections类主要提供了对集合操作或者返回集合的静态方法。

常用的方法:

static <T extends Object & Comparable<? super T>> T
max(Collection<? extends T> coll)根据元素的自然顺序返回给定集合的最大元素
static T max(Collection<? extends T> coll, Comparator<? super T> comp)根据指定比较器引发的顺序返回给定集合的最大元素
static <T extends Object & Comparable<?super T>> T min(Collection<? extends T> coll)根据元素的自然顺序返回给定集合的最小元素
static T min(Collection<? extends T> coll, Comparator<? super T> comp)根据指定比较器引发的顺序返回给定集合的最小元素
static void copy(List<? super T> dest, List<? extends T> src)将一个列表中的所有元素复制到另一个列表中,需要dest的size大于src的。
static void reverse(List<?> list) 反转指定列表中元素的顺序
static void shuffle(List<?> list) 使用默认的随机源随机置换指定的列表
static <T extends Comparable<? super T>> void sort(List list)根据其元素的自然顺序将指定列表按升序排序
static void sort(List list, Comparator<? super T> c)根据指定比较器指定的顺序对指定列表进行排序
static void swap(List<?> list, int i, int j) 交换指定列表中指定位置的元素

泛型机制:

基本概念:

只能引用类型
通常情况下集合中可以存放不同类型的对象,是因为将所有对象都看做Object类型放入的,因此从集合中取出元素时也是Object类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。
为了避免上述错误的发生,从Java5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错。泛型只在编译时期有效,在运行时期不区分是什么类型。

本质:

参数化类型。
泛型参数T只存在于编译时,在编译后会被擦除。
泛型类型在逻辑上看以看成是多个不同的类型,实际上运行时,都是相同的底层基本类型。
在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

底层原理:

泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中E相当于形式参数负责占位,而使用集合时<>中的数据类型相当于实际参数,用于给形式参数E进行初始化,
从而使得集合中所有的E被实际参数替换,由于实际参数可以传递各种各样广泛的数据类型,因此得名为泛型。

示例:

List<String> l = new LinkedList<String>();
List<String> l = new LinkedList<>();           从java7开始的新特性:菱形特性,不用再写两遍

自定义泛型接口:

泛型接口和普通接口的区别就是后面添加了类型参数列表,可以有多个类型参数,如:<E, T, .. >等。

自定义泛型类:

泛型类和普通类的区别就是类名后面添加了类型参数列表,可以有多个类型参数,如:<E, T, .. >等。
实例化时没有指定类型时,默认是Object类型。
实例化泛型类时应该指定具体的数据类型,并且是引用数据类型而不是基本数据类型(基本类型可以用包装类)。
继承:父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。如果不保留泛型和没有指定泛型,则父类的T为默认Object。A extends B如果不保留泛型但指定了泛型,则父类的T为指定类型A extends B<String>可以保留泛型A<T> extends B<T>    子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。A<T,E> extends B<T>
示例: public class Person<T> {private T gender;}

自定义泛型方法:

介绍:

泛型方法就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。我们在调用这个泛型方法的时需要对泛型参数进行实例化或者static。

泛型方法的格式:

[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) { 方法体; }
在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法。
用到类的泛型参数的方法不能static

泛型方法的实现:

注意泛型方法只能实现,不能重写(接口没有这个概念),但可以重载。
必须实现<T extends father> void xxx的方法。
重载可以<T extends son> void xxx(T a),使用不用的泛型。(多态接口的变量不能使用重载的方法,因为接口没有定义,解决:泛型接口的继承,实现类可以指定具体子类)
触发:传入father对象时,触发第一个方法。传入son对象以及son的子类对象时,触发第二个方法,而且对象a的成员变量为son的值,而不是son的子类的值。传入otherSon对象时,仍然触发father的方法。

泛型方法的继承:

子类中可以对方法重写override,也可以重载overload。
重载示例:@Override<T extends father> void print(T a)  // 重写,只能保持泛形一模一样。<T extends son> void print(T a)       // 重载,如果没有用到参数T,那么会提示错误,因为两个方法擦除泛型后都是同样的方法,both methods have same erasure
重载后触发:与普通方法类似,根据传参来确定。

泛型在继承上的体现:

如果B是A的一个子类或子接口,而G是具有泛型声明的类或接口,则G<B>并不是G<A>的子类型!
比如:String是Object的子类,但是List<String>并不是List<Object>的子类。但是是List的子类。

通配符的使用:

概述:

有时候我们希望传入的类型在一个指定的范围内,此时就可以使用泛型通配符了。
如:之前传入的类型要求为Integer类型,但是后来业务需要Integer的父类Number类也可以传入。

原理:

理解可以看泛型的本质

泛型中有三种通配符形式:

<?> 无限制通配符:表示我们可以传入任意类型的参数。     作为函数参数的多态传递,允许多个具体泛型传进来。因为既没有extends,也没有super,所以:不允许调用set(T)方法并传入引用(null除外);不允许调用T get()方法并获取T引用(只能获取Object引用)。不支持元素的添加操作。对比List不加泛型这个父类,可以添加元素,但相当于去掉了泛型。可以取元素,返回Object类型特点:Pair<?>是所有Pair<T>的超类,可以安全地向上转型。<? extends E> 表示类型的上界是E,引用只能是E或者是E的子类。不支持添加操作(不确定具体子类型),支持获取,返回的是E类型List<? extends Fruit> flist = new ArrayList<Apple>();Fruit fruit = flist.get(0);用途:用于遍历list,里面包含不同类型的对象,返回接收的类型都是E。遍历的对象可以用于super存放<? super E> 表示类型的下界是E,引用只能是E或者是E的父类。可以添加父类最多到E的类型(因为这些类型可以向上强转),只能获取返回Object类型。List<? super Fruit> flist = new ArrayList<Fruit>();flist.add(new Fruit());flist.add(new Apple());用途:能够存放父类最多到E的不同类型的对象。

运行如何判断泛型参数E为某个类型:

如果入参类型不为E,目前没办法,可以先根据re的类型,用instanceof来判断// 通过反射获得泛型签名中的类型,貌似也只能获取继承于泛型类的当前类已确定的泛型interface A<T, ID> {  }  class B implements A<String, Integer> {  }
如果输入参数类型为E,那么可以根据方法一:传目标的类型Typepublic <E> E selectOneValue(String sql,Class<E> myClass) throws SQLException {}        int rs = conn.selectOneValue("select id from t_json",Integer.class)然后就可以用myClass.isAssignableFrom(String.class)来判断   // myClass为class java.lang.Integer//也可以将myClass通过new存入类变量中public class MyGenericClass<T> {Class<T> t;public static <E> MyGenericClass<E> createMyGeneric(Class<E> t){ //通过该方法返回的instance包含类变量t为输入类型return new MyGenericClass<E>(t);}public MyGenericClass(Class<T> t) {this.t=t;}public void out() {System.out.println(t);           //instance的其他方法即可使用变量t}}方法二:传目标的类型变量public <E> void doSomething(E a)a instanceof Integer[]a.getClass().isAssignableFrom(Integer[].class)

异常机制:

基本概念:

异常就是"不正常"的含义,在Java语言中主要指程序执行中发生的不正常情况。
java.lang.Throwable类是Java语言中错误(Error)和异常(Exception)的超类。
其中Error类主要用于描述Java虚拟机无法解决的严重错误,通常无法编码解决,如:JVM挂掉了等。
其中Exception类主要用于描述因编程错误或偶然外在因素导致的轻微错误,通常可以编码解决,如:0作为除数等。

异常的分类:

java.lang.Exception类是所有异常的超类(不能抛出),主要分为以下两种:

RuntimeException - 运行时异常,也叫作非检测性异常,运行后才可能抛出
IOException和其它异常 - 其它异常,也叫作检测性异常,所谓检测性异常就是指在编译阶段都能被编译器检测出来的异常。

其中RuntimeException类的主要子类:

ArithmeticException类 - 算术异常
ArrayIndexOutOfBoundsException类 - 数组下标越界异常
NullPointerException - 空指针异常
ClassCastException - 类型转换异常
NumberFormatException - 数字格式异常

注意:

当程序执行过程中发生异常但又没有手动处理时,则由Java虚拟机采用默认方式处理异常,而默认处理方式就是:打印异常的名称、异常发生的原因、异常发生的位置以及终止程序。

异常的捕获:

语法格式:

try {编写可能发生异常的代码;
}
catch(异常类型 引用变量名) {编写针对该类异常的处理代码;
}
...
finally {编写无论是否发生异常都要执行的代码;如果catch发生异常,也会执行。try或者catch的模块return前需要执行finally!使得finally可能修改返回值。
}

注意:

当需要编写多个catch分支时,切记小类型应该放在大类型的前面;
懒人的写法:catch(Exception e) {}
finally通常用于进行善后处理,如:关闭已经打开的文件等。

异常的抛出:

基本概念:

在某些特殊情况下有些异常不能处理或者不便于处理时,就可以将该异常转移给该方法的调用者,这种方法就叫异常的抛出。
当方法执行时出现异常,则底层生成一个异常类对象抛出,此时异常代码后续的代码就不再执行。

语法格式:

访问权限 返回值类型 方法名称(形参列表) throws 异常类型1,异常类型2,...{ 方法体; }
如:public void show() throws IOException {}

方法继承:

子类重写的方法不能抛出更大的异常、不能抛出平级不一样的异常,但可以抛出一样的异常、更小的异常以及不抛出异常。

规范:

若父类中被重写的方法没有抛出异常时,则子类中重写的方法只能进行异常的捕获处理。
若一个方法内部又以递进方式分别调用了好几个其它方法,则建议这些方法内可以使用抛出的方法处理到最后一层进行捕获方式处理。

自定义异常:

基本概念:

当需要在程序中表达年龄不合理的情况时,而Java官方又没有提供这种针对性的异常,此时就需要程序员自定义异常加以描述。

实现流程:

a.自定义xxxException异常类继承Exception类或者其子类。
b.提供两个版本的构造方法,一个是无参构造方法,另外一个是字符串作为参数的构造方法(字符串用于Exception父类)。

异常的产生:

throw new 异常类型(实参);
如:throw new AgeException("年龄不合理!!!");

异常的处理:

一个是向上throws,一个是就地处理(适合继承重写的方法,它的父类没有抛出异常)。

规范:

Java采用的异常处理机制是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。

File类:

基本概念:

java.io.File类主要用于描述文件或目录路径的抽象表示信息,可以获取文件或目录的特征信息,如:大小等。

常用的方法:

File(String pathname) 根据参数指定的路径名来构造对象
File(String parent, String child) 根据参数指定的父路径和子路径信息构造对象
File(File parent, String child) 根据参数指定的父抽象路径和子路径信息构造对象
boolean exists() 测试此抽象路径名表示的文件或目录是否存在
String getName() 用于获取文件的名称
long length() 返回由此抽象路径名表示的文件的长度
long lastModified() 用于获取文件的最后一次修改时间,毫秒
String getAbsolutePath() 用于获取绝对路径信息
boolean delete() 用于删除文件,当删除目录时要求是空目录,不然返回true但还是删除失败
boolean createNewFile() 用于创建新的空文件
boolean mkdir() 用于创建目录
boolean mkdirs() 用于创建多级目录
File[] listFiles() 获取该目录下的所有内容,单层
boolean isFile() 判断是否为文件
boolean isDirectory() 判断是否为目录
File[] listFiles(FileFilter filter) 获取目录下满足筛选器的所有内容。new 一个匿名类,然后实现accept方法即可。

IO流:

概念:

IO就是Input和Output的简写,也就是输入和输出的含义。
IO流就是指读写数据时像流水一样从一端流到另外一端,因此得名为“流"。

基本分类:

按照读写数据的基本单位不同,分为 字节流 和 字符流。其中字节流主要指以字节为单位进行数据读写的流,可以读写任意类型的文件。其中字符流主要指以字符(2个字节)为单位进行数据读写的流,只能读写文本文件。
按照读写数据的方向不同,分为 输入流 和 输出流(站在程序的角度)。其中输入流主要指从文件中读取数据内容输入到程序中,也就是读文件。其中输出流主要指将程序中的数据内容输出到文件中,也就是写文件。
按照流的角色不同分为节点流和处理流。其中节点流主要指直接和输入输出源对接的流。其中处理流主要指需要建立在节点流的基础之上的流。

体系结构:

                   IO流 字节流                                       字符流 InputStream               OutputStream                       Reader    Writer               -->      抽象类
FileInputStream         FileOutputStream                FileReader         FileWriter
BufferedInputStream     BufferedOutputStream           BufferedReader     BufferedWriter
DataInputStream         DataOutputStream              InputStreamReader  OutputStreamReader
ObjectInputStream       ObjectOutputStream                                  PrintWriter             PrintStream

FileWriter类:

基本概念:

java.io.FileWriter类主要用于将文本内容写入到文本文件。

常用的方法:

FileWriter(String fileName) 根据参数指定的文件名构造对象,文件不存在会新建文件。会清空原内容。
FileWriter(String fileName, boolean append)以追加的方式根据参数指定的文件名来构造对象。文件不存在会新建文件。
void write(int c) 写入单个字符
void write(char[] cbuf, int off, int len)将指定字符数组中从数组偏移量off开始的len个字符写入此文件输出流
void write(char[] cbuf)将cbuf.length个字符从指定字符数组写入此文件输出流中
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

FileReader类:

基本概念:

java.io.FileReader类主要用于从文本文件读取文本数据内容。

常用的方法:

FileReader(String fileName)根据参数指定的文件名构造对象
int read() 读取单个字符的数据并返回,返回-1表示读取到末尾
int read(char[] cbuf, int offset, int length)从输入流中将最多len个字符的数据读入一个字符数组下标offset开始的位置中,返回读取到的字符个数,返回-1表示读取到末尾
int read(char[] cbuf)从此输入流中将最多 cbuf.length 个字符的数据读入字符数组中,返回读取到的字符个数,返回-1表示读取到末尾
void close() 关闭流对象并释放有关的资源

FileOutputStream类:

基本概念:

java.io.FileOutputStream类主要用于将图像数据之类的原始字节流写入到输出流中。

常用的方法:

FileOutputStream(String name) 根据参数指定的文件名来构造对象
FileOutputStream(String name,boolean append)以追加的方式根据参数指定的文件名来构造对象
void write(int b) 将指定字节写入此文件输出流
void write(byte[] b, int off, int len)将指定字节数组中从偏移量off开始的len个字节写入此文件输出流
void write(byte[] b)将 b.length 个字节从指定字节数组写入此文件输出流中
void flush() 刷新此输出流并强制写出任何缓冲的输出字节
void close() 关闭流对象并释放有关的资源

FileInputStream类(重点):

基本概念:

java.io.FileInputStream类主要用于从输入流中以字节流的方式读取图像数据等。

常用的方法:

FileInputStream(String name)根据参数指定的文件路径名来构造对象
int read() 从输入流中读取单个字节的数据并返回,返回-1表示读取到末尾
int read(byte[] b, int off, int len)从此输入流中将最多len个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾
int read(byte[] b)从此输入流中将最多 b.length 个字节的数据读入字节数组中,返回读取到的字节个数,返回-1表示读取到末尾
void close() 关闭流对象并释放有关的资源
int available() 获取输入流所关联文件的大小

写文件:

write(int b)太慢,write(byte[] b)场景不通用,write(arr,0,res=fis.read(barr))适合将读出的长度写入。

BufferedOutputStream类(重点):

基本概念:

java.io.BufferedOutputStream类主要用于描述缓冲输出流,此时不用为写入的每个字节调用底层系统(减小io)。
处理流。
多了一层缓冲区,自己可以write单个字节(仍然慢),或者字节数组write(快很多)都可以。

常用的方法:

BufferedOutputStream(OutputStream out) 根据参数指定的引用来构造对象,默认8192
BufferedOutputStream(OutputStream out, int size)根据参数指定的引用和缓冲区大小来构造对象
void write(int b) 写入单个字节
void write(byte[] b, int off, int len) 写入字节数组中的一部分数据
void write(byte[] b) 写入参数指定的整个字节数组
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

BufferedInputStream类(重点):

基本概念:

java.io.BufferedInputStream类主要用于描述缓冲输入流。

常用的方法:

BufferedInputStream(InputStream in) 根据参数指定的引用构造对象,默认8192
BufferedInputStream(InputStream in, int size) 根据参数指定的引用和缓冲区大小构造对象
int read() 读取单个字节
int read(byte[] b, int off, int len) 读取len个字节到数组中
int read(byte[] b) 读取b.length个字节
void close() 关闭流对象并释放有关的资源

BufferedWriter类(重点):

基本概念:

java.io.BufferedWriter类主要用于写入单个字符、字符数组以及字符串到输出流中。

常用的方法:

BufferedWriter(Writer out) 根据参数指定的引用来构造对象
BufferedWriter(Writer out, int sz) 根据参数指定的引用和缓冲区大小来构造对象
void write(int c) 写入单个字符到输出流中
void write(char[] cbuf, int off, int len)将字符数组cbuf中从下标off开始的len个字符写入输出流中
void write(char[] cbuf) 将字符串数组cbuf中所有内容写入输出流中
void write(String s, int off, int len) 将参数s中下标从off开始的len个字符写入输出流中
void write(String str) 将参数指定的字符串内容写入输出流中
void newLine() 用于写入行分隔符到输出流中
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

BufferedReader类(重点):

基本概念:

java.io.BufferedReader类用于从输入流中读取单个字符、字符数组以及字符串。

常用的方法:

BufferedReader(Reader in)根据参数指定的引用来构造对象。比如new BufferedReader(new InputStreamReader(System.in))输入。
BufferedReader(Reader in, int sz)根据参数指定的引用和缓冲区大小来构造对象
int read()从输入流读取单个字符,读取到末尾则返回-1,否则返回实际读取到的字符内容
int read(char[] cbuf, int off, int len)从输入流中读取len个字符放入数组cbuf中下标从off开始的位置上,若读取到末尾则返回-1,否则返回实际读取到的字符个数
int read(char[] cbuf) 从输入流中读满整个数组cbuf
String readLine() 读取一行字符串并返回,返回null表示读取到末尾
void close() 关闭流对象并释放有关的资源

示例:

new BufferedReader(new InputStreamReader(new FileInputStream(filename),"iso-8859-1"));
new BufferedReader(new InputStreamReader(System.in))输入。
new BufferedReader(new FileReader(file.getName()))

PrintStream类:

基本概念:

java.io.PrintStream类主要用于更加方便地打印各种数据内容。

常用的方法:

PrintStream(OutputStream out) 根据参数指定的引用来构造对象
void print(String s) 用于将参数指定的字符串内容打印出来
void println(String x) 用于打印字符串后并终止该行
void flush() 刷新流
void close() 用于关闭输出流并释放有关的资源

PrintWriter类:

基本概念:

java.io.PrintWriter类主要用于将对象的格式化形式打印到文本输出流。

常用的方法:

PrintWriter(Writer out) 根据参数指定的引用来构造对象
void print(String s) 将参数指定的字符串内容打印出来
void println(String x) 打印字符串后并终止该行
void flush() 刷新流
void close() 关闭流对象并释放有关的资源

OutputStreamWriter类:

基本概念:

java.io.OutputStreamWriter类主要用于实现从字节流到字符流的转换。

常用的方法:

OutputStreamWriter(OutputStream out) 根据参数指定的引用来构造对象
OutputStreamWriter(OutputStream out, String charsetName)根据参数指定的引用和编码构造对象
void write(String str) 将参数指定的字符串写入
void flush() 刷新流
void close()用于关闭输出流并释放有关的资源

InputStreamReader类:

基本概念:

java.io.InputStreamReader类主要用于实现从字节流到字符流的转换。

常用的方法:

InputStreamReader(InputStream in) 根据参数指定的引用来构造对象
InputStreamReader(InputStream in, String charsetName)根据参数指定的引用和编码来构造对象
int read(char[] cbuf) 读取字符数据到参数指定的数组
void close() 用于关闭输出流并释放有关的资源

字符编码:

编码表的由来:

计算机只能识别二进制数据,早期就是电信号。为了方便计算机可以识别各个国家的文字,就需要
将各个国家的文字采用数字编号的方式进行描述并建立对应的关系表,该表就叫做编码表。

常见的编码表:

ASCII:美国标准信息交换码, 使用一个字节的低7位二位进制进行表示。
ISO8859-1:拉丁码表,欧洲码表,使用一个字节的8位二进制进行表示。
GB2312:中国的中文编码表,最多使用两个字节16位二进制为进行表示。
GBK:中国的中文编码表升级,融合了更多的中文文字符号,最多使用两个字节16位二进制位表示。(最高位1表示两个字节,0表示一个字节)
Unicode:国际标准码,融合了目前人类使用的所有字符,为每个字符分配唯一的字符码。所有的文字都用两个字节16位二进制位来表示。

编码的发展:

面向传输的众多 UTF(UCS Transfer Format)标准出现了,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码并使编码无国界,这样就可以显示全世界上所有文化的字符了。
Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。
UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。(0表示一个字节,110表示两个,1110表示三个)

DataOutputStream类:

基本概念:

java.io.DataOutputStream类主要用于以适当的方式将8种基本数据类型写入输出流中。

常用的方法:

DataOutputStream(OutputStream out)根据参数指定的引用构造对象 OutputStream类是个抽象类,实参需要传递子类对象
void writeInt(int v)用于将参数指定的整数一次性写入输出流,优先写入高字节,4个字节全写入,int写入后转换为char
void write(int v)两者不同,只写入一个字节。
void close() 用于关闭文件输出流并释放有关的资源

DataInputStream类:

基本概念:

java.io.DataInputStream类主要用于从输入流中读取基本数据类型的数据

常用的方法:

DataInputStream(InputStream in)根据参数指定的引用来构造对象 InputStream类是抽象类,实参需要传递子类对象
int readInt() 用于从输入流中一次性读取一个整数数据并返回,没有或者不对的话返回EOFException。
void close() 用于关闭文件输出流并释放有关的资源

ObjectOutputStream类(重点):

基本概念:

java.io.ObjectOutputStream类主要用于将一个对象的所有内容整体写入到输出流中。
只能将支持 java.io.Serializable 接口的对象写入流中。
类通过实现 java.io.Serializable 接口以启用其序列化功能。
所谓序列化主要指将一个对象需要存储的相关信息有效组织成字节序列的转化过程。

常用的方法:

ObjectOutputStream(OutputStream out) 根据参数指定的引用来构造对象
void writeObject(Object obj) 用于将参数指定的对象整体写入到输出流中
void close() 用于关闭输出流并释放有关的资源

示例:

public class User implements java.io.Serializable {@Serialprivate static final long serialVersionUID = -6302068608321621724L;private String name;
}

ObjectInputStream类(重点):

基本概念:

java.io.ObjectInputStream类主要用于从输入流中一次性将对象整体读取出来。
所谓反序列化主要指将有效组织的字节序列恢复为一个对象及相关信息的转化过程。

常用的方法:

ObjectInputStream(InputStream in)根据参数指定的引用来构造对象
Object readObject()主要用于从输入流中读取一个对象并返回 无法通过返回值来判断是否读取到文件的末尾
void close() 用于关闭输入流并释放有关的资源

序列化版本号:

序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,
如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。

transient关键字:

transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。

经验的分享:

当希望将多个对象写入文件时,通常建议将多个对象放入一个集合中,然后将集合这个整体看做一个对象写入输出流中,此时只需要调用一次readObject方法就可以将整个集合的数据读取出来,
从而避免了通过返回值进行是否达到文件末尾的判断。

RandomAccessFile类:

基本概念:

java.io.RandomAccessFile类主要支持对随机访问文件的读写操作。

常用的方法:

RandomAccessFile(String name, String mode)根据参数指定的名称和模式构造对象r: 以只读方式打开rw:打开以便读取和写入rwd:打开以便读取和写入,同步文件内容的更新rws:打开以便读取和写入,同步文件内容和元数据的更新
int read() 读取单个字节的数据
void seek(long pos)用于设置从此文件的开头开始测量的文件指针偏移量,字节单位
void write(int b) 将参数指定的单个字节写入,如果存在会覆盖。
void close() 用于关闭流并释放有关的资源

多线程:

线程的创建:

Thread类的概念:

java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

创建方式:

1.继承Thread类
概述:
自定义类继承Thread类并重写run方法,然后创建该类的对象调用start方法(run方法调用相当于普通成员方法调用,start才是新线程)。
示例:
test extends Thread
new test().start();
优缺点:
继承Thread类的方式代码简单,但是若该类继承Thread类后则无法继承其它类。
匿名内部类的方式:
new Thread() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}}.start();
lambda方式:
     new Thread(() -> System.out.println(Thread.currentThread().getName())).start();同样
2.实现Runnable接口
概述:
自定义类实现Runnable接口并重写run方法,创建该类的对象作为实参来构造Thread类型的对象,然后使用Thread类型的对象调用start方法(target执行run方法)。
示例:
new Thread(new Runnable() {} ).start()
优缺点:
Runnable接口的方式代码复杂,但不影响该类继承其它类以及实现其它接口。
匿名内部类的方式:
new Thread(new Runnable() {      //区别在于此,有了实参@Overridepublic void run() {System.out.println();}}) {}.start();        // 可以重新定义run方法。线程会执行该方法。
lambda方式:
new Thread(()->{System.out.println();}).start();      // {}可以省略。Runnable是函数式接口
3.实现Callable接口:
概述:
从Java5开始新增加创建线程的第三种方式为实现java.util.concurrent.Callable接口。
重写call方法,然后将对象作为实参构造FutureTask类型的对象,再将future对象传入Thread类,调用start方法。
函数式接口
常用的方法:
V call() 计算结果并返回
FutureTask类:

概念:

java.util.concurrent.FutureTask类用于描述可取消的异步计算,该类提供了Future接口的基本实现,包括启动和取消计算、查询计算是否完成以及检索计算结果的方法,
也可以用于获取方法调用后的返回结果。
常用的方法
FutureTask(Callable callable) 根据参数指定的引用来创建一个未来任务
V get() 获取call方法计算的结果

使用:

FutureTask实现了Runnable接口,用Thread封装一下,然后start()
lambda表达式:
new Thread(new FutureTask(()-> {// logicreturn 1;})).start();

相关的方法:

Thread() 使用无参的方式构造对象,成员变量target为null,run方法会判断跳过执行。所以需要继承并重写run方法自定义功能。
Thread(String name) 根据参数指定的名称来构造对象
Thread(Runnable target)根据参数指定的引用来构造对象,其中Runnable是个接口类型
Thread(Runnable target,String name)根据参数指定引用和名称来构造对象
void run()若使用Runnable引用构造了线程对象,调用该方法时最终调用接口中的版本,若没有使用Runnable引用构造线程对象,调用该方法时则啥也不做
void start() 用于启动线程,Java虚拟机会自动调用该线程的run方法

线程的生命周期:

状态:

新建状态NEW - 使用new关键字创建之后进入的状态,此时线程并没有开始执行。
就绪状态READY - 调用start方法后进入的状态,此时线程还是没有开始执行。
运行状态RUNNING - 使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。
消亡状态TERMINATED - 当线程的任务执行完成后进入的状态,此时线程已经终止。
阻塞状态BLOCKED - 当线程执行的过程中发生了阻塞事件进入的状态,如:lock方法。阻塞状态解除后进入就绪状态。
等待(WAITING) - 进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
超时等待(TIMED_WAITING) - 该状态不同于WAITING,它可以在指定的时间后自行返回。

流转图:

线程的编号和名称:

long getId() 获取调用对象所表示线程的编号
String getName() 获取调用对象所表示线程的名称
void setName(String name) 设置/修改线程的名称为参数指定的数值
static Thread currentThread() 获取当前正在执行线程的引用,可以用于main方法和接口,获取当前主线程。

常用的方法:

static void yield()当前线程让出处理器(离开Running状态),使当前线程进入Runnable状态等待
static void sleep(times)使当前线程从 Running 放弃处理器进入Block状态, 休眠times毫秒, 再返回到Runnable。如果其他线程打断当前线程的Block(sleep), 就会发生InterruptedException。
void stop()停止线程,该方法已过时。
int getPriority() 获取线程的优先级
void setPriority(int newPriority)修改线程的优先级。优先级越高(数字越大)的线程不一定先执行,但该线程获取到时间片的机会会更多一些
void join() 主线程等待该线程start终止。默认主线程不会等待子线程start()执行完才执行下一步。
void join(long millis) 等待参数指定的毫秒数
boolean isDaemon() 用于判断是否为守护线程,默认不是
void setDaemon(boolean on)用于设置线程为守护线程

线程同步机制:

基本概念:

当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。
多个线程并发读写同一个临界资源时会发生线程并发安全问题。
异步操作:多线程并发的操作,各自独立运行。
同步操作:多线程串行的操作,先后执行的顺序。

synchronized加锁:

概述:
在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性,具体
同步代码块的方式:
格式如下:
synchronized(类类型的引用) {编写所有需要锁定的代码;
}
其中类类型的引用要求唯一,一般创建一个class demo{},然后实例化放到成员变量,传递给synchronized。
注意:
对于继承Thread,如果实例化了两个子类对象,那么锁的只是本对象,因为成员变量独立,解决是加static关键字。
对于实现接口Runnable,传给Thread的对象如果是一个,那么可以实现锁
同步方法的方式:
直接使用synchronized关键字来修饰整个方法即可,注意不同实例对象之间独立。
该方式等价于:synchronized(this) {             //this是当前调用对象。要注意是否this一致。整个方法体的代码 }
可以加static来锁类对象。对于重写方法比如run方法不方便加static,可以分离方法,然后调用传参。synchronized(类名.class)
静态方法的锁定:
1.当我们对一个静态方法加锁,如:
public synchronized static void xxx(){….}
那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
2.静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。
注意事项:
1.使用synchronized保证线程同步应当注意:
多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
2.在使用同步块时应当尽量减少同步范围以提高并发的执行效率。
底层实现:
Java虚拟机中的同步是通过显式(通过使用监视器输入和监视器输出指令)或隐式(通过方法调用和返回指令)的监视器monitor输入和退出来实现的。 显示就是使用monitorenter和monitorexit来控制同步代码块;隐式是修饰方法,在运行时常量池中通过ACC_SYNCHRONIZED来标志。
任何一个对象都有一个Monitor与之关联。
Monitor 是依靠底层操作系统的 Mutex Lock 来实现互斥的。
如果线程调用 wait() 方法,就会释放当前持有的 Mutex,并且该线程会进入 WaitSet 集合中,等待下一次被唤醒。如果当前线程顺利执行完方法,也将释放 Mutex。
synchronized 和 volatile 修饰符的比较:
transient 序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
偏向锁、轻量级锁和重量级锁:
背景:
synchronized底层原理是互斥获取对象的monitor。
Monitor 依赖于底层操作系统的实现,存在用户态和内核态的转换,所以增加了性能开销。
同时线程等待阻塞,切换也需要性能消耗。
偏向锁:

概述:

偏向锁主要用来优化同一线程多次申请同一个锁的竞争。可能大部分时间一个锁都是被一个线程持有和竞争。假如一个锁被线程 A 持有,后释放;接下来又被线程 A 持有、释放……如果使用 monitor,则每次都会发生用户态和内核态的切换,性能低下。

流程:

1.当对象被当做同步锁并有一个线程抢到了锁时,锁标志位还是 01,“是否偏向锁”标志位设置为 1,并且记录抢到锁的线程 ID,表示进入偏向锁状态。
2.当一个线程再次访问这个同步代码或方法时,该线程只需去对象头的 Mark Word 判断是否有偏向锁指向它的 ID,无需再进入 Monitor 去竞争对象了。
3.一旦出现其它线程竞争锁资源,偏向锁就会被撤销。撤销时机是在全局安全点,暂停持有该锁的线程,同时坚持该线程是否还在执行该方法。是则升级锁;不是则被其它线程抢占。在高并发场景下,大量线程同时竞争同一个锁资源,偏向锁肯定会被撤销。开启偏向锁会带来更大的性能开销,所以可以优化关闭。-XX:-UseBiasedLocking //关闭偏向锁(默认打开)-XX:+UseHeavyMonitors  //设置重量级锁

适合场景:

同一线程多次申请同一个锁
轻量级锁:

概述:
如果另一线程竞争锁,由于这个锁已经是偏向锁,则判断对象头的 Mark Word 的线程 ID 不是自己的线程 ID,就会进行 CAS 操作获取锁:

成功,直接替换 Mark Word 中的线程 ID 为当前线程 ID,该锁更新为轻量级锁定状态。
失败,自旋失败一定次数后,偏向锁会升级为重量级锁。

适合场景:

不存在锁竞争的场景(交替)
或者存在锁竞争但不激烈,仍然可以用自旋锁优化

volatile关键字:

背景:
在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。
原子性:Atomic包,原理是CAS+version,比如可以借助硬件的多核Cache一致性协议MESIsynchronized和Lock
可见性:线程1对变量i修改了之后(仍处于CPU高速缓存中),线程2没有立即看到线程1修改的值。volatile、synchronized和Lock
有序性:CPU会对指令重排序,确保单个线程的重排序不会影响执行的结果。但多线程仍然可能会影响。解决:volatile可以保证一定的有序性。在进行指令优化时,不能将在对volatile变量访问(赋值)前面的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。系统将不允许对 写入一个volatile变量的操作与其之前的任何读写操作 重新排序,也不允许将 读取一个volatile变量的操作与其之后的任何读写操作 重新排序。synchronized和Lock。
概述:
修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。(可见性)
有序性
作用:
保证可见性、有序性
和 CAS 结合,保证了复合操作的原子性
修饰long和double可以保证其读写单个操作原子性。(否则,在操作的时候,可以分成两步,每次对32位操作。)
限制:
volatile只能针对基础类型,如果是引用类型,不过只是一个指向数组的引用,而不是整个数组。
意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了。
实践:
1.单例模式Double-Check的解决
as-if-serial规则:
概述:
不管怎么重排序,单线程下的执行结果不能被改变。
编译器、runtime和处理器都必须遵守as-if-serial语义。
内存屏障:

概述:
又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:

保证特定操作的执行顺序。
影响某些数据(或则是某条指令的执行结果)的内存可见性。

作用:

编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。
Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。
happens-before原则:
概述:
JVM底层的内存操作原则
可以保证正确同步的多线程程序的执行结果不被改变。
分类:
单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。
对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。

Atomic包:

概述:
在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断。
分类:
原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
解决 ABA 问题的原子类:AtomicMarkableReference(通过引入一个 boolean来反映中间有没有变过),AtomicStampedReference(通过引入一个 int 来累加来反映中间有没有变过)
原理:
主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,不断自旋。

线程安全类和不安全类:

StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。
Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。(线程安全通过synchronized)
Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。  

Locks(锁):

基本概念:
从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。
java.util.concurrent.locks包.Lock接口是控制多个线程对共享资源进行访问的工具。实现类:ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁。ReadWriteLock接口是读写锁实现类:ReentrantReadWriteLock,分别实现了ReadLock和WriteLock类,分别使用了共享锁和排它锁。StampedLock类:和ReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!示例:StampedLock stampedLock = new StampedLock();long stamp = stampedLock.tryOptimisticRead(); // 获得一个乐观读锁if (!stampedLock.validate(stamp)) { // 检查乐观读锁后是否有其他写锁发生//说明读取过程中有写入操作,因此可能读取到错误的数据stamp = stampedLock.readLock(); // 获取一个悲观读锁try {......  //重新读取数据} finally {stampedLock.unlockRead(stamp); // 释放悲观读锁}}
常用的方法:
ReentrantLock() 使用无参方式构造对象
void lock() 获取锁
void unlock() 释放锁
使用方式:
lock.lock()          // 确保lock方法和try之间没有可能的异常发生
try {...
} finally {lock.unlock()    // 这样unlock的时候不会对未加锁的对象解锁
}
底层原理:
AbstractQueuedSynchronizer类
底层是CAS乐观锁(尝试需要依赖底层硬件来判断是否成功),如果被请求的共享资源(volatile成员变量)空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制。把所有的请求线程构成一个CLH队列。而对该队列的操作均通过Lock-Free(CAS)操作。功能更强大。CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)
常见方法:isHeldExclusively、tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared等默认的实现都是CAS (compare and swap) + volatile 和 native 的逻辑
设计模式:模板方法模式自定义同步器时需要重写AQS提供的模板方法。
CAS缺点:1.ABA问题。加version解决。2.自旋问题。多次循环需要耗费CPU。3.范围不能灵活控制。只能针对某一个,而不是多个共享变量的,不能针对多个共享变量同时进行CAS操作,因为这多个变量之间是独立的,简单的把原子操作组合到一起,并不具备原子性。理解:即多层CAS的过程中,前面的CAS判断的变量可能发生了变化。
两种资源共享方式:
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock。
不同同步器争用共享资源的方式不同,在实现时只需要实现共享资源 state 的获取与释放方式即可
ReentrantLock相关策略:
插队策略:
公平策略下,只要队列里有线程已经在排队,就不允许插队。
非公平策略下:在等待队列的头结点是尝试获取写锁的线程的时候,不允许读锁插队,否则可能会出现写锁等待太久饥饿的情况写锁只有在当前没有任何其他线程持有读锁和写锁的时候,才能插队成功。
升降级策略:
只能从写锁降级为读锁,不能从读锁升级为写锁。
与synchronized方式的比较:
1.Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动释放。
2.Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
3.使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。功能更强大更灵活,竞争激烈时性能较好。
4.两者都是可重入锁。synchronized通过获取自增,释放自减的方式实现重入。可重入锁:1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。
优点:
1.可以使锁更公平(按照申请顺序,和底层结构队列有关)
2.可以使线程在等待锁的时候响应中断(lockInterruptibly方法)
3.可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间(tryLock(long timeout, TimeUnit unit)方法)
4.可以在不同的范围,以不同的顺序获取和释放锁

Semaphore信号量:

概述:
synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
控制并发的数量
示例:
Semaphore semaphore = new Semaphore(10);
semaphore.acquire();
semaphore.release();

CountDownLatch计数器:

概述:
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到count=0,再开始执行。
控制多线程等待。比如等待指定数量的结果。
实现原理:
基于AQS的state,自己实现了tryAcquireShared方法来不断自旋获取。
示例:
CountDownLatch latch = new CountDownLatch(threadNum);
latch.countDown();      //  其他线程执行
latch.await();          // 主线程等待

CyclicBarrier循环栅栏:

概述:
CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。
主要应用场景和 CountDownLatch 类似。
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await()方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
示例:
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,new TourGuideTask()); // 传递给子线程。
cyclicBarrier.await();      // 子线程执行到这步后等待其他线程,都到达后,执行TourGuideTask线程任务。然后子线程继续执行。
实现原理:
加lock
每个线程lock.condition await阻塞等待
判断count=0后,执行command,然后signalAll唤醒所有线程。
区别:
1.CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
2.调用CountDownLatch的countDown方法后,当前线程并不会阻塞,会继续往下执行;而调用CyclicBarrier的await方法,会阻塞当前线程,直到指定的线程全部都到达了指定点的时候,才能继续往下执行;
3.CountDownLatch方法比较少,操作比较简单,而CyclicBarrier提供的方法更多,比如能够通过getNumberWaiting(),isBroken()这些方法获取当前多个线程的状态,并且CyclicBarrier的构造方法可以传入barrierAction,指定当所有线程都到达时执行的业务功能;
4.CountDownLatch是不能复用的,而CyclicLatch是可以复用的。

Condition:

概述:
synchronized可以配合wait和notify实现线程在条件不满足时等待,条件满足时唤醒。(其中等待的时候自动释放synchronized)
ReentrantLock使用Condition对象来实现wait和notify的功能。(也会释放lock,唤醒时会等候获取lock)
方法:
await()      与此 Condition 相关联的锁被原子释放,并且出于线程调度目的,当前线程被禁用,并且处于休眠状态1.将当前线程添加到条件队列尾部(等待队列),链表。2.fullyRelease释放当前线程获取的锁(通过操作 state 的值)3.调用 park 阻塞挂起当前线程4.唤醒,放到同步队列,抢锁
signal()
signalAll()
实现原理:
Condition 只是一个抽象类,它的主要实现逻辑是在 AQS 的内部类 ConditionObject 实现的。
与 Object 类方法区别:
底层原理比较类似,都是借助于队列。
ReentrantLock 类可以唤醒指定条件的线程,而 object 的唤醒是随机的

Exchanger:

概述:
一个用于线程间协作的工具类,用于两个线程间交换数据。它提供了一个交换的同步点,在这个同步点两个线程能够交换数据。
交换数据是通过exchange方法来实现的,如果一个线程先执行exchange方法,那么它会同步等待另一个线程也执行exchange方法,这个时候两个线程就都达到了同步点,两个线程就可以交换数据。
示例:
final Exchanger exchanger = new Exchanger();
// 将Exchanger通过闭包或者参数传递给线程方法。
String data2 = (String) exchanger.exchange(data1);

ThreadLocal:

概述:
本质上是一个封装变量。
线程隔离。并发下安全。
示例:
ThreadLocal<Integer> num = new ThreadLocal<>();
Integer a = num.get();
num.set(1)
实现原理:
ThreadLocal封装类,封装变量,提供set和get方法访问变量、设置值。
底层存放线程各自的ThreadLocalMap字典, key为当前threadLocal对象的WeakReference类对象(弱引用),value为对应的值。
开放地址法节省存储指针的空间
WeakReference弱引用,使得key可以进行回收。
内存泄漏:
现象:
ThreadLocal 在没有外部强引用时,发生 GC 时会被回收,那么 ThreadLocalMap 中保存的 key 值就变成了 null,而 Entry 又被 threadLocalMap 对象引用,threadLocalMap 对象又被 Thread 对象所引用,那么当 Thread 一直不终结的话,value 对象就会一直存在于内存中,也就导致了内存泄漏,直至 Thread 被销毁后,才会被回收。
解决:
在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收。
m.remove(this);
应用场景:
1.线程间数据隔离,各线程的 ThreadLocal 互不影响。
2.方便同一个线程使用某一对象,避免不必要的参数传递。
3.全链路追踪中的 traceId 或者流程引擎中上下文的传递一般采用 ThreadLocal。
4.Spring 事务管理器采用了 ThreadLocal。
5.Spring MVC 的 RequestContextHolder 的实现使用了 ThreadLocal。
线程透传:
概述:
默认情况下,父子线程不能传递threadlocal变量
解决:
java.lang.InheritableThreadLocal可以传递。
其实现原理就是在创建子线程将父线程当前存在的本地线程变量拷贝到子线程的本地线程变量中。
问题:
一般情况下,都复用线程池里的线程,变量只会复制一次。
父子线程关系的ThreadLocal值传递已经没有意义。
解决:
TransmittableThreadLocal,可以把任务提交给线程池时的ThreadLocal值传递到任务执行时。
原理:任务运行时,重新获取了变量。
用法:1.TtlRunnable封装Runnable。2.ExecutorService封装:executorService = TtlExecutors.getTtlExecutorService(executorService)。3.使用Java Agent来修饰JDK线程池实现类。详见文档。

线程间同步通信:

Object类常用的方法:

void wait()用于使得线程进入等待状态,将当前线程放入wait set,直到其它线程调用notify()或notifyAll()方法。自动释放synchronized对象锁。ObjectMonitor对象中有两个队列,都用来保存ObjectWaiter对象,分别是_WaitSet 和 _EntrySet。
void wait(long timeout)用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止
void notify() 用于唤醒在此对象监视器上等待的单个线程。如果所有的线程都在此对象上(synchronized监控的对象)等待,那么只会选择一个线程,选择是任意性的,并在对实现做出决定时发生。只能被作为此对象监视器的所有者的线程来调用。一次只能有一个线程拥有对象的监视器。
void notifyAll() 用于唤醒等待的所有线程线程操作的wait()、notify()、notifyAll()方法只能在synchronized步控制方法或同步控制块内调用。
如果在非同步控制方法或控制块里调用,程序能通过编译,但运行的时候,将得到 IllegalMonitorStateException 异常。

轮询间隔执行:

synchronized + wait实现自动释放,这样另一方可以获取到锁。
synchronized + notify可以让另一方获取到锁后,唤醒自己。

场景:

生产者消费者模型,控制wait和notify的执行速率即可。

线程池:

概念:

线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
解决了创建线程的消耗。

相关类和方法:

从Java5开始提供了线程池的相关类和接口:java.util.concurrent.Executors类和java.util.concurrent.ExecutorService接口。

Executors类:

概念:
工具类和线程池的工厂类,可以创建并返回不同类型的线程池,
常用方法:
static ExecutorService newCachedThreadPool()创建一个可根据需要创建新线程的线程池,可缓存线程池,此线程池不会对线程池大小做限制new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory)无缓冲队列,线程缓存一段时间
static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池,不会释放工作线程new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
static ExecutorService newSingleThreadExecutor() 创建一个只有一个线程的线程池,不用每次重新创建new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory)
不成文强制规定:
Executors返回的线程池对象弊端如下:
FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CacheThreadPool和ScheduledThreadPool:允许创建线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致CPU占用满,OOM。
调用ThreadPoolExecutor的构造函数来自己根据业务场景创建线程池。
提交优先级:先往core线程提交,再往Array队列提交,最后往maxnum线程提交,非核心线程满了后reject
执行优先级:先执行core线程,再执行非核心线程,再执行队列的任务,代码(task != null || (task = getTask()) != null)
作用:提交尽量避免创建非核心线程执行尽量先释放非核心线程
源码:
addWorker底层调用task.run()方法
worker执行完后再次调用addWorker进行复用

ExecutorService接口:

概念:
真正的线程池接口,主要实现类是ThreadPoolExecutor
构造方法:
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
主要参数:
corePoolSize:核心线程数线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。
maximumPoolSize:最大线程数,包含非核心线程,非核心线程只会缓存一段时间。
keepAliveTime:非核心线程闲置超时时间
queue:任务队列,常见SynchronousQueue无缓冲等待队列,LinkedBlockingQueue无界缓存等待队列,当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待ArrayBlockingQueue有界缓存等待队列,防止资源耗尽,但是可能较难调整和控制。配合压测一般生产环境没有问题。
RejectedExecutionHandler当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。AbortPolicy:直接抛出异常。这个策略默认情况下是,表示无法处理新任务时抛出异常,可以捕获异常之后来进行相应的处理。CallerRunsPolicy:只用调用者所在线程来运行任务。DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。DiscardPolicy:不处理,丢弃掉。也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务,存到数据库里,负载降低后启动线程取出到队列里。
示例:
ExecutorService executorService = new ThreadPoolExecutor(1,1,60L,TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(2));
常用方法:
void execute(Runnable command) 执行任务和命令,通常用于执行Runnable。对于异常信息会直接抛出去。并且线程退出。
Future submit(Callable task或Runnable task) 执行任务和命令,通常用于执行Runable和Callable,返回future对象(FutureTask接收这两类实参),可以获取结果或者异常信息
void shutdown() 启动有序关闭
BlockingQueue解析:
概述:
一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。
原理:
java8中,每个操作加ReentrantLock,满or空的时候通过Condition await方法等待。
类似思路:可以通过wait,notify,notifyAll,sychronized来实现。
实现类:
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
SynchronousQueue:一个不存储元素的阻塞队列。无缓冲,必须等待。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

异常的捕捉:

普通线程:

1.Thread.setDefaultUncaughtExceptionHandler,start启动后,可以捕捉到异常。
2.如果没有任何处理,会直接抛出并结束程序。

线程池:

知识点:
submit方法默认不显示异常
execute方法默认显示
如何捕捉:
1.线程内try catch包起来,输出信息
2.线程池execute + Thread.setDefaultUncaughtExceptionHandler可以捕捉到异常。
3.线程池submit + try catch捕捉打印 + future.get阻塞获取返回值,如果是异常则catch能捕捉到栈信息。注意submit + thread.setDefaultUncaughtExceptionHandler不能捕捉到异常,因为run方法将异常放到了 setException(ex),并赋值给future对象。get方法本身是阻塞的线程池需要shutdown()才会退出。
4.实现Thread.UncaughtExceptionHandler接口,实现void uncaughtException(Thread t, Throwable e);方法,并将该handler传递给线程池的ThreadFactory
Executors.newFixedThreadPool(8,factory);
5.类似的,继承ThreadGroup,覆盖其uncaughtException方法。

网络编程:

TCP:

编程模型:

服务器:
(1)创建ServerSocket类型的对象并提供端口号;
(2)等待客户端的连接请求,调用accept()方法;
(3)使用输入输出流进行通信;
(4)关闭Socket;
客户端:
(1)创建Socket类型的对象并提供服务器的IP地址和端口号;
(2)使用输入输出流进行通信;
(3)关闭Socket;

ServerSocket类:

概念:
java.net.ServerSocket类主要用于描述服务器套接字信息
常用方法:
ServerSocket(int port) 根据参数指定的端口号来构造对象
Socket accept() 侦听并接收到此套接字的连接请求
void close() 用于关闭套接字

Socket类:

概念:
java.net.Socket类主要用于描述客户端套接字,是两台机器间通信的端点
常用方法:
Socket() 创建一个未连接的套接字,系统默认类型为SocketImpl。 可以用bind方法
Socket(String host, int port) 根据指定主机名和端口来构造对象
InputStream getInputStream() 用于获取当前套接字的输入流
OutputStream getOutputStream() 用于获取当前套接字的输出流
void close() 用于关闭套接字

输入输出:

输入:
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("hello");
不限定PrintStream,可以Object类型和Data类型的stream都可以。
输出:
BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));

多线程:

封装类,继承Thread,重写run方法,构造函数接受socket套接字即可。
合成复用模型。

UDP:

编程模型:

接收方:
(1)创建DatagramSocket类型的对象并提供端口号;
(2)创建DatagramPacket类型的对象并提供缓冲区;
(3)通过Socket接收数据内容存放到Packet中,调用receive方法;
(4)关闭Socket;
发送方:
(1)创建DatagramSocket类型的对象;
(2)创建DatagramPacket类型的对象并提供接收方的通信地址;
(3)通过Socket将Packet中的数据内容发送出去,调用send方法;
(4)关闭Socket;

DatagramSocket类:

概念:
java.net.DatagramSocket类主要用于描述发送和接收数据报的套接字(邮局)。
常用方法:
DatagramSocket() 使用无参的方式构造对象,发送方
DatagramSocket(int port) 根据参数指定的端口号来构造对象,接收方
void receive(DatagramPacket p) 用于接收数据报存放到参数指定的位置
void send(DatagramPacket p) 用于将参数指定的数据报发送出去
void close() 关闭Socket并释放相关资源

DatagramPacket类:

概念:
java.net.DatagramPacket类主要用于描述数据报,数据报用来实现无连接包裹投递服务。
常用方法:
DatagramPacket(byte[] buf, int length)根据参数指定的数组来构造对象,用于接收长度为length的数据报,接收方
DatagramPacket(byte[] buf, int length,InetAddress address, int port)根据参数指定数组来构造对象,将数据报发送到指定地址和端口,发送方
InetAddress getAddress() 用于获取发送方或接收方的通信地址
int getPort() 用于获取发送方或接收方的端口号
int getLength() 用于获取发送数据或接收数据的长度,避免接受的数组长度超过

InetAddress类:

概念:
java.net.InetAddress类主要用于描述互联网通信地址信息。
常用方法:
static InetAddress getLocalHost() 用于获取当前主机的通信地址
static InetAddress getByName(String host) 根据参数指定的主机名获取通信地址

URL类:

基本概念:

java.net.URL(Uniform Resource Identifier)类主要用于表示统一的资源定位器

常用的方法:

URL(String spec) 根据参数指定的字符串信息构造对象
String getProtocol() 获取协议名称
String getHost() 获取主机名称
int getPort() 获取端口号
String getPath() 获取路径信息
String getFile() 获取文件名
URLConnection openConnection() 获取URLConnection类的实例

URLConnection类:

基本概念:

java.net.URLConnection类是个抽象类,该类表示应用程序和URL之间的通信链接的所有类的超类,主要实现类有支持HTTP特有功能的HttpURLConnection类。
HttpURLConnection类的常用方法
InputStream getInputStream() 获取输入流
void disconnect() 断开连接

JVM介绍:

JVM组成:

组成:

类装载器子系统、运行时数据区、执行引擎、本地方法接口。其中执行引擎包含:解释器、及时编译器、垃圾回收器

架构图:

生命周期:

虚拟机启动:

Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。

虚拟机执行:

执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程

虚拟机退出:

1.程序正常执行结束。
2.程序在执行过程中遇到了异常或错误而异常终止。
3.由于操作系统用现错误而导致Java虚拟机进程终止。
4.某线程调用Runtime类或system类的exit方法,或Runtime类的halt方法,并且ava安全管理器也允许这次exit或halt操作。
5.除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载 Java虚拟机时,Java虚拟机的退出情况   。

jvm启动流程:

1.JVM运行环境的设置和检查。
2.通过CreateExecutionEnvironment函数查找JAVA_DLL动态库是否存在,能不能访问。并设置一些变量。
3.加载动态库,将动态库中的一些函数链接至本地变量。
4.解析[options]和[args]参数。
5.新建线程初始化虚拟机。
6.加载并执行主类。

类加载:

类加载器:

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。
第一个:启动类/引导类:Bootstrap ClassLoader
第二个:扩展类加载器:Extension ClassLoader
第三个:应用程序类加载器:Application Classloader,程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。
第四个:自定义加载器,继承java.lang.ClassLoader类,重写findClass()方法

类装载方式:

类的加载时机:
加载时机:new、对该类型静态变量的读写、对该类型静态方法的调用,还有反射
程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的。
隐式加载(非传入类全限定名的加载)、静态加载(编译时类不可以缺席的加载):
A b = new A();  如果程序运行到这段代码时还没有A类,那么JVM会请求装载当前类的类装器来装载类。没有用到的类不会加载(可以代码块测试)。(但import的类都会编译)
显式加载(传入类全限定名的加载)、动态加载(编译时类可以缺席的加载):
ClassLoader.loadClass(className) :只执行装载过程。
Class.forName(className):执行类的装载、链接、初始化过程。
使用装载当前类的装载器Class.forName("test.A");
指定装载器Class.forName("test.A",true,this.getClass().getClassLoader());
使用类路径类装载装载.ClassLoader.getSystemClassLoader().loadClass("test.A");
使用当前进程上下文的使用的类装载器进行装载,这种装载类的方法常常被有着复杂类装载体系结构的系统所使用。Thread.currentThread().getContextClassLoader().loadClass("test.A")
使用自定义的类装载器装载类

过程:

加载:
加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。
字节码来源。一般的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
类加载器。一般包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
链接
验证:主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
准备:主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。
解析:将常量池内的符号引用替换为直接引用的过程。
初始化:
这个阶段主要是对类变量初始化,是执行类构造器的过程。
换句话说,只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
流程图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YRaj489p-1639974572378)(https://raw.githubusercontent.com/aurorazl/markdown/main/image-20211217175622087.png)]

双亲委托模型:

概述:
某一个类加载器加载一个特定的类,他并不是立即由自己加载了,而是将这个动作委托给父亲来完成,如果父亲还有父亲,就继续将这个动作向上传递,一直到没有父亲的根类加载器也就是启动类加载器。由根类加载来尝试着进行加载我们所要加载的class文件,如果不成功,就返回给拓展类加载器,拓展类加载器尝试着加载,如果不成功就继续向下传递,一直到加载成功,然后将整个加载动作返回给第一次尝试加载的加载器中。
1.  当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
2.  当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
3.  当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
流程图:

原因:
JVM中要确定某一个类,需要类的 全限定名 + 加载此类的ClassLoader来共同确定。
也就是说即使两个类的全限定名是相同的,但是因为不同的ClassLoader加载了此类,那么在JVM中它是不同的类。
采用了委托模型以后加大了不同的ClassLoader的交互能力,无论你程序中有多少个类加载器,那么这些类其实都是可以共享的,这样就避免了不同的类加载器加载了同样名字的不同类以后造成混乱。
通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
优势:
1.避免类的重复加载
2.保护程序安全,防止核心API被随意篡改
类冲突:
多个版本的同一个类,默认情况下只会加载一次(根类加载器)。
类隔离:
前提:
JVM 在触发类加载时调用的是 ClassLoader.loadClass 方法。委托给父加载器查询,如果父加载器查询不到,就调用 findClass 方法进行加载。
类加载传导机制:
JVM 会选择当前类的类加载器来加载所有该类的引用的类。
概述:
利用类加载传导规则实现了不同模块的类隔离,这样不同模块之间的依赖就不会互相影响。
实现1:
自定义类加载器继承ClassLoader,通过自定义findClass方法来通过path读取byte来加载,但由于双亲委托,引用的类会被委托给根类加载器加载。
实现2:
自定义类加载器,自定义loadclass方法,破坏双亲委派机制。所有类包括 java.lang 包里面的类都会通过该方法加载。
先使用Thread.currentThread().getContextClassLoader().getParent()尝试loadClass,可以加载所有JDK自带的类,而自定义的类会报异常java.lang.ClassNotFoundException: test.TestA
然后通过path获取bytes再defineClass,加载自定义的类。
注意:path必须是class文件的路径,不能是java文件
自定义String类:
由于双亲委托,会返回jdk自带的。

反射机制:

基本概念:

通常情况下编写代码都是固定的,无论运行多少次执行的结果也是固定的,在某些特殊场合中编写代码时不确定要创建什么类型的对象,也不确定要调用什么样的方法,这些都希望通过运行时传递的参数来决定,该机制叫做动态编程技术,也就是反射机制。
通俗来说,反射机制就是用于动态创建对象并且动态调用方法的机制。
目前主流的框架底层都是采用反射机制实现的。

Class类:

基本概念:

java.lang.Class类的实例可以用于描述Java应用程序中的类和接口,也就是一种数据类型。
该类没有公共构造方法,该类的实例由Java虚拟机和类加载器自动构造完成,本质上就是加载到内存中的运行时类。

获取Class对象的方式:

1.使用数据类型.class的方式可以获取对应类型的Class对象(掌握)。字符串表示形式是字符串“class”或“interface”,后跟一个空格,然后是getName返回的格式的类的完全限定名。如Class java.lang.String如果此类对象表示基本类型,则此方法返回基本类型的名称。 如果此类对象表示void,则此方法返回“void”。 如果此类对象表示数组类型,则此方法返回“class”,后跟getName 。
2.使用引用/对象.getClass()的方式可以获取对应类型的Class对象。注意基本数据类型不是对象,而方法只有类的对象才能调用。
3.使用包装类.TYPE的方式可以获取对应基本数据类型的Class对象。不是自身xxx.class。
4.使用Class.forName(String xx)的方式来获取参数指定类型的Class对象(掌握)。如java.lang.String完整路径,不能获取基本数据类型
5.使用类加载器ClassLoader.loadClass(String)的方式获取指定类型的Class对象。可以通过getClassLoader获取。

常用的方法:

static Class<?> forName(String className) 用于获取参数指定类型对应的Class对象并返回。String可以动态输入(键盘输入,读取配置)。
T newInstance() 用于创建该Class对象所表示类的新实例,无参构造的方式。已过时。
Class<?>[] getInterfaces()获取该类实现的接口类

Constructor类:

基本概念:

java.lang.reflect.Constructor类主要用于描述获取到的构造方法信息

Class类的常用方法:

Constructor getConstructor(Class<?>... parameterTypes)用于获取此Class对象所表示类型中参数指定的公共构造方法,不传参时获取无参构造。传参String.class
Constructor<?>[] getConstructors()用于获取此Class对象所表示类型中所有的公共构造方法 getDeclaredConstructors获取所有构造方法,包括公有、受保护、默认、私有

onstructor类的常用方法:

newInstance(Object... initargs)使用此Constructor对象描述的构造方法来构造Class对象代表类型的新实例
int getModifiers(String name) 获取方法的访问修饰符,整数类型
String getName(String name) 获取方法的名称
Class<?>[] getParameterTypes(String name)获取方法所有参数的类型

Field类:

基本概念:

java.lang.reflect.Field类主要用于描述获取到的单个成员变量信息。

Class类的常用方法:

Field getDeclaredField(String name)用于获取此Class对象所表示类中参数名字对应的单个成员变量信息
Field[] getDeclaredFields() 用于获取此Class对象所表示类中所有成员变量信息

Field类的常用方法:

Object get(Object obj) 获取参数对象obj中此Field对象所表示成员变量的数值
void set(Object obj, Object value)将参数对象obj中此Field对象表示成员变量的数值修改为参数value的数值
void setAccessible(boolean flag)当实参传递true时,则反射对象在使用时应该取消 Java 语言访问检查。
int getModifiers() 获取成员变量的访问修饰符
Class<?> getType() 获取成员变量的数据类型
String getName() 获取成员变量的名称

Method类:

基本概念:

java.lang.reflect.Method类主要用于描述获取到的单个成员方法信息。

Class类的常用方法:

Method getMethod(String name, Class<?>... parameterTypes)用于获取该Class对象表示类中名字为name参数为parameterTypes的指定公共成员方法
Method[] getMethods() 用于获取该Class对象表示类中所有公共成员方法

Method类的常用方法:

Object invoke(Object obj,Object... args)使用对象obj来调用此Method对象所表示的成员方法,实参传递args
int getModifiers() 获取方法的访问修饰符
Class<?> getReturnType() 获取方法的返回值类型
String getName() 获取方法的名称
Class<?>[] getParameterTypes() 获取方法所有参数的类型
Class<?>[] getExceptionTypes() 获取方法的异常信息

获取其它结构信息:

Package getPackage() 获取所在的包信息
Class<? super T> getSuperclass() 获取继承的父类信息
Class<?>[] getInterfaces() 获取实现的所有接口
Annotation[] getAnnotations() 获取注解信息
Type[] getGenericInterfaces() 获取泛型信息,接口的泛型。

修饰符:

Java语言提供了很多修饰符,主要分为以下两类:

- 访问修饰符
- 非访问修饰符

访问控制修饰符:

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。1. default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。2. private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)3. public : 对所有类可见。使用对象:类、接口、变量、方法 //类变量要想通过实例访问,得声明为public4. protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。默认情况:接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。请注意以下方法继承的规则:父类中声明为 public 的方法在子类中也必须为 public。父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。父类中声明为 private 的方法,不能够被继承。

非访问修饰符:

为了实现一些其他的功能,Java 也提供了许多非访问修饰符。1. static 修饰符,用来修饰类方法和类变量。static变量:static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。static方法:static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。对象可以访问静态方法。2. final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。final变量:表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。final 修饰符通常和 static 修饰符一起使用来创建类常量。final方法:可以被子类继承,但是不能被子类修改。声明 final 方法的主要目的是防止该方法的内容被修改。3. abstract 修饰符,用来创建抽象类和抽象方法。抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。

import static静态导入:

一般我们导入一个类都用 import com.....ClassName;
而静态导入是这样:import static com.....ClassName.*;这里的多了个static,还有就是类名ClassName后面多了个 .* ,意思是导入这个类里的静态方法。
当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了。
然后在这个类中,就可以直接用方法名调用静态方法,而不必用ClassName.方法名 的方式来调用。 如: System.out.println(Integer.MAX_VALUE);
变为:import static java.lang.System.out;import static java.lang.Integer.*; out.println(MAX_VALUE);

Optional类:

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
实例: Optional<Integer> a = Optional.ofNullable(value1);Optional<Integer> b = Optional.of(value2);           //null会抛出一个 空指针异常Integer value1 = a.orElse(new Integer(0));            //可能为空,如果存在值,则返回该值,如果不存在值,则返回它收到的参数:Integer value2 = b.get();                           //值为 null 时也会抛出异常。为避免出现异常,您可选择首先检验其中是否存在值ifPresent()。return value1 + value2;
方法:orElse()orElseGet()       //如果值存在,不会创建括号内对象orElseThrow()   //对象为空时,直接抛出一个异常flatMapfilteror()            //如果对象包含一个值,则λ表达式不会执行ifPresentOrElse()   //Consumer  和 Runnable。如果对象包含一个值,则会执行 Consumer  动作;否则,会执行 Runnable  动作。

JDBC:

概念:

JDBC(Java Data Base Connectivity) 是 Java 访问数据库的标准规范.是一种用于执行SQL语句的Java API,可以为 多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。是Java访问数据库的标准规范.

原理:

JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库!每个数据库厂商都需 要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。
JDBC就是由sun公司定义的一套操作所有关系型数据库的规则(接口),而数据库厂商需要实现这套接口,提供数据库驱动jar包, 我们可以使用这套接口编程,真正执行的代码是对应驱动包中的实现类。

API使用:

1.注册驱动

JDBC规范定义驱动接口: java.sql.Driver
MySql驱动包提供了实现类: com.mysql.jdbc.DriverClass.forName(数据库驱动实现类)加载和注册数据库驱动,数据库驱动由数据库厂商MySql提供 "com.mysql.jdbc.Driver"
源码里是执行static代码块,DriverManager.registerDriver(new Driver());
从 JDBC3 开始,目前已经普遍使用的版本。可以不用注册驱动而直接使用。 Class.forName 这句话可以省 略。

2.获得连接

Connection 接口,代表一个连接对象 ,具体的实现类由数据库的厂商实现
使用 DriverManager类的静态方法,getConnection可以获取数据库的连接
Connection getConnection(String url, String user, String password) 通过连接字符串和用户名,密码来获取数据 库连接对象

3.获取语句执行平台

通过Connection 的 createStatement方法 获取sql语句执行对象Statement createStatement() 创建 SQL语句执行对象
Statement : 代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结 果的对象。int executeUpdate(String sql); 执行insert update delete语句.返回int类型,代表受影响的行 数ResultSet executeQuery(String sql);  执行select语句, 返回ResultSet结果集对象

4.处理结果集

ResultSet接口 作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。boolean next() 可以循环获取1) 游标向下一行 2) 返回 boolean 类型,如果还有下一条记录,返回 true,否则返回 falsexxx getxxx(String or int)  返回指定的结果1) 通过列名,参数是 String 类型。返回不同的类型 2) 通过列号,参数是整数,从 1 开始。返回不同的类型

5.释放资源

1) 需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接
2) 释放原则:先开的后关,后开的先关。ResultSet ==> Statement ==> Connection
3) 放在哪个代码块中:finally 块,与IO流一样,使用后的东西都需要关闭!关闭的顺序是先开后关, 先得到的后关闭,后得到的先关闭

封装工具类:

JDBCUtils
1. 定义字符串常量, 记录获取连接所需要的信息
2. 静态代码块, 随着类的加载而加载,Class.forName(DRIVERNAME);
3.获取连接的静态方法关闭资源的静态方法

预处理对象PreparedStatement:

接口介绍:

PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语 句对象.
预编译: 是指SQL 语句被发送给db预编译,并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行 该语句。

特点:

因为有预先编译的功能,提高 SQL 的执行效率。
可以有效的防止 SQL 注入的问题,安全性更高

常用方法:

reparedStatement prepareStatement(String sql) 指定预编译的 SQL 语句,SQL 语句中使用占位符 ? 创建一个语句对象
int executeUpdate();        执行insert update delete语句.
ResultSet executeQuery();   执行select语句. 返回结果集对象 Resulet
void setDouble(int parameterIndex, double x)    将指定参数设置为给定 Java double 值。
void setObject(int parameterIndex, Object x)    使用给定对象设置指定参数的值。

事务相关API:

void setAutoCommit(boolean autoCommit)   参数是 true 或 false 如果设置为 false,表示关闭自动提交,相 当于开启事务
void commit()       提交事务
void rollback()     回滚事务

连接池:

介绍:

Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
常见的连接池有 DBCP连接池, C3P0连接池, Druid连接池

DBCP连接池:

介绍:
DBCP也是一个开源的连接池,是Apache成员之一,在企业开发中也比较常见,tomcat内置的连接池。
在DBCP包中提供了DataSource接口的实现类,我们要用的具体的连接池 BasicDataSource 类
编写工具类:
1.定义常量 保存数据库连接的相关信息
2.创建连接池对象 (有DBCP提供的实现类)
public static BasicDataSource dataSource = new BasicDataSource();
3.使用静态代码块进行配置
static{dataSource.setDriverClassName(DRIVERNAME);dataSource.setUrl(URL);dataSource.setUsername(USERNAME);dataSource.setPassword(PASSWORD);
}
4.获取连接的方法
connection connection = dataSource.getConnection();
接下来获取statement。
5.释放资源方法
con.close();归还连接
常见配置项:
driverClassName 数据库驱动名称
url数据库地址
username
password
maxActive最大连接数量
maxIdle最大空闲连接
minIdle最小空闲连接
initialSize初始化连接

C3P0连接池:

介绍:
C3P0是一个开源的JDBC连接池,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate、Spring等。
配置:
导入配置文件 c3p0-config.xml
c3p0-config.xml 文件名不可更改
直接放到src下,也可以放到到资源文件夹中,在项目下创建一个resource文件夹(专门存放资源文件),将文件放在resource目录下即可,创建连接池对象的时候会去加载这个配置文件
编写C3P0工具类:
1.创建连接池对象 C3P0对DataSource接口的实现类
C3P0提供的核心工具类, ComboPooledDataSource , 如果想使用连接池,就必须创建该类的对象
new ComboPooledDataSource(); 使用 默认配置
new ComboPooledDataSource("mysql"); 使用命名配置
2.获取连接的方法
dataSource.getConnection();
常见配置:
user 用户名
password
driverClass
jdbcUrl
initialPoolSize
maxPoolSize
minPoolSize
maxIdleTime

Druid连接池:

介绍:
Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行 情况。
导入配置文件:
是properties形式的
可以叫任意名称,可以放在任意目录下,我们统一放到 resources资源目录
编写Druid工具类:
1.创建属性集对象
Properties p = new Properties();
2.加载配置文件 Druid 连接池不能够主动加载配置文件 ,需要指定文件
InputStream inputStream =DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");
Druid 连接池不能够主动加载配置文件 ,需要指定文件
3.使用Properties对象的 load方法 从字节流中读取配置信息
p.load(inputStream);
4.通过工厂类获取连接池对象
dataSource = DruidDataSourceFactory.createDataSource(p);
5.获取连接
dataSource.getConnection();

DBUtils工具类:

简介:
使用JDBC我们发现冗余的代码太多了,为了简化开发 我们选择使用 DbUtils
Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。
使用方式:
DBUtils就是JDBC的简化开发工具包。需要项目导入 commons-dbutils-1.6.jar。
核心功能介绍:
1. QueryRunner 中提供对sql语句操作的API.
2. ResultSetHandler接口,用于定义select操作后,怎样封装结果集.
3. DbUtils类,他就是一个工具类,定义了关闭资源与事务处理相关方法.
表和类之间的关系:
整个表可以看做是一个类
表中的一行记录,对应一个类的实例(对象)
表中的一列,对应类中的一个成员属性
JavaBean组件:
JavaBean 就是一个类, 开发中通常用于封装数据,有以下特点1. 需要实现 序列化接口, Serializable (暂时可以省略) 2. 提供私有字段: private 类型 变量名;3. 提供 getter 和 setter4. 提供 空参构造
我们可以创建一个entity包,专门用来存放 JavaBean类
QueryRunner核心类:
构造方法:
QueryRunner()    DbUtils.closeQuietly(con);con关闭可以用这个方法,因为没有statement了,所以不用关闭。
QueryRunner(DataSource ds) ,提供数据源(连接池),DBUtils底层自动维护连接connection(创建关闭),如QueryRunner qr2 = new QueryRunner(DruidUtils.getDataSource());
常用方法:
update([Connection conn,] String sql, Object... params) ,用来完成表数据的增加、删除、更新操 作
query([Connection conn,] String sql, ResultSetHandler<T> rsh, Object... params) ,用来完成表 数据的查询操作
ResultSetHandler接口:
简介:
ResultSetHandler可以对查询出来的ResultSet结果集进行处理,达到一些业务上的需求。
常见实现类:
ArrayHandler将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这 条记录中的每一个字段的值。
ArrayListHandler将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集 合中。
BeanHandler将结果集中第一条记录封装到一个指定的javaBean中。如参数new BeanHandler<Employee>(Employee.class)通过反射机制一一对应属性创建对象
BeanListHandler将结果集中每一条记录封装到指定的javaBean中,再将这些javaBean在封装到List 集合中
ColumnListHandler将结果集中指定的列的字段值,封装到一个List集合中
KeyedHandler将结果集中每一条记录封装到Map<String,Object>,在将这个map集合做为另一个 Map的value,另一个Map集合的key是指定的字段的值。
MapHandler将结果集中第一条记录封装到了Map<String,Object>集合中,key就是字段名称, value就是字段值
MapListHandler将结果集中每一条记录封装到了Map<String,Object>集合中,key就是字段名称, value就是字段值,在将这些Map封装到List集合中。
ScalarHandler它是用于封装单个数据。例如 select count(*) from 表操作。

数据库批处理:

配置:

mysql 批处理是默认关闭的,所以需要加一个参数才打开mysql 数据库批处理,在url中添加 rewriteBatchedStatements=true

实现:

Statement和PreparedStatement都支持批处理操作

PreparedStatement的批处理方式:

void addBatch()将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中。 通过调用方法 executeBatch 可以批量执行此列表中的命令。
int[] executeBatch()每次提交一批命令到数据库中执行,如果所有的命令都成功执行了,那么返回一个数组,这个数组是说明每条命令所影响的行数
示例:ps.setString(1,"小强"+i);ps.addBatch();ps.executeBatch();

MySql元数据:

介绍:

除了表之外的数据都是元数据,可以分为三类
查询结果信息: UPDATE 或 DELETE语句 受影响的记录数。
数据库和数据表的信息: 包含了数据库及数据表的结构信息。
MySQL服务器信息: 包含了数据库服务器的当前状态,版本号等。

常用命令:

select version(); 获取mysql服务器的版本信息
show status; 查看服务器的状态信息
show columns from table_name; 显示表的字段信息等,和desc table_name一样
show index from table_name; 显示数据表的详细索引信息,包括PRIMARY KEY(主键)
show databases:列出所有数据库
show tables : 显示当前数据库的所有表
select database(): 获取当前的数据库名

使用JDBC 获取元数据:

通过JDBC 也可以获取到元数据,比如数据库的相关信息,或者当我们使用程序查询一个不熟悉的表时, 我们可以通过获取元素据信息,了解表中有多少个字段,字段的名称 和 字段的类型.

常用类介绍:

DatabaseMetaData描述数据库的元数据对象
ResultSetMetaData描述结果集的元数据对象

获取元数据对象的方法:

xxx.getMetaData ()connection 连接对象, 调用 getMetaData () 方法,获取的是DatabaseMetaData 数据库元数据对象PrepareStatement 预处理对象调用 getMetaData () , 获取的是ResultSetMetaData , 结果集元数据对象

DatabaseMetaData的常用方法:

getURL() : 获取数据库的URL
getUserName(): 获取当前数据库的用户名
getDatabaseProductName(): 获取数据库的产品名称
getDatabaseProductVersion(): 获取数据的版本号
getDriverName(): 返回驱动程序的名称
isReadOnly(): 判断数据库是否只允许只读 true 代表只读

ResultSetMetaData的常用方法:

getColumnCount() : 当前结果集共有多少列
getColumnName(int i) : 获取指定列号的列名, 参数是整数 从1开始
getColumnTypeName(int i): 获取指定列号列的类型, 参数是整数 从1开始

XML:

作用:

1.存储数据。通常,我们在数据库中存储数据。不过,如果希望数据的可移植性更强,我们可以 把数据存储 XML 文件中

2.配置文件。作为各种技术框架的配置文件使用 (最多)

3.传输格式。客户端可以使用XML格式向服务器端发送数据,服务器接收到xml格式数据,进行解析

语法:

文档声明必须为结束;
文档声明必写在第一行;

XML约束:

在XML技术里,可以编写一个文档来约束一个XML文档的书写规范,这称之为XML约束。
常见的xml约束:DTDSchema
DTD约束介绍:TD(Document Type Definition),文档类型定义,用来约束XML文档。规定XML文档中元素的名称,子元素的名称及顺序,元素的属性等。常情况我们都是通过框架提供的DTD约束文档,编写对应的XML文档。常见框架使用DTD约束有: Struts2、hibernate等。编写DTD创建约束文件 student.dtd引入DTD引入dtd文档到xml文档中,两种方式 内部dtd:将约束规则定义在xml文档中 外部dtd:将约束的规则定义在外部的dtd文件中
Schema约束介绍:1. Schema是新的XML文档约束, 比DTD强大很多,是DTD 替代者;2. Schema本身也是XML文档,但Schema文档的扩展名为xsd,而不是xml。 3. Schema 功能更强大,内置多种简单和复杂的数据类型4. Schema 支持命名空间 (一个XML中可以引入多个约束文档)XML引入Schema约束

XML 解析:

开发中比较常见的解析方式有两种,如下:

DOM:
要求解析器把整个XML文档装载到内存,并解析成一个Document对象。优点:元素与元素之间保留结构关系,故可以进行增删改查操作。
缺点:XML文档过大,可能出现内存溢出显现。
SAX:是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。并以事件驱动的方 式进行具体解析,每执行一行,都将触发对应的事件。(了解)
优点:占用内存少 处理速度快,可以处理大文件
缺点:只能读,逐行后将释放资源。

常见的解析器:

就是根据不同的解析方式提供的具体实现。有的解析器操作过于繁琐,为了方便开发人员,有提供易于操作的解析开发包
JAXP:sun公司提供的解析器,支持DOM和SAX两种思想
DOM4J:一款非常优秀的解析器 , Dom4j是一个易用的、开源的库,用于XML,XPath和XSLT。 它应用于Java平台,采用了Java集合框架并完全支持DOM,SAX和JAXP。
Jsoup:jsoup 是一款Java 的HTML解析器 ,也可以解析XML PULL:Android内置的XML解析方式,类似SAX。

dom4j:

介绍使用核心类SaxReader加载xml文档获得Document,通过Document 对象获得文档的根元素,然后就可以操作了
常用API:SaxReader对象read(...) 加载执行xml文档Document对象getRootElement() 获得根元素Element对象elements(...) 获得指定名称的所有子元素。可以不指定名称 element(...) 获得指定名称的第一个子元素。可以不指定名称 getName() 获得当前元素的元素名attributeValue(...) 获得指定属性名的属性值 elementText(...) 获得指定名称子元素的文本值getText() 获得当前元素的文本内容
xpath方式读取xml
介绍XPath 是一门在 XML 文档中查找信息的语言。 可以是使用xpath查找xml中的内容。由于DOM4J在解析XML时只能一层一层解析,所以当XML文件层数过多时使用会很不方便,结合 XPATH就可以直接获取到某个元素
XPath基本语法介绍使用dom4j支持xpath的操作的几种主要形式/AAA/DDD/BBBBBB[1] , BBB[last()]//BBB[@id='b1']
常用方法:selectSingleNode(query): 查找和 XPath 查询匹配的一个节点。参数是Xpath 查询串。selectNodes(query): 得到的是xml根节点下的所有满足 xpath 的节点;参数是Xpath 查询串。Node: 节点对象

JDBC自定义XML:

定义配置文件创建自定义xml 文件, 保存 数据库连接信息
使用:使用xpath 读取数据库信息SAXReader sr = new SAXReader();Document document = sr.readdocument.selectSingleNode

Properties类:

Properties 继承于 Hashtable。表示一个持久的属性集,属性列表以key-value的形式存在,key和value都是字符串。
读取:1. 使用java.util.Properties类的load(InputStream in)方法加载properties文件new Properties().load(new BufferedInputStream(new FileInputStream(new File(basePath))));path = prop.getProperty("path");//需要添加全路径:src/main/resources/system.conf2. 使用java.util.ResourceBundle类的getBundle()方法,注意:这个getBundle()方法的参数只能写成包路径+properties文件名,否则将抛异常ResourceBundle rb = ResourceBundle.getBundle("cn/habitdiary/prop");path = rb.getString("path");3. 使用java.util.PropertyResourceBundle类的构造函数ResourceBundle rb = new PropertyResourceBundle(new BufferedInputStream(new FileInputStream(basePath)));path = rb.getString("path");4. 使用class变量的getResourceAsStream()方法,注意:getResourceAsStream()方法的参数按格式写到包路径+properties文件名+.后缀new Properties().load(Myclass.class.getResourceAsStream("cn/habitdiary/prop.properties"));path = p.getProperty("path");5. 使用class.getClassLoader()所得到的java.lang.ClassLoader的getResourceAsStream()方法,getResourceAsStream(name)方法的参数必须是包路径+文件名+.后缀,否则会报空指针异常new Properties().load(Myclass.class.getClassLoader().getResourceAsStream("cn/habitdiary/prop.properties"));//能识别/src/main/resources路径下的文件6. 使用java.lang.ClassLoader类的getSystemResourceAsStream()静态方法,getSystemResourceAsStream()方法的参数格式也是有固定要求的new Properties().load(ClassLoader.getSystemResourceAsStream("cn/habitdiary/prop.properties"));
遍历:1. stringPropertyNames方法for (String key : properties.stringPropertyNames()) {System.out.println(key + "=" + properties.getProperty(key));}2. keySet方法Set<Object> keys = properties.keySet();//返回属性key的集合for (Object key : keys) {System.out.println(key.toString() + "=" + properties.get(key));}3. entrySet方法Set<Map.Entry<Object, Object>> entrySet = properties.entrySet();for (Map.Entry<Object, Object> entry : entrySet) {System.out.println(entry.getKey() + "=" + entry.getValue());}4. propertyNames方法Enumeration<?> e = properties.propertyNames();while (e.hasMoreElements()) {String key = (String) e.nextElement();String value = properties.getProperty(key);System.out.println(key + "=" + value);}

内存管理:

内存结构:

共享:

堆(Heap):线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。
方法区(Method Area):线程共享。存储类信息、方法信息、域信息、常量、静态变量、即时编译器编译后的代码。(JDK 1.8后,元空间存放在堆外内存中)

线程私有:

虚拟机栈(JVM Stack):线程私有。存储局部变量表、操作栈、动态链接、方法出口,对象指针。大小可以是动态(OutOfMemoryError)或者固定的(在线程创建的时候独立设置,超出会StackoverflowError)。
本地方法栈(Native Method Stack):线程私有。为虚拟机使用到的Native 方法服务。如Java使用c或者c++编写的接口服务时,代码在此区运行。
程序计数器(Program Counter Register):线程私有。有些文章也翻译成PC寄存器(PC Register),同一个东西。它可以看作是当前线程所执行的字节码的行号指示器。指向下一条要执行的指令。它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

结构图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52PYzWrV-1639974572379)(https://raw.githubusercontent.com/aurorazl/markdown/main/image-20211217175931679.png)]

JVM堆内存:

结构:

JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个MetaSpace(java8之前为永久代)。
年轻代又分为Eden生成区和Survivor区。Survivor区由FromSpace(s0)和ToSpace(s1)组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。几乎不进行垃圾回收。JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存(所以-XX指定的内存不包含)。配置:-XX:MetaspaceSize 和 -XX:MaxMetaspaceSizek8s内存分配:考虑-Xmx,metaspace,程序本身运行消耗。

结构图:

对象分代:

1.新生成的对象首先放到年轻代Eden区。
2.当Eden空间满了,触发Minor GC(清理年轻代),存活下来的对象移动到Survivor0区。
3.Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC(默认15次)仍然存活的对象移动到老年代。
4.老年代存储长期存活的对象,占满时会触发Major GC(Full GC),GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。

Jstat:

jstat -gc -t 58563 #显示pid是58563的垃圾回收堆的行为统计

线上问题排查:

jstat实时查看分析
堆溢出时自动堆转存-XX:+HeapDumpOnOutOfMemoryError,通过工具jhat分析

如何判断对象是否可回收?

1.引用计数器算法:

每当对象被引用一次计数器加 1,对象失去引用计数器减 1,计数器为 0 是就可以判断对象死亡了。
这种算法简单高效,但是对于循环引用或其他复杂情况,需要更多额外的开销,因此 Java 几乎不使用该算法。

2.根搜索算法-可达性分析算法:

所谓可达性分析是指,顺着 GCRoots 根一直向下搜索,整个搜索的过程就构成了一条“引用链”,只要在引用链上的对象叫做可达,
在引用链之外的(说明跟 GCRoots 没有任何关系)叫不可达,不可达的对象就可以判断为可回收的对象。
哪些对象可作为 GCRoots 对象呢? 虚拟机栈帧上本地变量表中的引用对象(方法参数、局部变量、临时变量)方法区中的静态属性引用类型对象、常量引用对象本地方法栈中的引用对象(Native 方法的引用对象)Java 虚拟机内部的引用对象,如异常对象、系统类加载器等所以被同步锁(synchronize)持有的对象Java 虚拟机内部情况的注册回调、本地缓存等

垃圾回收算法:

1.标记-清除算法

根据名称就可以理解改算法分为两个阶段:首先标记出所有需要被回收的对象,然后对标记的对象进行统一清除,清空对象所占用的内存区域
下图展示了回收前与回收后内存区域的对比,红色的表示可回收对象,橙色表示不可回收对象,白色表示内存空白区域。
缺点:第一个:是执行效率不可控,试想一下如果堆中大部分的对象都可回收的,收集器要执行大量的标记、收集操作。第二个:产生了许多内存碎片,通过回收后的内存状态图可以知道,被回收后的区域内存并不是连续的,当有大对象要分配而找不到满足大小的空间时,要触发下一次垃圾收集。

2.标记-复制算法

标记-复制算法将内存分为大小相同的两个区域,运行区域,预留区域,所有创建的新对象都分配到运行区域,当运行区域内存不够时,将运作区域中存活对象全部复制到预留区域,
然后再清空整个运行区域内存,这时两块区域的角色也发生了变化,每次存活的对象就像皮球一下在运行区域与预留区域踢来踢出,而垃圾对象会随着整个区域内存的清空而释放掉
优点:标记-复制算法在大量垃圾对象的情况下,只需复制少量的存活对象,并且不会产生内存碎片问题,新内存的分配只需要移动堆顶指针顺序分配即可,很好的兼顾了效率与内存碎片的问题。
缺点:预留一半的内存区域未免有些浪费了,并且如果内存中大量的是存活状态,只有少量的垃圾对象,收集器要执行更多次的复制操作才能释放少量的内存空间,得不偿失。

3.标记-整理算法

标记-复制算法要浪费一半内存空间,且在大多数状态为存活状态时使用效率会很低,针对这一情况计算机科学家又提出了一种新的算法“标记-整理算法”,
标记整理算法的标记阶段与其他算法一样,但是在整理阶段,算法将存活的对象向内存空间的一端移动,然后将存活对象边界以外的空间全部清空。
优点:不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价
缺点:标记整理算法解决了内存碎片问题,也不存在空间的浪费问题,看上去挺美好的。但是,当内存中存活对象多,并且都是一些微小对象,而垃圾对象少时,要移动大量的存活对象才能换取少量的内存空间。效率不高。不同的垃圾回收算法都有各自的优缺点,适应于不同的垃圾回收场景

4.分代收集算法

新生代:复制算法(CG后只有少量的对象存活)
老年代:标记-整理算法 或者 标记-清理算法(GC后对象存活率高)

垃圾收集器:

Serial收集器(复制算法):

新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

Serial Old收集器 (标记-整理算法):

老年代单线程收集器,Serial收集器的老年代版本;

ParNew收集器 (复制算法):

新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
除了Serial收集器之外,只有它能与CMS收集器配合工作。

Parallel Scavenge收集器 (复制算法):

概述:
新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间)
适合场景:
高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
与ParNew区别:
GC框架框架不同,所以不能与CMS搭配。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

Parallel Old收集器 (标记-整理算法):

概述:
老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
流程:
Mark阶段:串行标记所有从GC Roots可直达的对象,然后并行标记所有存活的对象。
Summary:串行执行,从左至右计算各个Region的密度,直到找到一个point,这个point左侧的Region都不值得整理,右侧的Region需要整理。
Compaction:利用Summary阶段的统计数据,针对需要整理的部分,采用“整理”算法进行并行操作。

CMS(Concurrent Mark Sweep)收集器(标记-清除算法):

概述:
老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
流程:
初始标记(CMS initial mark) -stop the world
并发标记(CMS concurrent mark) (用户线程和gc线程并行执行)
重新标记(CMS remark) -stop the world
并发清除(CMS concurrent sweep)
优点:
并发收集、低停顿。
适合重视服务的响应速度,希望系统停顿时间最短的互联网站。
缺点:
CMS收集器对CPU资源非常敏感。(占用多)
CMS收集器无法处理浮动垃圾(Floating Garbage)。由于CMS并发清理时,用户线程还在运行,伴随产生新垃圾,而这一部分出现在标记之后,只能下次GC时再清理。这一部分垃圾就称为”浮动垃圾“。需要预留空间给并发收集时用户程序产生的新对象。
CMS收集器是基于标记-清除算法,该算法的缺点都有。比如内存碎片,当大对象没有空间时,需要FullGC时开启内存碎片合并整理过程,这个过程无法并发,需要停顿。
发生promotion failed和concurrent mode failure时,会触发Full GC,切换到Serial Old(从未移植parallel old,而g1在java10中已经实现)promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。
配置:
-XX:+UseConcMarkSweepGC : 手动指定老年代使用CMS收集器
-XX:+UseCMSCompactAtFullCollection : 在进行Full GC时对内存进行压缩,JDK1.6以前不需要配置,默认开启
-XX:+UseParNewGC

G1(Garbage First)收集器 (新生代并行复制 + 老年代并发标记、增量整理算法):

概述:
Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。
此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
G1将堆分成许多相同大小的区域单元,每个单元称为Region。
虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
区域划分:
Eden regions(年轻代-Eden区)
Survivor regions(年轻代-Survivor区)
Old regions(老年代)
Humongous regions(巨型对象区域)- 存放超过region 一半大小的对象
Free resgions(未分配区域,也会叫做可用分区)-上图中空白的区域
流程:
年轻代GC(Young GC)young gc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。并行执行,stop the world
全局并发标记过程(global Concurrent Marking,伴随多次young GC)初始标记并发标记最终标记清除垃圾
混合回收(Mixed GC)选取所有的 Young region + 收益高的若干个 Old region。
优点:
G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。分区收集。
避免cms内存碎片问题的存在。
G1 Full gc在java10后使用了Parallel技术
适合场景:
对 GC 的停顿时间很敏感,比如 WEB 服务器(替代CMS)
小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间。
配置:
-XX:+UseG1GC
XX:MaxGCPauseMillis=200        如果MaxGCPauseMillis设置的过小,那么GC就会频繁,吞吐量就会下降。如果MaxGCPauseMillis设置的过大,应用程序暂停时间就会变长。

搭配关系:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wZ5gxtHN-1639974572380)(https://raw.githubusercontent.com/aurorazl/markdown/main/image-20211220120428420.png)]

虚线为即将废弃的搭配。

查看命令:

java -XX:+PrintCommandLineFlags -versionjava8使用的是UseParallelGC 即 Parallel Scavenge + Parallel Oldjava11使用的是UseG1GC
jmap -heap pid查看线上服务

GC:

分类:

部分收集(Partial GC)。不是完整收集整个Java堆的垃圾收集新生代收集(Minor GC/Young GC):只是新生代的垃圾收集老年代收集(Major GC/Old GC):只是老年代的圾收集目前,只有CMS GC会有单独收集老年代的行为备注:很多时候 Major GC 会与Full GC 混用,需要具体分辨是老年代回收还是整堆回收混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。目前,只有 G1 GC 会有这种行为
整堆收集(Full GC)。收集整个 java 堆和方法区的垃圾收集

触发机制和特点:

Minor GC触发条件和特点:
当Eden区满时,触发Minor GC。
Java对象大多都具备 朝生夕灭 的特性,所以Minor GC非常频繁,一般回收速度也比较快
Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
老年代GC(Major GC)触发机制及特点:
指发生在老年代的GC
出现Major GC,经常会伴随至少一次的Minor GC
Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长
如果Major GC后,内存还不足,就报OOM
Full GC触发条件:
(1)调用System.gc时,系统建议执行Full GC,但是JVM不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

查看gc情况:

jstat -gc 12538 1s

逃逸分析:

逃逸现象:

当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸。
通俗的说,如果一个对象的指针被多个方法或者线程引用时,那么就称这个对象的指针(或对象)的逃逸(Escape)。

概述:

一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。
通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围,从而决定是否要将这个对象分配到堆上。

编译器使用分析来优化:

栈上分配:将堆分配转化为栈分配,无需垃圾回收
同步省略:如果一个对象被发现只有一个线程被访问到,那么对于这个对象的操作可以不考虑同步
分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中(实际是存储在栈上)

逃逸分析的不足:

无法保证逃逸分析的收益大于进行此操作带来的性能消耗。

线上问题排查:

如果线程还存在,通过jstat查看gcutl的垃圾回收状态,分年轻代minor gc不用管,关注老年代full gc,内存占用满在启动参数配置OOM dump内存堆栈文件,用根据MAT或者esay.gc.io分析。
java启动参数 -Xmx10m - XX:HeapDumpOnOutOfMemoryError,借助工具IBM HeapAnalyzer看object list排行

某个线程CPU占用过高:

top -H -p <pid>  查看进程内各个线程的资源占用,通过jstack命令导出线程dump信息。把线程pid(对应线程dump内容中的nid,nid是16进制表示的)转换成十六进制,然后在线程dump文件中搜索即可找到对应的线程栈方法执行情况。jstack [ option ] pid-l长列表. 打印关于锁的附加信息-e打印额外信息
阿里开源Arthas

强软弱虚引用:

强引用:

概述:

强引用是最普遍的一种引用,我们写的代码,99.9999%都是强引用:
只要某个对象有强引用与之关联,这个对象永远不会被回收,即使内存不足,JVM宁愿抛出OOM,也不会去回收。

如何回收:

o = null;

软引用:

概述:

当内存不足,会触发JVM的GC,如果GC后,内存还是不足,就会把软引用的包裹的对象给干掉,也就是只有在内存不足,JVM才会回收该对象。

用法:

 SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());Student student = studentSoftReference.get();

弱引用:

概述:

弱引用的使用和软引用类似,只是关键字变成了WeakReference:
弱引用的特点是不管内存是否足够,只要发生GC,都会被回收:

用法:

WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]);
weakReference.get()

虚引用:

概述:

无法通过虚引用来获取对一个对象的真实引用。
虚引用必须与ReferenceQueue一起使用,当GC准备回收一个对象,如果发现它还有虚引用,就会在回收之前,把这个虚引用加入到与之关联的ReferenceQueue中。Reference poll = queue.poll();可以接收到。当发生GC,虚引用就会被回收,并且会把回收的通知放到ReferenceQueue中。

场景:

主要用来跟踪对象被垃圾回收器回收的活动,在所引用的对象的内存被回收之前采取必要的行动。

用法:

ReferenceQueue queue = new ReferenceQueue();PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);Reference poll = queue.poll();

java个人整理知识点相关推荐

  1. 高级 Java 面试通关知识点整理

    转载自 高级 Java 面试通关知识点整理 1.常用设计模式 单例模式:懒汉式.饿汉式.双重校验锁.静态加载,内部类加载.枚举类加载.保证一个类仅有一个实例,并提供一个访问它的全局访问点. 代理模式: ...

  2. 清华学长免费分享Java基础核心知识点基础篇(2)

    Java编程作为入门比较容易的编程语言,发展前景很好,非常适合零基础的小白入门学习. 对所学知识点没有全面了解,学习的过程中容易进入误区,影响学习进度,成从入门到放弃.所以,今天播妞整理了一些适合小白 ...

  3. 各大公司java面试整理对应问题博客整理

    各大公司java面试整理对应问题博客整理! 阿里 分库分表 数据库中间件 MyCAT是一个强大的数据库中间件,不仅仅可以用作读写分离,以及分表分库.容灾管理,而且可以用于多租户应用开发.云平台基础设施 ...

  4. 用大约 10 万字的内容对 Java 的核心知识点和常见的 1000 多道面试题,做了详细的介绍

    每个技术人都有个大厂梦,我觉得这很正常,并不是饭后的谈资而是每个技术人的追求.像阿里.腾讯.美团.字节跳动.京东等等的技术氛围与技术规范度还是要明显优于一些创业型公司/小公司,如果说能够在这样的公司锻 ...

  5. Java面试核心知识点(283页)Java面试题合集最新版(485页)

    阿里.腾讯两大互联网企业传来裁员消息,很多人都陷入担心,不安情绪蔓延-- 其实大家应该更冷静和理性地看待大厂裁员.每年三四月都是大厂人员调整期,这个季节是各个公司战略调整.战略规划的一个关键期,肯定会 ...

  6. 进大厂一条龙服务(Java核心面试知识点+一线大厂Java笔试面试题+月薪3万Java优秀简历模板),看这篇就够了

    你有没有觉得Java_工程师竞争压力大.就业困难?不知道面试Java工程师应该准备些什么?.. 现在一切都解决了!你想要的干货知识和面试题统统在这,还有月薪3万Java优秀简历模板,快拿回去嚼烂吧! ...

  7. Java研发工程师知识点总结

    Java研发工程师知识点总结 大纲 一.Java基础(语言.集合框架.OOP.设计模式等) 二.Java高级(JavaEE.框架.服务器.工具等) 三.多线程和并发 四.Java虚拟机 五.数据库(S ...

  8. Java刷题知识点之TCP、UDP、TCP和UDP的区别、socket、TCP编程的客户端一般步骤、TCP编程的服务器端一般步骤、UDP编程的客户端一般步骤、UDP编程的服务器端一般步骤...

    TCP和UDP是两个传输层协议,广泛应用于网络中不同主机之间传输数据.对任何程序员来说,熟悉TCP和UDP的工作方式都是至关重要的.这就是为什么TCP和UDP是一个流行的Java编程面试问题. Jav ...

  9. java发送get请求_如何快速掌握Java技术 Tomcat知识点有哪些

    如何快速掌握Java技术?Tomcat知识点概述有哪些?每一个对JavaWeb有所了解的人,都知道Tomcat是干什么用的!对,它是一个Servlet和JSP容器.然而,即使看过.使用过Tomcat, ...

最新文章

  1. 微信正在用的深度学习框架开源!支持稀疏张量,基于C++开发
  2. java数组遍历赋值,最终入职阿里
  3. python编程100例头条-Python 爱好者专用技术头条
  4. Linux Kernel中local_irq_enable()和local_irq_disable()的实现
  5. boost :: hash_combine从类的不同成员生成哈希值
  6. 在Quartus下仿真FIFO的读写
  7. lisp将图元追加选择_DNF:哈林防具和海博伦如何选择首饰提升率最大?你选对了吗?...
  8. 【elasticsearch】elasticsearch 搜索结果的含义
  9. 6.8使用Consul和envconsul来配置外部环境变量
  10. linux和windows下,C/C++开发的延时函数,sleep函数
  11. 网站性能工具-YSlow的23个规则-网站性能优化
  12. pandas获取dataframe的行数,列数,元素个数
  13. python语音识别终极指南_Python语音识别终极指南-帮你完成那个最难的从0到1
  14. mysql column specified twice_Mysql抛出Column 'descriptions' specified twice异常解决方法
  15. Android Studio集成NDK开发环境
  16. 苦学Excel、VBA,工作效率依旧低下?你该认识这个新工具了
  17. 主数据(MD Master Data)
  18. “汉语编程”是解决安全问题的终极之路?
  19. 使用TFS2010管理敏捷项目生命周期-系列指南5 TFS 故事墙(Story Wall)-看板(Dashboard)-Workbrench使用
  20. Gdal关于CAD转SHP格式

热门文章

  1. 【Vue】快乐学习第四篇,组件访问及插槽使用
  2. 桌面的回收站或IE图标不见后的解决办法
  3. html5制作学学课件,网页设计与制作教学课件作者HTML+CSS+JavaScript张洪斌教学资源5_JavaScript编程_电子课件课件.ppt...
  4. 台式电脑怎么找不到计算机在哪,台式电脑没有声音了怎么恢复(在家用这两个方法轻松解决)...
  5. 子女通过TeamViewer远程帮助父母解决手机使用问题
  6. 取模运算(包括负数)
  7. RC4 python实现
  8. 二值图像、8位灰度图像和彩色图像!
  9. 全媒体时代下高校思想政治教育目标应坚持的原则
  10. WireShark基本使用(1)第一章WireShark简介+练习题