Java总结(API)

Java总结——API

JAVA API Object String StringBuffer/StringBuilder

API(Application Programming Interface,应用程序接口)是一些预先定义的函数。目的是提供应用程序与开发人员基于某软件可以访问的一些功能集,但又无需访问源码或理解内部工作机制的细节。
API是一种通用功能集,有时公司会将API作为其公共开放系统,也就是公司制定自己的系统接口标准,当需要进行系统整合,自定义和程序应用等操作时,公司所有成员都可以通过该接口标准调用源代码。

Object

Object类是所有Java类的祖先,也就是说我们所说的”顶级父类”。它存在于java.lang.Object,这个包不需要我们手动导包。需要注意的是:每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法。在不明确给出超类的情况下,Java会自动把Object类作为要定义类的超类。
Object:每个类都使用Object作为超类,也就是我们说的“顶级父类”
当一个类没有明确指定父类时,默认以Object作为其父类
常用方法:
1)toString():本方法用于返回对应对象的字符串表示。默认实现返回的是地址值,重写后打印对象类型+属性值。
2)hashCode():返回对象的哈希码值。小贴士:哈希码值的得出是通过一种算法,意在让不同的对象具有不同的哈希码值,用于区分不同的对象。但是有时候也存在不同对象哈希码值相同的特殊情况,我们称之为”哈希碰撞”现象。
3)equals():默认实现==比较,比较的是地址值
3.1)toString()重写后比较的是类型+所有属性值,一致就返回true
3.2)String默认重写了equals(),它比较的是两个串的具体内容

String

String是一个封装char[]数组的对象,字符串不可变。底层实现可以看出:被final修饰,是常量。String str = “abc”;等效于:char data[]={‘a’,‘b’,‘c’};
创建String对象的方式:
方式一:
String(char[] value) 分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。
方式二: String str = “abc”;

1、如果是第一次使用字符串,java会在字符串堆中常量池创建一个对象。
2、再次使用相同的内容时,会直接访问堆中常量池中存在的对象。

常见方法:
length()-查看字符串的长度
charAt()—获取指定下标处位置上的字符
lastIndexOf()-某个字符最后一次出现的位置
substring()-截取子串,如果参数有两个左闭右开[1,5)
equals()-判断两个串是否相等,注意String重写了Object的此方法,所以内容相同就返回true
startsWith()-判断是不是以参数开头
endsWith()–判断是不是以参数结尾
split()—以指定字符分割
trim()-去掉首尾两端的空格
getBytes()-把串转换成数组
toUpperCase()-变成全大写
toLowerCase()-变成全小写
String.valueOf(10)-把int类型的10转换成String类型

StringBuilder/StringBuffer

特点
1、封装了char[]数组
2、是可变的字符序列
3、提供了一组可以对字符内容修改的方法
4、常用append()来代替字符串做字符串连接”+”
5、内部字符数组默认初始容量是16:super(str.length() + 16);
6、如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
7、StringBuffer 1.0出道线程安全,StringBuilder1.5出道线程不安全

常用函数 append()字符串连接函数

扩展
和equals的区别
1、当使用比较时,如果相比较的两个变量是引用类型,那么比较的是两者的地址值(内存地址),如果相比较的两个变量都是数值类型,那么比较的是具体数值是否相等。
2、当使用equals方法进行比较时,比较的结果实际上取决于equals()方法的具体实现
众所周知,任何类都继承自Object类,因此所有的类均具有Object类的特性,比如String,integer 等,他们在自己的类中重写了equals()方法,此时他们进行的是数值的比较,而在Object类的默认实现中,equals()方法的底层是通过
来实现的。

StringBuilder和StringBuffer的区别
1.在线程安全上 :
–StringBuffer是旧版本就提供的,线程安全的。@since JDK1.0
–StringBuilder是jdk1.5后产生,线程不安全的。@since 1.5
2. 在执行效率上,StringBuilder > StringBuffer > String
3.源码体现:本质上都是在调用父类抽象类AbstractStringBuilder来干活,只不过Buffer把代码加了同步关键字,使得程序可以保证线程安全问题。

JAVA 正则表达式 包装类 自动装箱/自动拆箱 BigDecimal

正则表达式Regex
概述:正确的字符串格式规则。常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的。
常见语法:

String提供了支持正则表达式的方法
Matches(正则) : 当前字符串能否匹配正则表达式
replaceAll(正则,子串) : 替换子串
split(正则) : 拆分字符串

包装类

把基本类型进行包装,提供更加完善的功能。
基本类型是没有任何功能的,只是一个变量,记录值,而包装类可以有更加丰富的功能
与基本类型的对应关系:

Number
数字包装类的抽象父类,提供了各种获取值的方式。

Integer
创建对象:
方式一:new Integer(5);
方式二:Integer.valueOf(5);
Integer类中包含256个Integer缓存对象,范围是-128~127
使用valueOf()时,如果指定范围内的值,直接访问缓存对象不新建;如果指定范围外的值,直接新建对象。
常见方法:
static int parseInt(String s)将字符串参数作为有符号的十进制整数进行解析

Double
创建对象:
new Double(3.14);
Double value)f(3.14);//和new没有区别
常用方法:
Double.parseDouble();

自动装箱和自动拆箱

概述:
自动装箱:把基本类型包装成对应的包装类型的过程。Integer a = 5;//a是引用类型,引用了包装对象的地址。编译器会完成对象的自动装箱:Integer a = Integer.valueOf(5);
自动拆箱:从包装类型的值,自动变成基本类型的值。int i = a;//a现在是包装类型,没法给变量赋值,需要把5取出来。编译器会完成自动拆箱:int i = a.intValue();
BigDecimal
常用来解决精确的浮点数运算不精确的问题
创建对象:
方式一:BigDecimal(double val);将double转换为BigDecimal,后者是double的二进制浮点值十进制表示形式,有坑!
方式二:BigDecimal(String val);将String类型字符串的形式转换为BigDecimal。
常用方法:
Add(BigDecimal bd) : 做加法运算
Subtract(BigDecimal bd) : 做减法运算
Multiply(BigDecimal bd) : 做乘法运算
Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常
Divide(BigDecimal bd,保留位数,舍入方式) : 除不尽时使用
setScale(保留位数,舍入方式) : 同上
pow(int n) : 求数据的几次幂

扩展:
舍入方式解析
ROUND_HALF_UP 四舍五入,五入 如:4.4结果是4; 4.5结果是5
ROUND_HALF_DOWN 五舍六入,五不入 如:4.5结果是4; 4.6结果是5
ROUND_HALF_EVEN 公平舍入(银行常用)
比如:在5和6之间,靠近5就舍弃成5,靠近6就进位成6,如果是5.5,就找偶数,变成6
ROUND_UP 直接进位,不算0.1还是0.9,都进位
ROUND_DOWN 直接舍弃,不算0.1还是0.9,都舍弃
ROUND_CEILING(天花板) 向上取整,取实际值的大值
朝正无穷方向round 如果为正数,行为和round_up一样,如果为负数,行为和round_down一样
ROUND_FLOOR(地板) 向下取整,取实际值的小值
朝负无穷方向round 如果为正数,行为和round_down一样,如果为负数,行为和round_up一样

JAVA IO流 File 字节流 字符流

IO简介
流Stream
1、流只能单方向流动
2、输入流用来读取->in
3、输出流用来写出->out
4、数据只能从头到尾顺序的读写一次。所以以程序的角度来思考,in/out相对于程序而言的输入(读取)/输出(写出)的过程。

IO流的继承结构
在java中,根据处理的数据单位不同,可以把流分为字节流和字符流。
字节流:针对二进制文件。
字符流:针对文本文件,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8,在结合对应类型的输入和输出方向,常用的流有:
File
字节流:针对二进制文件
InputStream
FileInputStream
BufferedInputStream
ObjectInputStream
OutputStream
FileOutputStream
BufferedOutputStream
ObjectOutputStream
字符流:针对文本文件
Reader
FileReader
BufferedReader
InputStreamReader
Writer
FileWriter
BufferedWriter
OutputStreamWriter
PrintWriter一行行写出

File文件流
概述:封装一个磁盘路径字符串,对这个路径可以执行一次操作。可以封装文件路径,文件夹路径,不存在的路径。
创建对象:File(String patname)通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例。new File(“d:/abc/a.txt”); new File(“d://abc”,“a.txt”);
常用方法:

字节流读取:
字节流是由字节组成的,字符流是由字符组成的。
Java里字符由两个字节组成。字节流是基本,主要用在处理二进制数据。
流式传输主要指将整个音频和视频及三维媒体等多媒体文件经过特定的压缩方式解析成一个个压缩包,由视频服务器向用户计算机顺序或实时传送。在采用流式传输方式的系统中,用户不必像采用下载方式那样等到整个文件全部下载完毕,而是只需经过几秒或几十秒的启动延时即可在用户的计算机上利用解压设备对压缩的A/V、3D等多媒体文件解压后进行播放和观看。此时多媒体文件的剩余部分将在后台的服务器内继续下载。

InputStream抽象类
此抽象类是表示字节输入流的所有类的超类/抽象类,不可创建对象。
常用方法:
abstract int rad()从输入流中读取数据的下一字节
int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
int read(byte[] b,int off,int len)将输入流中最多len个数据字节读入byte数组,off表示存时的偏移量
void close()关闭此输入流并释放与该关联的所有系统资源

FileInputStream子类
直接插在文件上,直接读取文件数据
创建对象:
FileInputStream(File file)一直接传文件对象
通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定FileInputStream(String pathname)——传路径
通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的路径名name指定

BufferedInputStream子类
BufferedInputStream为另一个输入流添加一些功能,即缓冲输入以及支持mark和reset方法的能力。在创建BfferedInputStream时,会创建一个内部缓冲区数组(默认8k大小)。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。
创建对象:
BufferedInputStream(InputStream in)
创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用。

字符流读取
常用于处理纯文本数据

Reader抽象类
用于读取字符流的抽象类
常用方法:
int read()读取单个字符
int read(char[] cbuf)将字符读入数组
abctract int read(char[],int off,int len)将字符读入数组的某一部分
int read(CharBuffer target)试图将字符读入指定的字符缓冲区
abstract void close()关闭该流并释放与之关联的所有资源

FileReader子类
用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在FileInputStream上构造一个InputStreamReader。
创建对象:
FileReader(String fileName)在给定从中读取数据的文件名的情况下创建一个新FileReader。

BufferedReader子类
从字符输入流中读取文件,缓冲各个字符,从而实现字符、数组和行的高效读取。可以指定缓冲区的大小,或者可使用默认大小。大多数情况下,默认值的大小就够了。
创建对象:
BufferedReader(Reader in)创建一个使用默认大小输入缓冲区的缓冲字符输入流。

字节流写出
OutputStream抽象类
此抽象类是表示输出字节流的所有类的超类,输出流接受输出字节并将这些字节发送到某个接收器。
常用方法:
void close()关闭此输出流并释放与此流相关的所有类资源
void flush()刷新此输出流并强制写出所有缓冲的输出字节
void write(byte[] b)将b.length个字节从指定的buye数组写入此输出流
void write(byte[] b,int off,int len)将指定byte数组中从偏移量off开始的len个字节写入输出流
abstract void write(int b)将指定的字节写入此输出流。

FileOutputStream子类
直接插在文件上,直接写出文件数据
构造方法(创建对象):
FileOutputStream(String name)
创建一个向具有指定名称的文件中写入数据的文件输出流
FileOutStream(File file)
创建一个向指定File对象表示文件中写入数据的文件输出流
FileOutputStream(File file,boolean append)——如果第二个参数为true,表示追加,不覆盖。
创建一个向指定File对象表示文件中写入数据的文件输出流,后面的参数是指定是否覆盖原文件内容。

BufferedOutputStream子类
该类实现缓冲的输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必每次针对字节写出调用底层系统。
构造方法(创建对象):

字符流写出
写入字符流的抽象类
常用方法:
Abstract void close()关闭此流,但要先刷新它
void write(char[] cbuf)写入字符数组
void write(int c)写入单个字符
void write(String str)写入字符串
void write(String str,int off,int len)写入字符串的某一部分
abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分

FileWriter子类
用来写入字符文件的便捷类,此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。如果需要自己定义这些值,可以先在FileOutputStream上构造一个OutputStreamWriter。
构造方法(创建对象):
FileWriter(String filename)
根据给定的文件名构造一个FileWriter对象
FileWriter(String filename,boolean append)
根据给定的文件名以及指定是否附加写入数据的boolean值来构造FileWriter

BufferedWriter子类
将文本写入字符输出流,缓冲各个字符,从而提供单个字符,数组和字符串的高效写入,可以指定缓冲区的大小,或者接受默认的大小,在大多数情况下,默认值就足够大了。
构造方法(创建对象):
BufferedWrite(Write out)
创建一个使用默认大小输出缓冲区的缓冲字符输出流

总结:
1、主流分类
1.1、按照方向进行分类:输入流,输出流(相对于程序而言,从程序写数据到文件中是输出)
1.2、按照传输类型进行分类:字节流,字符流
1.3、组合:字节输入流,字节输出流,字符输入流,字符输出流
2、学习方法:在抽象父类中学习通用的方法,在子类中学习如何创建对象
3、字节输入流:
InputStream抽象类,不能new,可以作为超类,学习其所提供的共性方法
–FileInputStream子类,操作文件的字节输入流,普通类
–BufferedInputStream子类,缓冲字节输入流,普通类
4、字符输入流:
Reader抽象类,不能new,可以作为超类,学习其所提供的共性方法
–FileReader,子类,操作文件的字符输出流,普通类
–BufferedReader,子类,缓冲字符输入流,普通类
5、字节输出流:
OutputStream抽象类,不能new,可以作为超类,学习其所提供的共性方法
–FileOutputStream子类,操作文件的字节输出流,普通类
–BufferedStream子类,缓冲字节输出流,普通类
6、字符输出流
Writer抽象类,不能new,可以作为超类,学习所提供的共性方法
–FileWriter,子类,操作文件的字符输出流,普通类
–BufferedWriter,子类,缓冲字符输出流,普通类

JAVA序列化与反序列化

概述:序列化是指将对象的状态信息转换为可以存储或传输形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后可以通过从存储区中读取或者反序列化对象的状态,重新创建该对象。
序列化:利用ObjectOutputStrteam,把对象的信息,按照固定的格式转成一串字节输出并持久保存到磁盘。
反序列化:利用ObjectInputStream,读取磁盘中之前序列化好的数据,重新恢复成对象。

特点/应用场景
1、需要序列化的文件必须实现Serializable接口,用来启用序列化功能
2、不需要序列化的数据可以修饰成static,原因:static资源属于类资源,不随着对象被序列化输出
3、每一个被序列化的文件都有一个唯一的id,如果没有添加此id,编译器会自动根据类的定义信息计算产生一个
4、在反序列化时,如果和序列化的版本号不一致,无法完成反序列化
5、常用与服务器之间的数据传输,序列化成文件,反序列化读取数据
6、常用使用套接字流在主机之间传递对象
7、不需要序列化的数据也可以被修饰成transient(临时的),只在程序运行期间在内存中存在,不会被序列化持久保存

涉及到的流对象
序列化:ObjectOutputStream
ObjectOutputStream将Java对象的基本数据类型写入OutputStream,通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
构造方法
ObjectOutputStream(OutputStream out)
创建写入方法指定OutputStream的ObjectOutputStream
普通方法:
writeObject(Object obj)
将指定的对象写入OnjectOutputStream

反序列化:ObjectInputStream
ObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化重构对象。
构造方法:
ObjectInputStream(InputStream in)创建从指定InputStream读取的ObjectInputStream
普通方法:
readObject()从ObjectInputStream读取对象

测试报错NotSerializableException:
报错原因:要序列化对象所在的类并没有实现序列化接口
解决方案:实现序列化接口
测试报错InvalidClassException:
报错原因:本次序列化时使用的UID与序列化时的UID不匹配
解决方案:反序列化时的UID与序列化时的UID要保持一致,或者测试时一次序列操作对应一次反序列化操作,否则不匹配就报错

为什么反序列化版本号要与序列化版本号一致?
我们在反序列化时,JVM会拿着反序列化流中的SerialVersionUID与序列化时相应的实体类中的serialVersionUID来比较,如果不一致,就无法正常反序列化,出现序列化版本不一致的异常InvalidClassException。
而且我们在定义需要序列化的实体类时,如果没有手动添加UID,Java序列化机制会根据编译的class自动生成一个,那么只有同一次编译生成的class才是一样的UID。如果我们手动添加了UID,只要这个值不修改,就可以不论编译次数,进行序列化和反序列化操作。

JAVA泛型Collection List Set

集合
泛型:JDK1.5的一个新特性,通常用来和集合对象一起使用。泛型概念十分重要,他是程序的增强器,它是目前主流的开发方式。
作用:可以把泛型理解成一个“语法糖”,本质上就是编译器为了提供更好的可读性而提供的一种小手段,小技巧,虚拟机层面是不存在所谓“泛型”的概念的。我们可以通过泛型的语法定义<>,来约束集合中元素的类型,编译器可以在编译期根据泛型约束提供一定的类型安全检查,这样可以避免程序运行时才暴露的BUG,代码的通用性也会更强。泛型可以提升程序代码的可读性,但是它只是一个“语法糖”(编译后这样的部分会被删除,不出现在最终的源码中),所以不会影响JVM后续运行时的性能。

泛型声明
泛型可以在接口、类、方法上使用
public class TestStudy{}
public interface Collection{}
public void print(E e){}
在方法的返回值前声明了一个,表示后面出现的E是泛型,而不是普通的java变量。
常用名称:
E - Element(在集合中使用,因为集合中存放的是元素)
T - Type(Java类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型

Collection接口
集合前言
Java语言的java.util包中提供了一些集合类,这些集合类又称之为容器。提到容器不难想到数组,集合类与数组最主要的不同之处是,数组的长度是固定的,集合的长度是可变的,而数组的访问方式比较单一,插入/删除等操作比较繁琐,而集合的访问方式比较灵活。

常用的集合类有List集合、Set集合、Map集合、其中List集合与Set集合继承了Collection接口,各个接口还提供了不同的实现类。

集合概念
集合的英文名称是Collection,是用来存放对象的数据结构,而且长度可变,可以存放不同类型的对象,并且还提供了一组操作成批对象的方法。Collection接口层次结构,接口不能直接使用,但是该接口提供了添加元素/删除元素/管理元素的父类接口公共方法。
由于List接口与Set接口都继承了Collection接口,因此这些方法对于List集合和Set集合都是通用的。

集合的继承结构
Collection接口
List接口【数据有下标,有序,可重复】
ArrayList子类
LinkedList子类
Set接口【数据无下标,无序,不可重复】
HashSet子类
Map接口【键值对的方式存数据】
HashMap子类

Collection方法速查表

List接口
概述:有序的collection(也称为序列)。此接口的用户可以对列表中的每个元素的插入位置进行精确的控制,用户可以根据元素的整数索引(在列表中的位置)来访问元素,并搜索列表中的元素。
特点:
1、元素都有下标
2、数据是有序的
3、允许存放重复的数据
List方法速查表

ArrayList
概述:
1、存在java.util包中
2、内部是用数组结构存放数据,封装数组的操作,每个对象都有下标
3、内部数组默认的初始容量是10,如果不够会以1.5倍的容量增长
4、查询快,增删数据效率会降低

创建对象
ArrayList()构造一个初始容量为10的空序列
源码摘抄:int newCapacity = oldCapacity + (oldCapacity >> 1);
解释:数组的新容量 = 旧容量/2的一次方–相当于原来的1.5倍扩容

LinkedList
概述:链表,两端效率高,底层就是链表实现的。

总结:
ArrayList底层是数组结构,查询快,增删慢,适合查询较多的场景。
LinkedList底层是链表结构,查询慢,增删快,适合增删操作较多的场景
注意:LinkedList查询慢是指数据量大时,查询中间的要慢。首尾操作还是比较快的。

创建对象
LinkedList构造一个空列表
常用方法:
void addFirst(E e) 将指定元素插入此列表的开头
void addLast(E e) 将指定元素添加到此列表的结尾
E getFirst() 返回此列表的第一个元素
E getLast() 返回此列表的最后一个元素
E removeFirst()移除并返回此列表的第一个元素
E removeLast() 移除并返回此列表的最后一个元素
E element() 获取但不移除此列表的头(第一个元素)
boolean offer(E e) 将指定元素添加到此列表的末尾(最后一个元素)
boolean offerFirst(E e) 在此列表的开头插入指定的元素
boolean offerLast(E e) 在此列表末尾插入指定的元素
E peek() 获取但不移除此列表的头(第一个元素)
E peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null
E peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null
E poll()获取并移除此列表的头(第一个元素)
E pollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null
E pollLast() 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null

扩展:ArrayList扩容
ArrayList相当于在没指定的initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10;之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,ArrayList继续扩容变为101.5=15;当添加第16个数据时,继续扩容变为151.5=22个。ArrayList没有对外暴露其容量个数,查看源码我们可以知道,实际其值存放在elementData对象数组中,那我们只需拿到这个数组的长度,观察其值变化了几次就知道其扩容了多少次。怎么获取呢?只能用反射技术了。

Set接口
概述:
1、Set是一个不包含重复数据的Collection
2、Set集合中的数据时无序的(因为Set集合没有下标)
3、Set集合中的元素不可以重复-常用来给数据去重
Set集合的特点
1、数据无序且数据不允许重复
2、HashSet:底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K,存入内部的HashMap中。当然K仍然不允许重复
3、TreeSet:底层是TreeMap,也是红黑树的形式,便于查找数据

HashSet
概述:底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K存入内部的HashMap中,其中K不允许重复,允许使用null。

Map接口
概述:Java.util接口Map<K,V>
类型参数:K-表示此映射所维护的键 V-表示此映射所维护的对应的值
也叫做哈希表、散列表、常用于键值对结构的数据,其中键不能重复,值可以重复。

特点:
1、Map可以根据键来提取对应的值
2、Map的键不允许重复,如果重复,对应的值会被覆盖
3、Map存放的都是无序的数据
4、Map的初始容量是16,默认的加载因子是0.75
TIPS:源码摘抄:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
初始容量1<<4,相当于1*(2^4),也就是16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认的加载因子是0.75f,也就是存到75%开始扩容,按照2的次幂进行扩容

继承结构

常用方法
学习Collection接口中的方法即可
void clear() 从此映射中移除所有映射关系(可选操作) boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图
boolean equals(Object o) 比较指定的对象与此映射是否相等
V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
int hashCode() 返回此映射的哈希码值
boolean isEmpty() 如果此映射未包含键-值映射关系,则返回 true
Set keySet() 返回此映射中包含的键的 Set 视图
V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)
void putAll(Map<? extends K,? extends V> m)从指定映射中将所有映射关系复制到此映射中(可选操作)
V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
int size() 返回此映射中的键-值映射关系数
Collection values() 返回此映射中包含的值的 Collection 视图

HashMap
HashMap底层是一个Entry[]数组,当存放数据时,会根据hash算法来计算数据的存放位置
算法:hash(key)%n,n就是数组的长度,其实也就是集合的容量
当计算的位置没有数据的时候,会直接存放数据
当计算的位置有数据时,会发生hash冲突/hash碰撞,解决的办法就是采用链表的结构,在数组中指定位置处已有元素之后插入新的元素,也就是说数组中的元素都是最早加入的节点。

set接口

概述
1.Set是一个不包含重复数据的Collection
2.Set集合中的数据是无序的(因为Set集合没有下标)
3.Set集合中的元素不可以重复——常用来给数据去重

Set集合的特点
1.数据无序且不允许数据重复
2.HashSet:底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为k,存入内部的HashMap中。当然k仍然不允许重复。
3.TreeSet:底层是TreeMap,也是红黑树的形式,便于查找数据

常用方法
学习Collection接口中的方法即可

HashSet概述
底层是哈希表,包装了HashMap,相当于向HashSet存入数据时,会把数据作为k存入内部的HashMap中,其中k不允许重复,允许使用null。

拓展
HashMap扩容

成长因子:
static final float DEFAULT_LOAD_FACTOR = 0.75f;
前面的讲述已经发现,当你空间只有仅仅为10的时候是很容易造成2个对象的hashcode 所对应的地址是一个位置的情况。这样就造成 2个 对象会形成散列桶(链表)。这时就有一个加载因子的参数,值默认为0.75 ,如果你hashmap的 空间有 100那么当你插入了75个元素的时候 hashmap就需要扩容了,不然的话会形成很长的散列桶结构,对于查询和插入都会增加时间,因为它要一个一个的equals比较。但又不能让加载因子很小,如0.01,这样显然是不合适的,频繁扩容会大大消耗你的内存。这时就存在着一个平衡,jdk中默认是0.75,当然负载因子可以根据自己的实际情况进行调整。

进程与线程

进程的概念
进程就是正在运行的程序,它代表了程序所占用的内存区域

进程的特点
独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,成为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的。
并发性:多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响。

并行和并发

高并发:多个进程抢占公共资源
并行:多个CPU同时处理不同的进程
HA(High Availability)高可用:指在高并发的情景中,尽可能的保证程序的可用性,减少系统不能提供服务的时间

线程
概念:线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程
我们看到的进程的切换,切换的也是不同进程的主线程
多线程扩展了多进程的概念,使得同一个进程可以同时并发助理多个任务

进程与线程的关系
一个操作系统中可以有多个进程,一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)

每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间
所以想使用线程技术,得先有进程,进程的创建时OS操作系统来创建的,一般是C或者C++完成

多线程的特性:
随机性:线程的随机性指的是同一时刻,只有一个程序在执行、我们宏观上觉得这些程序像是同时运行,但是实际上微观时间是因为CPU在高效的切换着,这使得各个程序从表面上看是同时进行的,也就是说,宏观层面上,所有的进程/线程看似同时进行,但是微观层面上,同一时刻,一个CPU只能处理一件事,切换的速度甚至是纳秒级别的,非常快。

CPU分时调度
时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
注意:我们无法控制OS选择执行哪些线程,OS底层有自己的规则,如:1、
FCFS(First Come First Service先来先服务算法),2、SJS(Short Job Service短服务算法)

线程的状态:
由于线程状态比较复杂,我们有易到难,先学习线程的三种基本状态及其转换,简称“三态模型”:
就绪(可执行)状态:线程已经准备好运行,只要活得CPU,就可立即执行
执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞

就绪->执行:为就绪线程分配CPU即可变为执行状态
执行->就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行->阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
(例如线程正好访问临界资源,而资源正在被其他线程访问)
反之,如果获得了之前需要的资源,则由阻塞状态变为就绪状态,等待分配CPU再次执行

扩展:“五态模型”
创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统

PCB(Process Control Block):为了保证参与并发执行的每个线程都能独立运行,OS配置了特有的数据结构PCB来描述线程的基本情况和活动过程,进而控制和管理线程

线程状态与代码对照

线程生命周期,主要有五种状态:

新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

多线程代码创建方式1:继承Thread
概述:Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例
启动线程的唯一方法就是通过Thread类的start()实例方法
start()方法是唯一native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()
这种方法实现的多线程很简单,通过自己的类直接extends Thread,并重写run()方法,就可以自动启动新线程并执行自己定义的run()方法
模拟开启多个线程,每个线程调用run()方法

常用方法
构造方法
Thread() 分配新的Thread对象
Thread(String name) 分配新的Thread对象
Thread(Runnable target) 分配新的Thread对象
Thread(Runnable target,String name) 分配新的Thread对象

普通方法
static Thread currentThread( )
返回对当前正在执行的线程对象的引用
long getId()
返回该线程的标识
String getName()
返回该线程的名称
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法
static void sleep(long millions)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
void start()
使该线程开始执行:Java虚拟机调用该线程的run()

多线程代码创建方式2:实现Runnable接口
概述:如果自己的类已经extends另一个类,就无法继承,此时,可以实现一个Runnable接口
常用方法:void run()使用实现接口Runnable的对象创建线程时,启动该线程将导致在独立执行的线程中调用对象的run()方法

两种方法的比较:
继承Thread类:
优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程
缺点:自定义的线程类已继承了Thread类,所以后续无法继承其他的类
实现Runnable接口
优点:自定义的线程类知识实现了Runnable接口或Callable接口,后续还可以继承其他类,在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、还有数据分开(解耦),形成清晰的模型,较好地体现了面向对象的思想。
缺点:编程稍微复杂,如想访问当前线程,则需要使用Thread.currentThread()方法

同步锁-线程安全问题解决方案

同步锁
如何判断程序有没有可能出现线程安全问题,主要有一下三个条件:
在多线程序中+有共享数据+多条语句操作共享数据
多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起卖100张票,这个是业务),所以思路可以从第3点"多条语句操作共享数据"入手,既然是在这多条语句操作数据过程中出现了问题,那我们可以把有可能出现问题的代码都包裹起来,一次只让一个线程来执行。

同步与异步
使用synchronized关键字来实现同步效果,也就是说,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的
**同步:**体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。坏处就是效率会降低,不过保证了安全。
**异步:**体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。坏处就是有安全隐患,效率要高一些。

synchronized同步关键字
写法:synchronized(锁对象){需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);}

前提
同步效果的使用有两个前提:
前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)

特点
1、synchronized同步关键字可以用来修饰方法,称为同步方法,使用的锁对象是this
2、synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象可以任意
3、同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的
4、但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了
为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢?
因为同步代码块可以保证同一个时刻只有一个线程进入,但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步。

快速查找某个类的快捷键:Ctrl+Shift+T

线程创建的其他方式 ExeutorService/Executors
EcecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理
execute(Runnable任务对象)把任务丢到线程池
Excutors辅助创建线程池的工具类
newFixedThreadPool(int n Threads)最多n个线程的线程池
newCachedThreadPool()足够多的线程,使任务不必等待
newSingleThreadExecutor()只有一个线程的线程池

扩展:线程锁
悲观锁和乐观锁
悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态.
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态.
乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

ReentrantLock 排他锁(悲观锁,有罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。

ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

两种方式的区别:
需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。

设计模式之单例设计模式

设计模式

单例设计模式
单例模式可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的。单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。其中单例模式最重要的是确保对象只有一个。简单来说,保证一个类在内存中的对象就一个。

单例设计模式1-饿汉式

package cn.tedu.design;
/*本类用于测试单例设计模式 1 - 饿汉式*/
public class Singleton1 {public static void main(String[] args) {//6.通过类名调用getSingle()方法获取本类对象MySingle single1 = MySingle.getSingle();MySingle single2 = MySingle.getSingle();//7.测试获取到的这两个引用类型变量是否相等System.out.println(single1 == single2);//true,==比较的是地址值,说明是同一个对象System.out.println(single1);//cn.tedu.single.MySingle@1b6d3586System.out.println(single2);//cn.tedu.single.MySingle@1b6d3586}
}
//0.创建自己的单例程序
class MySingle{//1.提供构造方法,并将构造方法私有化/*1.构造方法私有化的目的:为了不让外界随意实例化/new本类对象*/private MySingle(){ };/*3.思考:构造方法和对象私有化后,通过公共的访问点来获取对象,那外界如何调用这个公共方法呢?* 之前我们都是在外部创建本类对象并进行方法调用,但是现在单例程序中外部无法直接创建本类对象* 解决方案:我们可以利用之前学习的静态的概念,将方法修饰成静态的,就可以通过类名直接调用啦* 注意事项:静态只能调用静态,所以静态方法中返回的对象也需用静态修饰*///2.在类的内部,创建本类对象,并且私有化//5.2本资源也需要使用static修饰,因为静态方法getSingle()只能调用静态资源static private MySingle single = new MySingle();/*2.也就是以公共的方式向外界提供获取本类私有对象的方法*///3.对外提供一个公共的全局访问点//5.1用static关键字来修饰本方法,为了外部可以通过类名直接调用本方法static public MySingle getSingle(){//4.把内部创建好的对象返回到调用位置,谁调用这个方法,谁就能拿到返回的single对象return single;}
}

单例设计模式2-懒汉式

package cn.tedu.single;
/*本类用于测试单例设计模式 2 - 懒汉式--面试重点!!!*/
/*总结:
* 关于单例设计模式的两种实现方式:
* 1.饿汉式 : 不管你用不用这个类的对象,都会直接先创建一个
* 2.懒汉式 : 先不给你创建这个类的对象,等你需要的时候再帮你创建--利用了延迟加载的思想
* 延迟加载的思想:是指不会在第一时间就把对象创建好来占用内存,而是什么时候用到,什么时候再去创建对象
* 3.线程安全问题 : 是指共享资源有线程并发的数据安全隐患,可以通过加锁的方式[同步代码块/同步方法]
* */
public class Singleton2 {public static void main(String[] args) {//6.创建对象进行测试MySingle2 s1 = MySingle2.getMySingle2();MySingle2 s2 = MySingle2.getMySingle2();System.out.println( s1 == s2 );//true,比较的是地址值,说明是同一个对象System.out.println( s1 );//cn.tedu.single.MySingle2@1b6d3586System.out.println( s2 );//cn.tedu.single.MySingle2@1b6d3586}
}
//0.创建单例程序
class MySingle2{//1.私有化构造方法,为了防止外部调用构造方法直接创建本类对象private MySingle2(){}//2.在类的内部创建好引用类型变量(延迟加载的思想)--注意私有化//5.2本处的引用类型变量也需要修饰成static的,因为静态只能调用静态,getMySingle2()是静态方法static private MySingle2 single2;//7.2.2同步代码块中使用的唯一的锁对象static Object o = new Object();/*问题:程序中有共享资源single2,并且有多条语句(3句)操作了共享资源* 此时single2共享资源在多线程环境下一定会存在多线程数据安全隐患* 解决方案1:同步代码块[加锁,范围是操作共享资源的所有代码]* 解决方案2:同步方法[如果方法中的所有代码都需要被同步,那么这个方法可以修饰成同步方法]* 注意事项:锁对象在静态方法中,不可以使用this,因为静态资源优先于对象加载* 锁对象可以使用外部创建好的唯一的锁对象o,但请注意,需要是静态的,静态只能调用静态* *///3.对外提供公共的全局访问点//5.1注意要使用static来修饰本公共方法,为了方便后续可以通过类名直接调用//7.1将方法修饰成同步方法synchronized static public MySingle2 getMySingle2(){//4.当用户调用此方法时,才说明用到这个对象了,那么我们就把这个对象返回/*注意:这里需要增加一个判断如果调用方法时single2的值为null,说明之前没有new过,保存的是默认值这时才需要new对象,如果single2的值不为null,直接return single2即可*///7.2可以将操作共享资源的多条语句放入同步代码块之中synchronized (o) {//synchronized (this) {if (single2 == null) {single2 = new MySingle2();//没有对象时才创建对象,并赋值给single2}return single2;//有对象则直接返回single2}}
}

注解与自定义注解

**注解:**注解很厉害,它可以增强我们的java代码,同时利用反射技术可以扩充实现很多功能。它们被广泛应用于三大框架底层。传统我们通过xml文本文件声明方式,而现在最主流的开发都是基于注解方式,代码量少,框架可以根据注解去自动生成很多代码,从而减少代码量,程序更易读。

注解的分类:JDK自带注解,元注解,自定义注解

JDK注解(就5个):
@Override :用来标识重写方法
@Deprecated标记就表明这个方法已经过时了,但我就要用,别提示我过期
@SuppressWarnings(“deprecation”) 忽略警告
@SafeVarargs jdk1.7出现,堆污染,不常用
@FunctionallInterface jdk1.8出现,配合函数式编程拉姆达表达式,不常用

元注解(用来描述注解的注解,就5个):
@Target 注解用在哪里:类上、方法上、属性上等等
@Retention 注解的生命周期:源文件中、字节码文件中、运行中
@Inherited 允许子注解继承
@Documented 生成javadoc时会包含注解,不常用
@Repeatable注解为可重复类型注解,可以在同一个地方多次使用,不常用

@Target ElementType…
描述注解存在的位置:
ElementType.TYPE 应用于类的元素
ElementType.METHOD 应用于方法级
ElementType.FIELD 应用于字段或属性(成员变量)
ElementType.ANNOTATION_TYPE 应用于注释类型
ElementType.CONSTRUCTOR 应用于构造函数
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.PACKAGE 应用于包声明
ElementType.PARAMETER 应用于方法的参数

@Retention RetentionPolicy…
该注解定义了自定义注解被保留的时间长短,比如某些注解仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中; 编译在class文件中的注解可能会被虚拟机忽略,而另一些在class被装载时将被读取。

为何要分字节码文件中有还是没有呢?如果没有时,反射技术就拿不到,从而就无法去识别处理。它的值一共3种:SOURCE 在源文件中有效(即源文件保留);CLASS 在class文件中有效(即class保留);RUNTIME 在运行时有效(即运行时保留)

自定义注解
注意:注解的语法写法和常规java的语法写法不同

反射

**Reflection(反射)**是java程序开发语言的特征之一,它允许运行中的java程序对自身进行检查,或者说“自审”,也有称作“自省”。反射非常强大,它甚至能直接操作程序的私有属性。反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操作类的字段,方法,构造器等部分。有一个框架Spring就是一个非常专业且功能强大的产品,它可以帮我们创建对象,管理对象。以后我无需手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。

反射需要用到的API
获取字节码对象
Class.forName(“类的全路径”);
类名.class
对象.getClass();

常用方法:
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名

获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)

获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)

获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)

反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法

反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null

反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

暴力反射
指可以将程序中的私有属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如下:

内部类

**概述:**如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。就是把类定义在类的内部的情况就可以形成内部类的形式。A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:

特点:
1、内部类可以直接访问外部类中的成员,包括私有成员
2、外部类要访问内部类的成员,必须要建立内部类的对象
3、在成员位置的内部类是成员内部类
4、在局部位置的内部类是局部内部类

总结:
成员内部类被Private修饰以后,无法被外界直接创建对象使用,所以可以创建外部对象类对象,通过外部类对象间接访问内部类的资源
静态资源访问时不需要创建对象,可以通过类名直接访问,访问静态类中的静态资源可以通过“…”链式加载的方式访问
匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用

Java总结(API)

Java总结——API

JAVA API Object String StringBuffer/StringBuilder

API(Application Programming Interface,应用程序接口)是一些预先定义的函数。目的是提供应用程序与开发人员基于某软件可以访问的一些功能集,但又无需访问源码或理解内部工作机制的细节。
API是一种通用功能集,有时公司会将API作为其公共开放系统,也就是公司制定自己的系统接口标准,当需要进行系统整合,自定义和程序应用等操作时,公司所有成员都可以通过该接口标准调用源代码。

Object

Object类是所有Java类的祖先,也就是说我们所说的”顶级父类”。它存在于java.lang.Object,这个包不需要我们手动导包。需要注意的是:每个类都使用Object作为超类.所有对象(包括数组)都实现这个类的方法。在不明确给出超类的情况下,Java会自动把Object类作为要定义类的超类。
Object:每个类都使用Object作为超类,也就是我们说的“顶级父类”
当一个类没有明确指定父类时,默认以Object作为其父类
常用方法:
1)toString():本方法用于返回对应对象的字符串表示。默认实现返回的是地址值,重写后打印对象类型+属性值。
2)hashCode():返回对象的哈希码值。小贴士:哈希码值的得出是通过一种算法,意在让不同的对象具有不同的哈希码值,用于区分不同的对象。但是有时候也存在不同对象哈希码值相同的特殊情况,我们称之为”哈希碰撞”现象。
3)equals():默认实现==比较,比较的是地址值
3.1)toString()重写后比较的是类型+所有属性值,一致就返回true
3.2)String默认重写了equals(),它比较的是两个串的具体内容

String

String是一个封装char[]数组的对象,字符串不可变。底层实现可以看出:被final修饰,是常量。String str = “abc”;等效于:char data[]={‘a’,‘b’,‘c’};
创建String对象的方式:
方式一:
String(char[] value) 分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。
方式二: String str = “abc”;

1、如果是第一次使用字符串,java会在字符串堆中常量池创建一个对象。
2、再次使用相同的内容时,会直接访问堆中常量池中存在的对象。

常见方法:
length()-查看字符串的长度
charAt()—获取指定下标处位置上的字符
lastIndexOf()-某个字符最后一次出现的位置
substring()-截取子串,如果参数有两个左闭右开[1,5)
equals()-判断两个串是否相等,注意String重写了Object的此方法,所以内容相同就返回true
startsWith()-判断是不是以参数开头
endsWith()–判断是不是以参数结尾
split()—以指定字符分割
trim()-去掉首尾两端的空格
getBytes()-把串转换成数组
toUpperCase()-变成全大写
toLowerCase()-变成全小写
String.valueOf(10)-把int类型的10转换成String类型

StringBuilder/StringBuffer

特点
1、封装了char[]数组
2、是可变的字符序列
3、提供了一组可以对字符内容修改的方法
4、常用append()来代替字符串做字符串连接”+”
5、内部字符数组默认初始容量是16:super(str.length() + 16);
6、如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
7、StringBuffer 1.0出道线程安全,StringBuilder1.5出道线程不安全

常用函数 append()字符串连接函数

扩展
和equals的区别
1、当使用比较时,如果相比较的两个变量是引用类型,那么比较的是两者的地址值(内存地址),如果相比较的两个变量都是数值类型,那么比较的是具体数值是否相等。
2、当使用equals方法进行比较时,比较的结果实际上取决于equals()方法的具体实现
众所周知,任何类都继承自Object类,因此所有的类均具有Object类的特性,比如String,integer 等,他们在自己的类中重写了equals()方法,此时他们进行的是数值的比较,而在Object类的默认实现中,equals()方法的底层是通过
来实现的。

StringBuilder和StringBuffer的区别
1.在线程安全上 :
–StringBuffer是旧版本就提供的,线程安全的。@since JDK1.0
–StringBuilder是jdk1.5后产生,线程不安全的。@since 1.5
2. 在执行效率上,StringBuilder > StringBuffer > String
3.源码体现:本质上都是在调用父类抽象类AbstractStringBuilder来干活,只不过Buffer把代码加了同步关键字,使得程序可以保证线程安全问题。

JAVA 正则表达式 包装类 自动装箱/自动拆箱 BigDecimal

正则表达式Regex
概述:正确的字符串格式规则。常用来判断用户输入的内容是否符合格式的要求,注意是严格区分大小写的。
常见语法:

String提供了支持正则表达式的方法
Matches(正则) : 当前字符串能否匹配正则表达式
replaceAll(正则,子串) : 替换子串
split(正则) : 拆分字符串

包装类

把基本类型进行包装,提供更加完善的功能。
基本类型是没有任何功能的,只是一个变量,记录值,而包装类可以有更加丰富的功能
与基本类型的对应关系:

Number
数字包装类的抽象父类,提供了各种获取值的方式。

Integer
创建对象:
方式一:new Integer(5);
方式二:Integer.valueOf(5);
Integer类中包含256个Integer缓存对象,范围是-128~127
使用valueOf()时,如果指定范围内的值,直接访问缓存对象不新建;如果指定范围外的值,直接新建对象。
常见方法:
static int parseInt(String s)将字符串参数作为有符号的十进制整数进行解析

Double
创建对象:
new Double(3.14);
Double value)f(3.14);//和new没有区别
常用方法:
Double.parseDouble();

自动装箱和自动拆箱

概述:
自动装箱:把基本类型包装成对应的包装类型的过程。Integer a = 5;//a是引用类型,引用了包装对象的地址。编译器会完成对象的自动装箱:Integer a = Integer.valueOf(5);
自动拆箱:从包装类型的值,自动变成基本类型的值。int i = a;//a现在是包装类型,没法给变量赋值,需要把5取出来。编译器会完成自动拆箱:int i = a.intValue();
BigDecimal
常用来解决精确的浮点数运算不精确的问题
创建对象:
方式一:BigDecimal(double val);将double转换为BigDecimal,后者是double的二进制浮点值十进制表示形式,有坑!
方式二:BigDecimal(String val);将String类型字符串的形式转换为BigDecimal。
常用方法:
Add(BigDecimal bd) : 做加法运算
Subtract(BigDecimal bd) : 做减法运算
Multiply(BigDecimal bd) : 做乘法运算
Divide(BigDecimal bd) : 做除法运算,除不尽时会抛异常
Divide(BigDecimal bd,保留位数,舍入方式) : 除不尽时使用
setScale(保留位数,舍入方式) : 同上
pow(int n) : 求数据的几次幂

扩展:
舍入方式解析
ROUND_HALF_UP 四舍五入,五入 如:4.4结果是4; 4.5结果是5
ROUND_HALF_DOWN 五舍六入,五不入 如:4.5结果是4; 4.6结果是5
ROUND_HALF_EVEN 公平舍入(银行常用)
比如:在5和6之间,靠近5就舍弃成5,靠近6就进位成6,如果是5.5,就找偶数,变成6
ROUND_UP 直接进位,不算0.1还是0.9,都进位
ROUND_DOWN 直接舍弃,不算0.1还是0.9,都舍弃
ROUND_CEILING(天花板) 向上取整,取实际值的大值
朝正无穷方向round 如果为正数,行为和round_up一样,如果为负数,行为和round_down一样
ROUND_FLOOR(地板) 向下取整,取实际值的小值
朝负无穷方向round 如果为正数,行为和round_down一样,如果为负数,行为和round_up一样

JAVA IO流 File 字节流 字符流

IO简介
流Stream
1、流只能单方向流动
2、输入流用来读取->in
3、输出流用来写出->out
4、数据只能从头到尾顺序的读写一次。所以以程序的角度来思考,in/out相对于程序而言的输入(读取)/输出(写出)的过程。

IO流的继承结构
在java中,根据处理的数据单位不同,可以把流分为字节流和字符流。
字节流:针对二进制文件。
字符流:针对文本文件,读写容易出现乱码的现象,在读写时,最好指定编码集为UTF-8,在结合对应类型的输入和输出方向,常用的流有:
File
字节流:针对二进制文件
InputStream
FileInputStream
BufferedInputStream
ObjectInputStream
OutputStream
FileOutputStream
BufferedOutputStream
ObjectOutputStream
字符流:针对文本文件
Reader
FileReader
BufferedReader
InputStreamReader
Writer
FileWriter
BufferedWriter
OutputStreamWriter
PrintWriter一行行写出

File文件流
概述:封装一个磁盘路径字符串,对这个路径可以执行一次操作。可以封装文件路径,文件夹路径,不存在的路径。
创建对象:File(String patname)通过将给定路径名字符串转换为抽象路径名来创建一个新的File实例。new File(“d:/abc/a.txt”); new File(“d://abc”,“a.txt”);
常用方法:

字节流读取:
字节流是由字节组成的,字符流是由字符组成的。
Java里字符由两个字节组成。字节流是基本,主要用在处理二进制数据。
流式传输主要指将整个音频和视频及三维媒体等多媒体文件经过特定的压缩方式解析成一个个压缩包,由视频服务器向用户计算机顺序或实时传送。在采用流式传输方式的系统中,用户不必像采用下载方式那样等到整个文件全部下载完毕,而是只需经过几秒或几十秒的启动延时即可在用户的计算机上利用解压设备对压缩的A/V、3D等多媒体文件解压后进行播放和观看。此时多媒体文件的剩余部分将在后台的服务器内继续下载。

InputStream抽象类
此抽象类是表示字节输入流的所有类的超类/抽象类,不可创建对象。
常用方法:
abstract int rad()从输入流中读取数据的下一字节
int read(byte[] b)从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中
int read(byte[] b,int off,int len)将输入流中最多len个数据字节读入byte数组,off表示存时的偏移量
void close()关闭此输入流并释放与该关联的所有系统资源

FileInputStream子类
直接插在文件上,直接读取文件数据
创建对象:
FileInputStream(File file)一直接传文件对象
通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的File对象file指定FileInputStream(String pathname)——传路径
通过打开一个到实际文件的连接来创建一个FileInputStream,该文件通过文件系统中的路径名name指定

BufferedInputStream子类
BufferedInputStream为另一个输入流添加一些功能,即缓冲输入以及支持mark和reset方法的能力。在创建BfferedInputStream时,会创建一个内部缓冲区数组(默认8k大小)。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。
创建对象:
BufferedInputStream(InputStream in)
创建一个BufferedInputStream并保存其参数,即输入流in,以便将来使用。

字符流读取
常用于处理纯文本数据

Reader抽象类
用于读取字符流的抽象类
常用方法:
int read()读取单个字符
int read(char[] cbuf)将字符读入数组
abctract int read(char[],int off,int len)将字符读入数组的某一部分
int read(CharBuffer target)试图将字符读入指定的字符缓冲区
abstract void close()关闭该流并释放与之关联的所有资源

FileReader子类
用来读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。要自己指定这些值,可以先在FileInputStream上构造一个InputStreamReader。
创建对象:
FileReader(String fileName)在给定从中读取数据的文件名的情况下创建一个新FileReader。

BufferedReader子类
从字符输入流中读取文件,缓冲各个字符,从而实现字符、数组和行的高效读取。可以指定缓冲区的大小,或者可使用默认大小。大多数情况下,默认值的大小就够了。
创建对象:
BufferedReader(Reader in)创建一个使用默认大小输入缓冲区的缓冲字符输入流。

字节流写出
OutputStream抽象类
此抽象类是表示输出字节流的所有类的超类,输出流接受输出字节并将这些字节发送到某个接收器。
常用方法:
void close()关闭此输出流并释放与此流相关的所有类资源
void flush()刷新此输出流并强制写出所有缓冲的输出字节
void write(byte[] b)将b.length个字节从指定的buye数组写入此输出流
void write(byte[] b,int off,int len)将指定byte数组中从偏移量off开始的len个字节写入输出流
abstract void write(int b)将指定的字节写入此输出流。

FileOutputStream子类
直接插在文件上,直接写出文件数据
构造方法(创建对象):
FileOutputStream(String name)
创建一个向具有指定名称的文件中写入数据的文件输出流
FileOutStream(File file)
创建一个向指定File对象表示文件中写入数据的文件输出流
FileOutputStream(File file,boolean append)——如果第二个参数为true,表示追加,不覆盖。
创建一个向指定File对象表示文件中写入数据的文件输出流,后面的参数是指定是否覆盖原文件内容。

BufferedOutputStream子类
该类实现缓冲的输出流,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必每次针对字节写出调用底层系统。
构造方法(创建对象):

字符流写出
写入字符流的抽象类
常用方法:
Abstract void close()关闭此流,但要先刷新它
void write(char[] cbuf)写入字符数组
void write(int c)写入单个字符
void write(String str)写入字符串
void write(String str,int off,int len)写入字符串的某一部分
abstract void write(char[] cbuf,int off,int len)写入字符数组的某一部分

FileWriter子类
用来写入字符文件的便捷类,此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。如果需要自己定义这些值,可以先在FileOutputStream上构造一个OutputStreamWriter。
构造方法(创建对象):
FileWriter(String filename)
根据给定的文件名构造一个FileWriter对象
FileWriter(String filename,boolean append)
根据给定的文件名以及指定是否附加写入数据的boolean值来构造FileWriter

BufferedWriter子类
将文本写入字符输出流,缓冲各个字符,从而提供单个字符,数组和字符串的高效写入,可以指定缓冲区的大小,或者接受默认的大小,在大多数情况下,默认值就足够大了。
构造方法(创建对象):
BufferedWrite(Write out)
创建一个使用默认大小输出缓冲区的缓冲字符输出流

总结:
1、主流分类
1.1、按照方向进行分类:输入流,输出流(相对于程序而言,从程序写数据到文件中是输出)
1.2、按照传输类型进行分类:字节流,字符流
1.3、组合:字节输入流,字节输出流,字符输入流,字符输出流
2、学习方法:在抽象父类中学习通用的方法,在子类中学习如何创建对象
3、字节输入流:
InputStream抽象类,不能new,可以作为超类,学习其所提供的共性方法
–FileInputStream子类,操作文件的字节输入流,普通类
–BufferedInputStream子类,缓冲字节输入流,普通类
4、字符输入流:
Reader抽象类,不能new,可以作为超类,学习其所提供的共性方法
–FileReader,子类,操作文件的字符输出流,普通类
–BufferedReader,子类,缓冲字符输入流,普通类
5、字节输出流:
OutputStream抽象类,不能new,可以作为超类,学习其所提供的共性方法
–FileOutputStream子类,操作文件的字节输出流,普通类
–BufferedStream子类,缓冲字节输出流,普通类
6、字符输出流
Writer抽象类,不能new,可以作为超类,学习所提供的共性方法
–FileWriter,子类,操作文件的字符输出流,普通类
–BufferedWriter,子类,缓冲字符输出流,普通类

JAVA序列化与反序列化

概述:序列化是指将对象的状态信息转换为可以存储或传输形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后可以通过从存储区中读取或者反序列化对象的状态,重新创建该对象。
序列化:利用ObjectOutputStrteam,把对象的信息,按照固定的格式转成一串字节输出并持久保存到磁盘。
反序列化:利用ObjectInputStream,读取磁盘中之前序列化好的数据,重新恢复成对象。

特点/应用场景
1、需要序列化的文件必须实现Serializable接口,用来启用序列化功能
2、不需要序列化的数据可以修饰成static,原因:static资源属于类资源,不随着对象被序列化输出
3、每一个被序列化的文件都有一个唯一的id,如果没有添加此id,编译器会自动根据类的定义信息计算产生一个
4、在反序列化时,如果和序列化的版本号不一致,无法完成反序列化
5、常用与服务器之间的数据传输,序列化成文件,反序列化读取数据
6、常用使用套接字流在主机之间传递对象
7、不需要序列化的数据也可以被修饰成transient(临时的),只在程序运行期间在内存中存在,不会被序列化持久保存

涉及到的流对象
序列化:ObjectOutputStream
ObjectOutputStream将Java对象的基本数据类型写入OutputStream,通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
构造方法
ObjectOutputStream(OutputStream out)
创建写入方法指定OutputStream的ObjectOutputStream
普通方法:
writeObject(Object obj)
将指定的对象写入OnjectOutputStream

反序列化:ObjectInputStream
ObjectInputStream对以前使用ObjectOutputStream写入的基本数据和对象进行反序列化重构对象。
构造方法:
ObjectInputStream(InputStream in)创建从指定InputStream读取的ObjectInputStream
普通方法:
readObject()从ObjectInputStream读取对象

测试报错NotSerializableException:
报错原因:要序列化对象所在的类并没有实现序列化接口
解决方案:实现序列化接口
测试报错InvalidClassException:
报错原因:本次序列化时使用的UID与序列化时的UID不匹配
解决方案:反序列化时的UID与序列化时的UID要保持一致,或者测试时一次序列操作对应一次反序列化操作,否则不匹配就报错

为什么反序列化版本号要与序列化版本号一致?
我们在反序列化时,JVM会拿着反序列化流中的SerialVersionUID与序列化时相应的实体类中的serialVersionUID来比较,如果不一致,就无法正常反序列化,出现序列化版本不一致的异常InvalidClassException。
而且我们在定义需要序列化的实体类时,如果没有手动添加UID,Java序列化机制会根据编译的class自动生成一个,那么只有同一次编译生成的class才是一样的UID。如果我们手动添加了UID,只要这个值不修改,就可以不论编译次数,进行序列化和反序列化操作。

JAVA泛型Collection List Set

集合
泛型:JDK1.5的一个新特性,通常用来和集合对象一起使用。泛型概念十分重要,他是程序的增强器,它是目前主流的开发方式。
作用:可以把泛型理解成一个“语法糖”,本质上就是编译器为了提供更好的可读性而提供的一种小手段,小技巧,虚拟机层面是不存在所谓“泛型”的概念的。我们可以通过泛型的语法定义<>,来约束集合中元素的类型,编译器可以在编译期根据泛型约束提供一定的类型安全检查,这样可以避免程序运行时才暴露的BUG,代码的通用性也会更强。泛型可以提升程序代码的可读性,但是它只是一个“语法糖”(编译后这样的部分会被删除,不出现在最终的源码中),所以不会影响JVM后续运行时的性能。

泛型声明
泛型可以在接口、类、方法上使用
public class TestStudy{}
public interface Collection{}
public void print(E e){}
在方法的返回值前声明了一个,表示后面出现的E是泛型,而不是普通的java变量。
常用名称:
E - Element(在集合中使用,因为集合中存放的是元素)
T - Type(Java类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型

Collection接口
集合前言
Java语言的java.util包中提供了一些集合类,这些集合类又称之为容器。提到容器不难想到数组,集合类与数组最主要的不同之处是,数组的长度是固定的,集合的长度是可变的,而数组的访问方式比较单一,插入/删除等操作比较繁琐,而集合的访问方式比较灵活。

常用的集合类有List集合、Set集合、Map集合、其中List集合与Set集合继承了Collection接口,各个接口还提供了不同的实现类。

集合概念
集合的英文名称是Collection,是用来存放对象的数据结构,而且长度可变,可以存放不同类型的对象,并且还提供了一组操作成批对象的方法。Collection接口层次结构,接口不能直接使用,但是该接口提供了添加元素/删除元素/管理元素的父类接口公共方法。
由于List接口与Set接口都继承了Collection接口,因此这些方法对于List集合和Set集合都是通用的。

集合的继承结构
Collection接口
List接口【数据有下标,有序,可重复】
ArrayList子类
LinkedList子类
Set接口【数据无下标,无序,不可重复】
HashSet子类
Map接口【键值对的方式存数据】
HashMap子类

Collection方法速查表

List接口
概述:有序的collection(也称为序列)。此接口的用户可以对列表中的每个元素的插入位置进行精确的控制,用户可以根据元素的整数索引(在列表中的位置)来访问元素,并搜索列表中的元素。
特点:
1、元素都有下标
2、数据是有序的
3、允许存放重复的数据
List方法速查表

ArrayList
概述:
1、存在java.util包中
2、内部是用数组结构存放数据,封装数组的操作,每个对象都有下标
3、内部数组默认的初始容量是10,如果不够会以1.5倍的容量增长
4、查询快,增删数据效率会降低

创建对象
ArrayList()构造一个初始容量为10的空序列
源码摘抄:int newCapacity = oldCapacity + (oldCapacity >> 1);
解释:数组的新容量 = 旧容量/2的一次方–相当于原来的1.5倍扩容

LinkedList
概述:链表,两端效率高,底层就是链表实现的。

总结:
ArrayList底层是数组结构,查询快,增删慢,适合查询较多的场景。
LinkedList底层是链表结构,查询慢,增删快,适合增删操作较多的场景
注意:LinkedList查询慢是指数据量大时,查询中间的要慢。首尾操作还是比较快的。

创建对象
LinkedList构造一个空列表
常用方法:
void addFirst(E e) 将指定元素插入此列表的开头
void addLast(E e) 将指定元素添加到此列表的结尾
E getFirst() 返回此列表的第一个元素
E getLast() 返回此列表的最后一个元素
E removeFirst()移除并返回此列表的第一个元素
E removeLast() 移除并返回此列表的最后一个元素
E element() 获取但不移除此列表的头(第一个元素)
boolean offer(E e) 将指定元素添加到此列表的末尾(最后一个元素)
boolean offerFirst(E e) 在此列表的开头插入指定的元素
boolean offerLast(E e) 在此列表末尾插入指定的元素
E peek() 获取但不移除此列表的头(第一个元素)
E peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null
E peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null
E poll()获取并移除此列表的头(第一个元素)
E pollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null
E pollLast() 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null

扩展:ArrayList扩容
ArrayList相当于在没指定的initialCapacity时就是会使用延迟分配对象数组空间,当第一次插入元素时才分配10(默认)个对象空间。假如有20个数据需要添加,那么会分别在第一次的时候,将ArrayList的容量变为10;之后扩容会按照1.5倍增长。也就是当添加第11个数据的时候,ArrayList继续扩容变为101.5=15;当添加第16个数据时,继续扩容变为151.5=22个。ArrayList没有对外暴露其容量个数,查看源码我们可以知道,实际其值存放在elementData对象数组中,那我们只需拿到这个数组的长度,观察其值变化了几次就知道其扩容了多少次。怎么获取呢?只能用反射技术了。

Set接口
概述:
1、Set是一个不包含重复数据的Collection
2、Set集合中的数据时无序的(因为Set集合没有下标)
3、Set集合中的元素不可以重复-常用来给数据去重
Set集合的特点
1、数据无序且数据不允许重复
2、HashSet:底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K,存入内部的HashMap中。当然K仍然不允许重复
3、TreeSet:底层是TreeMap,也是红黑树的形式,便于查找数据

HashSet
概述:底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为K存入内部的HashMap中,其中K不允许重复,允许使用null。

Map接口
概述:Java.util接口Map<K,V>
类型参数:K-表示此映射所维护的键 V-表示此映射所维护的对应的值
也叫做哈希表、散列表、常用于键值对结构的数据,其中键不能重复,值可以重复。

特点:
1、Map可以根据键来提取对应的值
2、Map的键不允许重复,如果重复,对应的值会被覆盖
3、Map存放的都是无序的数据
4、Map的初始容量是16,默认的加载因子是0.75
TIPS:源码摘抄:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
初始容量1<<4,相当于1*(2^4),也就是16
static final float DEFAULT_LOAD_FACTOR = 0.75f;
默认的加载因子是0.75f,也就是存到75%开始扩容,按照2的次幂进行扩容

继承结构

常用方法
学习Collection接口中的方法即可
void clear() 从此映射中移除所有映射关系(可选操作) boolean containsKey(Object key) 如果此映射包含指定键的映射关系,则返回 true
boolean containsValue(Object value) 如果此映射将一个或多个键映射到指定值,则返回 true
Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射关系的 Set 视图
boolean equals(Object o) 比较指定的对象与此映射是否相等
V get(Object key) 返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
int hashCode() 返回此映射的哈希码值
boolean isEmpty() 如果此映射未包含键-值映射关系,则返回 true
Set keySet() 返回此映射中包含的键的 Set 视图
V put(K key, V value) 将指定的值与此映射中的指定键关联(可选操作)
void putAll(Map<? extends K,? extends V> m)从指定映射中将所有映射关系复制到此映射中(可选操作)
V remove(Object key) 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)
int size() 返回此映射中的键-值映射关系数
Collection values() 返回此映射中包含的值的 Collection 视图

HashMap
HashMap底层是一个Entry[]数组,当存放数据时,会根据hash算法来计算数据的存放位置
算法:hash(key)%n,n就是数组的长度,其实也就是集合的容量
当计算的位置没有数据的时候,会直接存放数据
当计算的位置有数据时,会发生hash冲突/hash碰撞,解决的办法就是采用链表的结构,在数组中指定位置处已有元素之后插入新的元素,也就是说数组中的元素都是最早加入的节点。

set接口

概述
1.Set是一个不包含重复数据的Collection
2.Set集合中的数据是无序的(因为Set集合没有下标)
3.Set集合中的元素不可以重复——常用来给数据去重

Set集合的特点
1.数据无序且不允许数据重复
2.HashSet:底层是哈希表,包装了HashMap,相当于向HashSet中存入数据时,会把数据作为k,存入内部的HashMap中。当然k仍然不允许重复。
3.TreeSet:底层是TreeMap,也是红黑树的形式,便于查找数据

常用方法
学习Collection接口中的方法即可

HashSet概述
底层是哈希表,包装了HashMap,相当于向HashSet存入数据时,会把数据作为k存入内部的HashMap中,其中k不允许重复,允许使用null。

拓展
HashMap扩容

成长因子:
static final float DEFAULT_LOAD_FACTOR = 0.75f;
前面的讲述已经发现,当你空间只有仅仅为10的时候是很容易造成2个对象的hashcode 所对应的地址是一个位置的情况。这样就造成 2个 对象会形成散列桶(链表)。这时就有一个加载因子的参数,值默认为0.75 ,如果你hashmap的 空间有 100那么当你插入了75个元素的时候 hashmap就需要扩容了,不然的话会形成很长的散列桶结构,对于查询和插入都会增加时间,因为它要一个一个的equals比较。但又不能让加载因子很小,如0.01,这样显然是不合适的,频繁扩容会大大消耗你的内存。这时就存在着一个平衡,jdk中默认是0.75,当然负载因子可以根据自己的实际情况进行调整。

进程与线程

进程的概念
进程就是正在运行的程序,它代表了程序所占用的内存区域

进程的特点
独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,成为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的。
并发性:多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响。

并行和并发

高并发:多个进程抢占公共资源
并行:多个CPU同时处理不同的进程
HA(High Availability)高可用:指在高并发的情景中,尽可能的保证程序的可用性,减少系统不能提供服务的时间

线程
概念:线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程
我们看到的进程的切换,切换的也是不同进程的主线程
多线程扩展了多进程的概念,使得同一个进程可以同时并发助理多个任务

进程与线程的关系
一个操作系统中可以有多个进程,一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)

每个线程在共享同一个进程中的内存的同时,又有自己独立的内存空间
所以想使用线程技术,得先有进程,进程的创建时OS操作系统来创建的,一般是C或者C++完成

多线程的特性:
随机性:线程的随机性指的是同一时刻,只有一个程序在执行、我们宏观上觉得这些程序像是同时运行,但是实际上微观时间是因为CPU在高效的切换着,这使得各个程序从表面上看是同时进行的,也就是说,宏观层面上,所有的进程/线程看似同时进行,但是微观层面上,同一时刻,一个CPU只能处理一件事,切换的速度甚至是纳秒级别的,非常快。

CPU分时调度
时间片,即CPU分配给各个线程的一个时间段,称作它的时间片,即该线程被允许运行的时间,如果在时间片用完时线程还在执行,那CPU将被剥夺并分配给另一个线程,将当前线程挂起,如果线程在时间片用完之前阻塞或结束,则CPU当即进行切换,从而避免CPU资源浪费,当再次切换到之前挂起的线程,恢复现场,继续执行。
注意:我们无法控制OS选择执行哪些线程,OS底层有自己的规则,如:1、
FCFS(First Come First Service先来先服务算法),2、SJS(Short Job Service短服务算法)

线程的状态:
由于线程状态比较复杂,我们有易到难,先学习线程的三种基本状态及其转换,简称“三态模型”:
就绪(可执行)状态:线程已经准备好运行,只要活得CPU,就可立即执行
执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞

就绪->执行:为就绪线程分配CPU即可变为执行状态
执行->就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行->阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
(例如线程正好访问临界资源,而资源正在被其他线程访问)
反之,如果获得了之前需要的资源,则由阻塞状态变为就绪状态,等待分配CPU再次执行

扩展:“五态模型”
创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统

PCB(Process Control Block):为了保证参与并发执行的每个线程都能独立运行,OS配置了特有的数据结构PCB来描述线程的基本情况和活动过程,进而控制和管理线程

线程状态与代码对照

线程生命周期,主要有五种状态:

新建状态(New) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态.
处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行
运行状态(Running):当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态
就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态
阻塞状态(Blocked):处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:
等待阻塞:运行状态中的线程执行wait()方法,本线程进入到等待阻塞状态
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

多线程代码创建方式1:继承Thread
概述:Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例
启动线程的唯一方法就是通过Thread类的start()实例方法
start()方法是唯一native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()
这种方法实现的多线程很简单,通过自己的类直接extends Thread,并重写run()方法,就可以自动启动新线程并执行自己定义的run()方法
模拟开启多个线程,每个线程调用run()方法

常用方法
构造方法
Thread() 分配新的Thread对象
Thread(String name) 分配新的Thread对象
Thread(Runnable target) 分配新的Thread对象
Thread(Runnable target,String name) 分配新的Thread对象

普通方法
static Thread currentThread( )
返回对当前正在执行的线程对象的引用
long getId()
返回该线程的标识
String getName()
返回该线程的名称
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法
static void sleep(long millions)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
void start()
使该线程开始执行:Java虚拟机调用该线程的run()

多线程代码创建方式2:实现Runnable接口
概述:如果自己的类已经extends另一个类,就无法继承,此时,可以实现一个Runnable接口
常用方法:void run()使用实现接口Runnable的对象创建线程时,启动该线程将导致在独立执行的线程中调用对象的run()方法

两种方法的比较:
继承Thread类:
优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this即可获得当前线程
缺点:自定义的线程类已继承了Thread类,所以后续无法继承其他的类
实现Runnable接口
优点:自定义的线程类知识实现了Runnable接口或Callable接口,后续还可以继承其他类,在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码、还有数据分开(解耦),形成清晰的模型,较好地体现了面向对象的思想。
缺点:编程稍微复杂,如想访问当前线程,则需要使用Thread.currentThread()方法

同步锁-线程安全问题解决方案

同步锁
如何判断程序有没有可能出现线程安全问题,主要有一下三个条件:
在多线程序中+有共享数据+多条语句操作共享数据
多线程的场景和共享数据的条件是改变不了的(就像4个窗口一起卖100张票,这个是业务),所以思路可以从第3点"多条语句操作共享数据"入手,既然是在这多条语句操作数据过程中出现了问题,那我们可以把有可能出现问题的代码都包裹起来,一次只让一个线程来执行。

同步与异步
使用synchronized关键字来实现同步效果,也就是说,当多个对象操作共享数据时,可以使用同步锁解决线程安全问题,被锁住的代码就是同步的
**同步:**体现了排队的效果,同一时刻只能有一个线程独占资源,其他没有权利的线程排队。坏处就是效率会降低,不过保证了安全。
**异步:**体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。坏处就是有安全隐患,效率要高一些。

synchronized同步关键字
写法:synchronized(锁对象){需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);}

前提
同步效果的使用有两个前提:
前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)

特点
1、synchronized同步关键字可以用来修饰方法,称为同步方法,使用的锁对象是this
2、synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象可以任意
3、同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的
4、但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了
为什么同步代码块的锁对象可以是任意的同一个对象,但是同步方法使用的是this呢?
因为同步代码块可以保证同一个时刻只有一个线程进入,但同步方法不可以保证同一时刻只能有一个线程调用,所以使用本类代指对象this来确保同步。

快速查找某个类的快捷键:Ctrl+Shift+T

线程创建的其他方式 ExeutorService/Executors
EcecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理
execute(Runnable任务对象)把任务丢到线程池
Excutors辅助创建线程池的工具类
newFixedThreadPool(int n Threads)最多n个线程的线程池
newCachedThreadPool()足够多的线程,使任务不必等待
newSingleThreadExecutor()只有一个线程的线程池

扩展:线程锁
悲观锁和乐观锁
悲观锁:像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态.
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

乐观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态.
乐观锁认为竞争不总是会发生,因此它不需要持有锁,将”比较-替换”这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。
每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

ReentrantLock 排他锁(悲观锁,有罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。

ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

两种方式的区别:
需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。

设计模式之单例设计模式

设计模式

单例设计模式
单例模式可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的。单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。其中单例模式最重要的是确保对象只有一个。简单来说,保证一个类在内存中的对象就一个。

单例设计模式1-饿汉式

package cn.tedu.design;
/*本类用于测试单例设计模式 1 - 饿汉式*/
public class Singleton1 {public static void main(String[] args) {//6.通过类名调用getSingle()方法获取本类对象MySingle single1 = MySingle.getSingle();MySingle single2 = MySingle.getSingle();//7.测试获取到的这两个引用类型变量是否相等System.out.println(single1 == single2);//true,==比较的是地址值,说明是同一个对象System.out.println(single1);//cn.tedu.single.MySingle@1b6d3586System.out.println(single2);//cn.tedu.single.MySingle@1b6d3586}
}
//0.创建自己的单例程序
class MySingle{//1.提供构造方法,并将构造方法私有化/*1.构造方法私有化的目的:为了不让外界随意实例化/new本类对象*/private MySingle(){ };/*3.思考:构造方法和对象私有化后,通过公共的访问点来获取对象,那外界如何调用这个公共方法呢?* 之前我们都是在外部创建本类对象并进行方法调用,但是现在单例程序中外部无法直接创建本类对象* 解决方案:我们可以利用之前学习的静态的概念,将方法修饰成静态的,就可以通过类名直接调用啦* 注意事项:静态只能调用静态,所以静态方法中返回的对象也需用静态修饰*///2.在类的内部,创建本类对象,并且私有化//5.2本资源也需要使用static修饰,因为静态方法getSingle()只能调用静态资源static private MySingle single = new MySingle();/*2.也就是以公共的方式向外界提供获取本类私有对象的方法*///3.对外提供一个公共的全局访问点//5.1用static关键字来修饰本方法,为了外部可以通过类名直接调用本方法static public MySingle getSingle(){//4.把内部创建好的对象返回到调用位置,谁调用这个方法,谁就能拿到返回的single对象return single;}
}

单例设计模式2-懒汉式

package cn.tedu.single;
/*本类用于测试单例设计模式 2 - 懒汉式--面试重点!!!*/
/*总结:
* 关于单例设计模式的两种实现方式:
* 1.饿汉式 : 不管你用不用这个类的对象,都会直接先创建一个
* 2.懒汉式 : 先不给你创建这个类的对象,等你需要的时候再帮你创建--利用了延迟加载的思想
* 延迟加载的思想:是指不会在第一时间就把对象创建好来占用内存,而是什么时候用到,什么时候再去创建对象
* 3.线程安全问题 : 是指共享资源有线程并发的数据安全隐患,可以通过加锁的方式[同步代码块/同步方法]
* */
public class Singleton2 {public static void main(String[] args) {//6.创建对象进行测试MySingle2 s1 = MySingle2.getMySingle2();MySingle2 s2 = MySingle2.getMySingle2();System.out.println( s1 == s2 );//true,比较的是地址值,说明是同一个对象System.out.println( s1 );//cn.tedu.single.MySingle2@1b6d3586System.out.println( s2 );//cn.tedu.single.MySingle2@1b6d3586}
}
//0.创建单例程序
class MySingle2{//1.私有化构造方法,为了防止外部调用构造方法直接创建本类对象private MySingle2(){}//2.在类的内部创建好引用类型变量(延迟加载的思想)--注意私有化//5.2本处的引用类型变量也需要修饰成static的,因为静态只能调用静态,getMySingle2()是静态方法static private MySingle2 single2;//7.2.2同步代码块中使用的唯一的锁对象static Object o = new Object();/*问题:程序中有共享资源single2,并且有多条语句(3句)操作了共享资源* 此时single2共享资源在多线程环境下一定会存在多线程数据安全隐患* 解决方案1:同步代码块[加锁,范围是操作共享资源的所有代码]* 解决方案2:同步方法[如果方法中的所有代码都需要被同步,那么这个方法可以修饰成同步方法]* 注意事项:锁对象在静态方法中,不可以使用this,因为静态资源优先于对象加载* 锁对象可以使用外部创建好的唯一的锁对象o,但请注意,需要是静态的,静态只能调用静态* *///3.对外提供公共的全局访问点//5.1注意要使用static来修饰本公共方法,为了方便后续可以通过类名直接调用//7.1将方法修饰成同步方法synchronized static public MySingle2 getMySingle2(){//4.当用户调用此方法时,才说明用到这个对象了,那么我们就把这个对象返回/*注意:这里需要增加一个判断如果调用方法时single2的值为null,说明之前没有new过,保存的是默认值这时才需要new对象,如果single2的值不为null,直接return single2即可*///7.2可以将操作共享资源的多条语句放入同步代码块之中synchronized (o) {//synchronized (this) {if (single2 == null) {single2 = new MySingle2();//没有对象时才创建对象,并赋值给single2}return single2;//有对象则直接返回single2}}
}

注解与自定义注解

**注解:**注解很厉害,它可以增强我们的java代码,同时利用反射技术可以扩充实现很多功能。它们被广泛应用于三大框架底层。传统我们通过xml文本文件声明方式,而现在最主流的开发都是基于注解方式,代码量少,框架可以根据注解去自动生成很多代码,从而减少代码量,程序更易读。

注解的分类:JDK自带注解,元注解,自定义注解

JDK注解(就5个):
@Override :用来标识重写方法
@Deprecated标记就表明这个方法已经过时了,但我就要用,别提示我过期
@SuppressWarnings(“deprecation”) 忽略警告
@SafeVarargs jdk1.7出现,堆污染,不常用
@FunctionallInterface jdk1.8出现,配合函数式编程拉姆达表达式,不常用

元注解(用来描述注解的注解,就5个):
@Target 注解用在哪里:类上、方法上、属性上等等
@Retention 注解的生命周期:源文件中、字节码文件中、运行中
@Inherited 允许子注解继承
@Documented 生成javadoc时会包含注解,不常用
@Repeatable注解为可重复类型注解,可以在同一个地方多次使用,不常用

@Target ElementType…
描述注解存在的位置:
ElementType.TYPE 应用于类的元素
ElementType.METHOD 应用于方法级
ElementType.FIELD 应用于字段或属性(成员变量)
ElementType.ANNOTATION_TYPE 应用于注释类型
ElementType.CONSTRUCTOR 应用于构造函数
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.PACKAGE 应用于包声明
ElementType.PARAMETER 应用于方法的参数

@Retention RetentionPolicy…
该注解定义了自定义注解被保留的时间长短,比如某些注解仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中; 编译在class文件中的注解可能会被虚拟机忽略,而另一些在class被装载时将被读取。

为何要分字节码文件中有还是没有呢?如果没有时,反射技术就拿不到,从而就无法去识别处理。它的值一共3种:SOURCE 在源文件中有效(即源文件保留);CLASS 在class文件中有效(即class保留);RUNTIME 在运行时有效(即运行时保留)

自定义注解
注意:注解的语法写法和常规java的语法写法不同

反射

**Reflection(反射)**是java程序开发语言的特征之一,它允许运行中的java程序对自身进行检查,或者说“自审”,也有称作“自省”。反射非常强大,它甚至能直接操作程序的私有属性。反射就像一面镜子,它可以在运行时获取一个类的所有信息,可以获取到任何定义的信息(包括成员变量,成员方法,构造器等),并且可以操作类的字段,方法,构造器等部分。有一个框架Spring就是一个非常专业且功能强大的产品,它可以帮我们创建对象,管理对象。以后我无需手动new对象,直接从Spring提供的容器中的Beans获取即可。Beans底层其实就是一个Map<String,Object>,最终通过getBean(“user”)来获取。而这其中最核心的实现就是利用反射技术。

反射需要用到的API
获取字节码对象
Class.forName(“类的全路径”);
类名.class
对象.getClass();

常用方法:
获取包名 类名
clazz.getPackage().getName()//包名
clazz.getSimpleName()//类名
clazz.getName()//完整类名

获取成员变量定义信息
getFields()//获取所有公开的成员变量,包括继承变量
getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量
getField(变量名)
getDeclaredField(变量名)

获取构造方法定义信息
getConstructor(参数类型列表)//获取公开的构造方法
getConstructors()//获取所有的公开的构造方法
getDeclaredConstructors()//获取所有的构造方法,包括私有
getDeclaredConstructor(int.class,String.class)

获取方法定义信息
getMethods()//获取所有可见的方法,包括继承的方法
getMethod(方法名,参数类型列表)
getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法
getDeclaredMethod(方法名,int.class,String.class)

反射新建实例
clazz.newInstance();//执行无参构造创建对象
clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象
clazz.getConstructor(int.class,String.class)//获取构造方法

反射调用成员变量
clazz.getDeclaredField(变量名);//获取变量
clazz.setAccessible(true);//使私有成员允许访问
f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null
f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null

反射调用成员方法
Method m = Clazz.getDeclaredMethod(方法名,参数类型列表);
m.setAccessible(true);//使私有方法允许被调用
m.invoke(实例,参数数据);//让指定实例来执行该方法

暴力反射
指可以将程序中的私有属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如下:

内部类

**概述:**如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。就是把类定义在类的内部的情况就可以形成内部类的形式。A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:

特点:
1、内部类可以直接访问外部类中的成员,包括私有成员
2、外部类要访问内部类的成员,必须要建立内部类的对象
3、在成员位置的内部类是成员内部类
4、在局部位置的内部类是局部内部类

总结:
成员内部类被Private修饰以后,无法被外界直接创建对象使用,所以可以创建外部对象类对象,通过外部类对象间接访问内部类的资源
静态资源访问时不需要创建对象,可以通过类名直接访问,访问静态类中的静态资源可以通过“…”链式加载的方式访问
匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用