概述
一.Java的优点
1. 跨平台性。一次编译,到处运行。Java编译器会将Java代码编译成能在JVM上直接运行的字节码文件,C++会将源代码编译成可执行的二进制代码文件,所以C++执行速度快
2. 纯面向对象。Java 所有的代码都必须在类中书写。C++兼具面向对象和面向过程的特点?
3. Java提供很多内置的类库,例如支撑多线程和GC
4. Java由JVM自动进行内存分配与回收,c++需要开发人员管理内存。
5. 简洁。去除了C++的指针,多重继承等
6. Java不支持多继承,但是可以实现多个接口来做到。C++支持多继承
7. Java不支持运算符重载
8. Java不支持自动类型转换,必须开发人员显式转换,C++支持自动转换
二、Public static void main(String[] args)解析
1. Public表示这个方法可以由任何类或对象访问
2. Static表示全局,static修饰的方法为静态方法,独立于该类的所有实例,可以通过类名访问,不能通过对象名访问,
a) 方法内不能使用this super等关键字。
b) 方法内不能访问实例变量,因为实例变量属于某个具体实例的
c) Static修饰的静态变量在全局内只有一个拷贝,但是实例变量每创建一个实例就会分配一次内存
d) Static的特点:
i. 对共享数据单独空间存储,节省空间
ii. 可以直接通过类名调用
iii. 缺点:生命周期过长,且静态方法内只能访问静态变量,访问局限性。
3. void表示可以没有return
4. main()表示入口方法
三、一个Java文件是否可以有多个类?
1. 一个Java文件可以包含多个类
2. 但是! public访问权限的类只能有一个且必须与文件名同名
3. 如果文件中没有public访问权限,那么文件名是任意名字都可以。
4. Javac编译时会对每一个类、包括内部类生成一个单独的class文件
四、面向对象与面向过程
1. 面向过程:以过程为中心,分析出解决问题的步骤,用函数把这些步骤一步一步实现。
a) 举例:汽车发动、汽车行驶、汽车刹车三个事件,针对三个事件写三个函数,按过程调用
2. 面向对象:一个对象类定义了具有相似性质的一组对象,核心是继承封装多态
a) 关注的汽车这类对象有的方法,可以调用任意一个方法
五、继承封装多态
继承
1. 将类的共有属性抽象,提取到父类中,提供代码复用性
2. java只支持单继承,多继承可以用接口实现
3. 判断对象A是否继承了类:Object A instanceOf ClassB
4. 判断对象A是否【是】该类的对象:ObjectA.getClass() == Parent.class
封装
1. 隐藏对象的属性和实现细节,仅提供公共访问方式访问,提供get/set函数访问属性
2. 好处:提高代码健壮性
多态
1. 动态方法调用。当父类对象的引用变量指向子类对象实例时,被引用对象(子类)的类型决定了调用谁的成员方法,而非引用变量的类型决定
2. 表现:重载(编译时多态,编译时就能确定执行哪个)、重写(运行时多态,运行时决定)
3. 应用:例如登陆页面有两种用户:管理员和普通用户,有相同的方法但是登陆后进入不同的界面,所以都继承父类的login方法,但是不同对象有不同的操作
六、JDK与JRE
1. JDK是JavaDevelopment Kit,java开发工具,包含JRE和编译器javac。如果需要写Java代码需要安装JDK
2. JRE是Java RuntimeEnvironment ,Java运行时环境,如果只需要运行java程序,只需要安装jre
七、一个Java程序编译、加载、执行全过程
1. Java源码编译
a) 分析和输入到符号表
b) 注解处理
c) 语义分析
d) 生成class字节码文件,class字节码文件包括:结构信息,例如class文件格式的版本号,各部分的数量与大小;元数据:方法声明和常量池;方法信息:对应java语句和表达式对应的信息
2. 类加载
a) 类加载分为五个过程:加载、验证、准备、解析、初始化
b) 发生时机:运行期间(因为编辑阶段已经结束)
c) 加载:
i. 将编译生成的class字节码文件的二进制字节流读到内存中,将其放到方法区中,在方法区中创建一个Class对象。---工具:类加载器(见八)
ii. 验证:确保class文件中的字节流符合java虚拟机的要求。验证以下四方面“
1. 文件格式的验证:c字节流是否符合class文件的规范‘
2. 元数据验证:对class文件描述的信息进行语义分析,例如这个类是否有父类、是否继承了不允许被继承的父类
3. 字节码验证:确保程序语义是合法的
4. 符号引用验证:为第四步提供服务,确保解析动作能正确执行
iii. 准备:
1. 为【类变量】分配内存并初始化为默认值。
2. 类变量指的是static修饰的、属于类的而非属于对象实例的变量,实例变量会在对象实例化时被分配堆空间
iv. 解析:把常量池中的符号引用转换为直接引用
v. 初始化:这一步才是开始执行java代码。
1. 根据程序员的意志对【类变量】赋初值。
2. 必须初始化的情况:
a) 创建类的实例对象new之前必须对类初始化
b) 初始化一个类时,若父类没有被初始化,那么必须先初始化其父类
c) 通过反射调用某个类时,若类还未初始化,必须先初始化
d) 访问类的静态变量static时
e) 虚拟机启动时必须先初始化指定的主类
3. 类执行
八、类加载器
1. 作用:读取class字节码文件,将其加载到方法区中并生成一个对象
2. 分类
a) Bootstrap启动类加载器,加载Java的核心jar库(由C++编写),<JAVA_HOME>/lib下
b) Extension ClassLoader扩展类加载器。加载Java的扩展类库,<JAVA_HOME>/lib/ext
c) Application ClassLoader应用类加载器。加载java应用类,classpath路径下
d) 自定义类加载器。
3. 双亲委派模型
a) 原理:当一个类加载器请求加载某个类时,他不会自己尝试去加载这个类,而是把这个请求委托给自己的父加载器,由父加载器再向上委托。每一层的累积都是这样,因此所有的类加载器的请求最终都会被委托到启动类加载器,只有当父加载器返回无法加载时才会自己尝试加载。
b) 好处:防止类加载过程中的安全性问题。若一个开发者编写了一个与java类库同名的java类Object,那么不会使用自己定义的类加载器,而是由上层的类加载器加载。
i. 例子:
ii. 写了一个Object类放在java.lang包下,那么编译不出错,但是运行出错,因为真正的Object类被启动类加载器加载到内存中,若应用类加载器也成功加载的话将会出现多个不同的Object类
iii. 不放在java.lang包,不会报错,因为启动类加载器和扩展类加载器的加载路径中没有这个class,所以应用类加载器会去尝试主动加载。
4. 自定义类加载器
a) 自定义类加载器要继承java.lang.ClassLoader类,重写其findClass()方法
b) 原因:JVM提供的类加载器只会加载指定路径下的jar和class,如果想要加载网络上或者其他目录的class文件,需要自定义类加载器
c) 实现:
i. 继承ClassLoader,
ii. 实例变量:一个加载路径为path,类加载器的名字name
iii. 构造方法是传入一个字符串设置为类加载器的名字name
iv. findClass方法:new一个文件输入流读入路径下该名字.class这个class文件,写入到字节流,即转换成二进制字节码,然后调用defineClass函数传入name 二进制字节码创建Class对象
d) 使用:
i. New一个类加载器的实例。
ii. 调用加载函数生成一个Class对象
iii. Class.newInstance()生成对象实例
九、对象实例化
1. 对象实例化的五种方法:
a) new关键字
b) 使用Class类的newInstance方法,实质上也是调用的构造器的方法,只不过是无参构造函数
c) 调用Class对象的getConstructor方法得到构造器,对指定构造器调用newInstance方法
d) clone方法,调用clone方法的对象所属的类必须实现了Cloneable接口
e) 反序列化:反序列化一个对象时,JVm会创建一个对象接收,然后赋值给我们定义的对象引用。类必须实现Serializable接口
2. 对象实例化过程
a) 编译
b) 类加载
c) 执行static代码块
d) 在堆中开辟内存空间,分配内存地址,建立对象的实例变量,进行默认初始化
e) 实例变量的初始化
f) 构造代码块
g) 构造方法
h) 将内存地址赋值给虚拟机栈中-局部变量表-reference表中的对象引用p
十、变量
1. 成员变量作用于整个类中,存放于堆中(因为对象的存在而存在),可以不用显式初始化,因为对象实例化的一步就是为成员变量赋初值,可以用访问修饰符修饰
2. 局部变量作用域方法中,存放于虚拟机栈-局部变量表中,必须显式初始化,不能用访问修饰符修饰
3. 同名时,默认使用局部变量,若变量前加this那么使用成员变量
十一、访问权限
1. public:子类中、不同包中
2. protected:同一包中、子类中
3. default:同一包中,该类中
4. private:只能在该类中访问
十二、构造函数
1. 构造函数名和类名一致,不能有返回语句,函数签名也不能有返回值,void也不行
2. 对象一旦建立就会调用构造函数
3. 若类中未定义构造函数,那么系统默认一个无参的构造函数;若自定义了构造函数,那么不会再创建空的构造函数了。
4. 一个类可以有多个构造函数
5. 构造函数不能由开发者调用,必须由系统在创建对象时1调用
6. 子类的第一条语句就是调用父类的构造函数,因为初始化子类必须先初始化父类,若父类有无参构造函数则不需要显式调用;父类无无参构造函数则必须显式指明super.父类构造函数
7. 执行顺序:父类静态变量/静态代码块-子类静态变量/静态代码块-父类实例变量/实例代码块-父类构造函数-子类实例变量/实例代码块-子类构造函数
一个实例变量在对象初始化过程中会被赋值几次?
1. JVM在为对象分配内存后,对每一个实例变量赋默认值
2. 若在声明变量时进行了赋初值操作,那么第二次赋值
3. 若在实例代码块中对变量进行了赋值操作,那么第三次赋值
4. 若在构造函数中对实例变量进行了复制操作,那么第四次赋值
5. 所以,最少一次,最多4次
十三、重载和重写
1. 是Java编译时多态(重载)和运行时多态(重写)的表现
2. 重载:
a) 多个同名函数在同一个类中出现
b) 参数列表【必须】不同
c) 返回值不关注
d) 访问权限不关注
e) 异常类型不关注
3. 重写:
a) 子类中有和父类函数同名的函数
b) 参数列表、返回值必须相同(返回值可以是父类该方法返回值的子类)
c) 不能缩小访问权限
d) 异常类型必须是子类
e) 不能对final修饰的方法重写
十四、抽象类和接口
1. 抽象类
a) 用处:将一些信息抽象到一个类中,且不需要使用他的对象,限制子类的设计。
b) 定义:包含抽象方法的类叫抽象类,用abstract修饰
c) 抽象类中也可以包含非抽象方法
d) 抽象类不能创建对象实例
2. 抽象方法
a) 只有方法声明,没有实现
b) 访问权限必须是public/peotected,因为要子类去实现
c) 如果一个子类继承一个抽象类,那么必须实现其所有抽象方法,否则子类也必须是抽象的
3. 接口
a) 用处:比抽象类更抽象,只是为了标记有哪些功能
b) 接口中只能包含抽象方法,不能包含非抽象方法
c) 接口中的变量指定为public static final,static表示不需实例化就可以调用,final不可更改
d) 接口中的方法public abstract
e) 一个类可以实现多个接口
4. 标记接口
a) 接口的源码为空
b) 三个:RandomAccess标记是否支持按索引随机访问;Cloneable标记是否支持clone方法;Serializable表示是否支持序列化
5. 对比
a) 抽象类可以包含非抽象方法,接口中不可以包含
b) 抽象类必须有构造函数(可以隐式),因为子类实例化需要调用父类的构造函数;接口不能有构造方法,写入构造方法编译出错
i. 为什么?因为构造方法用于初始化成员变量,接口的成员变量全部是public static final,不需要构造函数来初始化。且
ii. 一个类可以实现多个接口,若接口有构造方法,不好确定构造方法的调用次序
c) 抽象类中可以包含static静态代码块和静态方法,接口不可以(无法被实现)
d) 抽象类是对事务的抽象,接口是对行为的抽象。继承类是一个是不是的关系,实现接口是一个有没有的关系
十五、序列化Serializable
1. 序列化的类必须实现Serializable标记接口。
2. 使用原因:
a) 持久化对象。将对象的实例变量持久化到磁盘中,日后可以重新恢复这个对象
b) 远程方法调用。将对象从一个应用程序域发送到另一个应用程序域,例如分布式应用
3. 避免序列化:transient关键字修饰的成员变量不会被序列化,反序列化后是默认值。
4. 序列化只会对实例变量序列化,所以static修饰的变量不会被序列化
5. 方法不会被序列化,序列化的是类名和属性
6. 序列化的步骤
a) 创建一个ObjectOutputStream输出流,调用writeObject方法
7.
public static void main(String[] args)throws Exception {
8.
9.
Constructor<Student> constructor= Student.class.getConstructor(Integer.class);
10.
Student stu3 =constructor.newInstance(123);
11.
12.
// 写对象--序列化
13.
ObjectOutputStream output = newObjectOutputStream(new FileOutputStream("student.bin"));
14.
output.writeObject(stu3);
15.
output.close();
16.
17.
// 读对象---反序列化
18.
ObjectInputStream input = newObjectInputStream(new FileInputStream("student.bin"));
19.
Student stu5 = (Student)input.readObject();
20.
System.out.println(stu5);
21.
}
十六、内部类(补充更多)
1. 概念:将一个类定义在另一个类的内部,内部类在编译时也会被单独编译成一个class字节码文件
2. 四种内部类:
a) 成员内部类
b) 局部内部类:定义在方法内部
c) 匿名内部类
i. 没有名字的内部类,只会在该外部类的一个方法中使用到,所以不必要定义一个成员内部类然后new实例对象,只需要在该方法的参数列表中new
history_bt.setOnClickListener(newOnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated methodstub
}
i.
});
因为直接new 类,所以只会创建一个实例
d) 静态内部类
十七、泛型
1. 概念:参数类型化,在调用时传入具体的参数类型。
2. 特点:
a) 编译阶段有效。在编译阶段会检查类型安全;正确编译后会将泛型信息擦除,泛型信息不会进入运行阶段
b) 类型擦除目的:向下兼容,兼容java5之前的非泛型代码
3. 泛型的类型只能为【类】类型,不能为基本数据类型(可以使用其封装类)
分类:泛型类、泛型方法、泛型接口
1. 泛型类
a) 类的定义中不指定具体类型,在类的实例化时指定具体的参数类型,所以可以产生不同类型的对象
b) 代表:容器接口的具体实现类:ArrayList HashMap HashSet
c) 模板:
2. 泛型接口
a) 代表:List Set Map
b) 虽然只创建了一个泛型接口,但是可以根据传入泛型的类型的不同,形成不同的接口
3. 泛型方法
a) 概念:方法声明时不制定具体类型,用泛型代替;调用时给出具体的类型
b) 格式:在修饰符之后,返回类型之前加入一个泛型参数列表<T, G>,表明方法中的哪个字母代表泛型参数
c) 例子:
i.
通配符
1. 非限定通配符:?
a) 解决问题:不同版本的泛型类实例不兼容,子类也不可以。例如一个实例使用Number作为形参,如果存入Integer那么会报错。
b) 用?代替具体的类型
c) 但是一般只用于读,不用于写
2. 限定通配符
a) 上边界限定:? extends 类A,表示传入的类型必须是A类或者其子类类型
b) 下边界限定:? super 类A,表示传入的类型必须是A或其父类
十八、Java数据类型
1. 三类:基本数据类型、枚举类型、引用类型
2. 基本数据类型(8):char-16,boolean-1,Byte-8,short-16,int-32,long-64,float-32,double-64
3. String不是基本数据类型
十九、值传递&引用传递
1. 值传递
a) 将实参的值copy一份给形参,内存中存在着两个相等的基本类型,方法中的操作都是对这个形参的值的修改,不会影响到原来的实参
b) 基本数据类型
2. 引用传递
a) 将实参的引用copy一份传给形参,函数接收的是原始值的内存地址。形参实参指向的是同一块内存地址,所以形参的修改会影响实参
3. 本质上,两种传递都是值传递,引用传递可理解为传递的是地址值
4. 注意:String不是基本书库类型,但是形参的修改也不会影响原来实参,因为String是不可变的,一旦创建若修改那么会新建一个String对象来存储,方法返回的是新对象的引用地址
二十、==与equals
二十一、Object类
1. Object类是其他所有类的基类,所有的Java类都直接或间接继承Object类;接口不集成Object类
2. 方法:
a) clone()方法
i. 目的:解决对象复制时的引用传递,使得改变新对象时旧对象不会随之改变。
ii. 方法签名:protected native object clone() throws CloneNotException
iii. 调用clone函数返回的是新对象的引用
iv. 用native关键字修饰(由C/C++实现)
v. 要使用clone方法,类必须实现了Cloneable标记接口
vi. 深拷贝与浅拷贝
1. 区别:在对于有引用类型的变量的对象的拷贝时,是否会对引用指向的对象进行拷贝
2. 浅拷贝:在拷贝对象时,对于基本数据类型的变量会重新复制一份,但是对于引用类型的变量只是复制引用地址,对引用指向的对象没有复制。例如对象c1中有引用类型的变量,c2是c1的浅拷贝,那么c1中引用变量所指的对象的成员变量改变时,c2也会变
b) getClass()方法
i. getClass方法得到这个对象所属类的类对象
ii. ObjectA.getClass() == 类.class
c) equals方法与hashCode()方法
i. 必须成对重写
ii. equals方法默认是基本数据类型比较值,引用类型比较地址(相当于==),可以通过重写改为逻辑上的值比较
iii. 两个对象相等,那么equals相等
iv. equals相等,那么hashcode一定相等
v. hashCode相等,equals不一定相等
d) toString()方法
i. 由对象所属的类和对象的hash码唯一确定,
ii. 源码:
public String toString() {
returngetClass().getName() + "@" + Integer.toHexString(hashCode());
}
e) wait/notify/notifyAll方法
i. 只能在synchronized代码块中使用
ii. 都是Object的方法
iii. 线程A调用wait方法,那么A释放锁并进入等待唤醒状态,其他线程B拿到锁后调用notifyAll方法,唤醒A,当B的同步代码块执行完毕后释放锁
f) finalize()方法
i. 与JVM垃圾回收机制有关,在可达性分析后,会检查是否有必要执行finalize方法来进行自我拯救
ii. 源码是一个空方法,由JVM自动调用,不能人工调用
iii. 任意一个对象的finalize方法只会被调用一次,所以finalize没必要执行的情况:1已经执行过,2finalize方法未被重写
二十二、String,StringBuilder,StringBuffer
1. 他们三个类的源码都是final修饰的,【不可被继承】
2. String一旦创建不可更改,StringBuilder和StringBuffer创建后可以更改
3. String不可修改的原因:String存储在final修饰的char[]数组,final修饰的变量一旦赋值不可以更改
4. 使用
a) String的好处是相同的字符串不汇率再往常量池中放,常量池中对于相同的只保存一份,所以节省空间
b) StringBuffer和StringBuilder是长度可变的,可以用append追加字符,所以涉及频繁的字符串修改操作,选择StringBuffer/StringBuilder
c) StringBuffer类中所有的方法都是synchronized修饰的(方法与StringBuilder相同),所以是线程安全的。StringBuilder线程不安全==效率高于StringBuffer
二十三、STL
1. 分类:Collection接口、Map接口、工具类
a) Collection接口包括List和Set
b) List包括ArrayList,LinkedList,Vector,Stack
c) Set包括HashSet,TreeSet,LinkedHashSet,SortedSet,Set中的实现类都是基于Map实现的
d) Map包括HashMap,TreeMap,LinkedHashMap,HashTable
e) 工具类包括Collections(操作集合),Enumeration(枚举),Iterator(遍历),Arrays(操作数组)
2. 标记接口:
a) ArrayList和Vector和Stack实现了三个标记接口,其他的STL都只实现了两个接口(Cloneable,Serializable)
3. List接口
a) 特点:List中的元素时有序的,允许重复,可以精确控制每个元素的插入删除位置
b) 分类:ArrayList LinkedList Vector Stack(继承Vector)
c) List的remove方法有两个:
i. remove(Object o):删除List中的元素o
ii. remove(int index):删除index位置的元素
iii. 区别:传入参数是对象or int
iv. remove后,后面的元素会依次前移
d) ArrayList:
i. 特点:
1. 线程不安全
2. 底层数据结构是动态数组
3. 需要扩容
ii. 扩容机制:
1. 默认初始容量为10,扩容为1.5倍
2. 数组扩容时,会将原数组中的元素拷贝一份到新数组,操作代价很高
iii. 线程不安全的解决方法:
1. Coleections工具类中有一个synchronizedList方法,可以将list包成线程安全的list
2. concurrent包下有一个CopyOnWriteList类
a) 特点:读写分离,读和写不同的容器
b) 原理:写时复制,多个线程读时共享一个容器中的数据,但是有一个线程在add数据时,会复制一个容器副本,大小为length+1,在新List中添加数据,最后将原容器的引用指向新容器,在这个过程中其他线程读,仍是读原容器的数据。
c) 优点:多线程并发,读时可写,写时可读
d) 缺点:两个数组长期驻扎在内存中,内存占用大;只能保证数据的最终一致性,不能保证实时一致性
e) LinkedList
i. 特点:
1. 不是线程安全的
2. 基于双向链表实现,所以可以用作栈、队列
3. 无默认初始大小,无扩容机制
ii. add元素的原理:
生成一个新的Node节点,前驱pre为尾节点,后继为null,若尾节点为null,那么将头结点赋值为Node,否则将尾节点的后继赋值为Node
f) Vector
i. 特点:
1. 线程安全
2. 底层数据结构是动态数组
3. 初始为10,扩容为2倍
g) ArrayList与LinkedList对比
i. 相同
1. 都是List接口的实现类
2. 都实现了Cloneable,Serializable接口,可以克隆,可以序列化
ii. 不同:
1. 一个基于动态数组,一个基于双向链表
2. ArrayList需要扩容,初始大小为10;LinkedList不需要扩容,无初始默认大小
3. 对于随机访问,ArrayLIst效率更高,因为LinkedList需要移动指针;对于插入删除元素,LinkedList效率更高,因为ArrayList要移动数据(除非在末尾)
h) ArrayList与vector对比
i. 相同
1. 都是基于动态数组
2. 都需要扩容
3. 都支持索引随机访问
4. 初始大小都为10
ii. 不同
1. 一个线程不安全,一个线程安全
2. ArrayList扩容为1.5倍,Vector为2倍
3. 线程安全的Vector和HashTable支持Enumeration遍历,线程不安全的不支持
i) synchronizedList与Vector对比
i. 不同点:synchronizedList扩容为1.5倍,Vector扩容速度更快为2倍
4. Set接口
a) 特点:无序,不重复
b) 分类:HashSet,TreeSet,LinkedHashSet,SortedSet
c) HashSet
i. 特点
1. 基于HashMap实现
2. 线程不安全
3. 允许null元素,但只能有一个
d) TreeSet
i. 特点
1. 元素存储在红黑树中,有序
2. 默认为升序
3. 线程不安全
e) LinkedHashSet
i. 特点:保存【插入】顺序
f) SortedSet
i. 特点
1. 有序
5. Map接口
a) 特点:键值对存储
b) 分类:HashMap,HashTable,TreeMap,LinkedHashMap,ConcurrentHashMap
c) 演进:Java1.0引入HashTable,实现线程安全;Java5引入ConcurrentHashMap,是HashTable的替代,分段锁使得效率更高,读写可并发
d) 三种视图
i. keyset():返回键的集合(Set)
ii. values()返回值的集合(Collection)
iii. entrySet()返回键值对集合(Set)
e) HashMap
i. 存储结构
1. 是Entry数组+链表的组合,每个Entry元素对应一个桶(链表),链表的每个节点存储的是key-value【键值对】
2. 如果Entry数组够大,即使比较差的hash算法也能够数据分散;如果Entry数组过小,那么及时比较好的hash算法也会产生较多的碰撞冲突,所以需要时间空间权衡
ii. 扩容机制
1. 初始Entry数组大小(桶数)为16,当HashMap中元素个数大于负载因子*桶数,那么扩容为原来的两倍
2. Java1.8变化
a) 在Java1.7及之前,扩容后原Map中的元素需要重新hash,重新散列到新Map,在1.8之后,扩容后不需要重新hash,新容量=旧容量*2,所以看hash码新增的一位是0/1,0则保持原位置,1则原位置+oldLength
b) 相当于新建两条链表,位置不变的插入链表A,位置变化的插入链表B,然后拼接两条链表
c) 好处:省去了重新计算hash码的时间,而且也保证了随机,也避免了向新Map中插入元素时的倒置,所以不会死锁
3. java7之前HashMap在多线程情况下CPU占用率会100%的原因?
a) 若两个线程都检测到HashMap需要扩容,俺么他们会试着同时调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的桶中时,HashMap采用的是头插法,将元素放在头部。如果条件竞争发生,那么死循环。
b) 解决办法:
i. HashTable代替不安全的HashMap
ii. synchronizedMap
iii. concurrentHashMap
iv. java8之后的新的扩容机制
iii. Hash算法
1. Java1.7及之前:计算出元素key的hashCode,对数组的大小取模,hash%length-或者hash&(length-1),得到位于哪个Entry(桶的位置),若该桶无元素,直接插入;若有元素,那么链表法在该位置生成一条链表,插入到链表末尾
2. java1.8之后:HasCode的高16位异或低16位,然后再对数组的大小取模,这样能保证高低位全部参与运算
iv. hash攻击
1. 极端情况下所有的元素hashCode经过映射后都位于同一个桶,形成一条长长的链表,这样get一个元素就相当于链表查找,O(N)复杂度,所以Hash算法和HashMap的初始大小很重要。
a) 例如hash攻击:有人知道计算hashCode的源码,所以伪造相同的hashcode攻击。这样hashMap就会退化成链表。
b) 解决:
i. 限制客户端请求的参数个数
ii. 限制post请求的数据报大小
iii. 修改服务器的hash算法更复杂
2. java7处理退化链表是使用TreeMap代替链表,java8是链表长度超过8使用红黑树
v. 碰撞冲突
1. 拉链法,将相同hash值存放在同一个桶
2. 开放地址法:通过一个探测算法,当某个桶被占据那个继续查找下一个可以使用的桶
vi. put源码:
1. 计算得到Entry数组的位置,若桶中无元素,直接插入,有元素那么判断桶中首位元素是否与当前key相同
2. 相同那么覆盖
3. 不同,那么判断桶中是否是红黑树,若是那么直接插入键值对;不是那么执行链表的插入操作,若在插入过程中发现了相同的key那么覆盖
4. 插入成功后判断是否需要扩容
vii. get源码:
1. 对key检查,若key为null那么entry[0]的元素会被返回
2. 对key调用hashCode函数计算hash值,根据hash值找到Entry
3. 迭代Entry中的链表,调用equals方法检查key的相等性
f) HashMap与HashTable的区别:
i. HashMap初始桶数为16,HashTable为11
ii. HashTable线程安全,所有的方法用synchronized修饰
iii. HashMap中可以出现一个null的键,多个null值,HashTable中键值都不能为null
iv. 单线程下HashMap快于HashTable
v. HashTable可以用Enumeration遍历
二十四、异常
1. 特点:
a) Java中所有的异常都继承Trowable类
b) 异常被处理完成后,会在下一个垃圾回收过程中被回收掉
2. 分类:
a) Error
i. Error是程序终结者,出现Error那么程序结束
ii. 举例:OutOfMemoryError,ThreadDeath线程死锁,VirtualMachineError虚拟机错误
b) Exception
i. 分类:根据是否能在编译阶段检查,分为检查异常和非检查异常
ii. 检查异常必须程序员手动在代码中捕获,否则编译不通过
iii. 非检查异常编译阶段不会发现,一般是代码有问题,由JVM自动捕获和抛出,例如空指针异常NullPointerException,数组越界异常ArrayIndexOutOfBoundsException,类型转换异常ClassCastException
3. 检查异常
a) 处理方式:try/catch代码块中处理异常,或者函数签名中声明throws,将异常抛给上层调用者处理
b) 分类:IOException,SQLException
4. try/catch处理异常
a) try中放可以发生异常的代码,如果不发生异常那么执行finally,如果异常那么去匹配catch
b) try必须搭配catch或者finally
c) 每一个catch块用于捕获一个异常,catch块中可处理异常,也可thow给上层处理
i. catch块遵循由上到下匹配
ii. catch块若有父子关系,必须从子类到父类
iii. 若匹配不到,那么执行finally,且在函数的调用者中匹配异常
d) finally一般用于关闭释放资源,一般不在finally中做其他处理;无论是否抛出异常,finally必须执行
e) 若try中有return,finally无return,那么finally仍要执行,只不过return的结果不会受finally影响
f) 若finally中有return,无论try/catch是否有return,那么只会返回finally中的return,且不会抛出异常
g) 所以!不要再finally中return!!!
5. throw/thorws处理异常
a) 使用场景:函数体中
b) 若本层不处理异常,那么函数签名中使用throws或者函数体中throw交给上层处理
c) throw
i. 一个动作,将产生的异常抛给上层调用者
d) throws
i. 在函数签名中使用
ii. 用来指明该方法处理不了,需要上层调用者处理的异常
iii. throws的列表可以是多条异常
e) 对比
i. 都是消极处理方式,表示本层不能处理,需要上层处理
ii. throw出现在函数体,throws出现在函数签名
iii. throw语句执行,那么一定抛出了异常;throws则是表示可能抛出异常,不是绝对会发生
6. 自定义异常类
a) 所有的异常都是Throwable的子类
b) 若需要自定义一个检查异常,那么继承Exception
c) 若需要自定义一个非检查异常(运行时异常),继承RuntimeException类
d) 用处:
使用自定义异常,可以定义抛出异常后的异常信息,隐藏底层的堆栈信息,更具可读性
7. 异常链
a) 当子类重写父类带有throws声明的方法,子类throws的异常一定不能大于父类throws的异常
二十五、反射
概念
1. 在运行状态中,对于任意一个类,可以获得类、对象、方法的所有信息
2. 用处:不需要主动加载类,程序在运行时根据需要动态加载类,这些类可能之前用不到所以没有加载到JVM,而是在运行时根据需要动态加载(new是静态加载)
3. 优点:增加灵活性,运行时可以动态获取对象实例
4. 缺点:效率低(解释操作),破坏封装因为可以通过反射得到私有的成员变量和成员方法
应用
1. 可以通过反射获取一个类的class对象、构造方法、成员变量、成员方法
2. 获取一个类的class对象的三种方式
a) 调用Object.getClass方法
b) 类的class属性
c) 通过Class类的静态方法forName(类名)
3. 通过反射获取类的构造方法并使用
a) 使用第三种方式获取到Class对象
b) 通过Class类的getConstructors(所有public)/getDeclareConstructors(所有)/getConstrcutor(参数类型的class对象)-指定的public构造方法/getDeclaredConstructor(参数类型的class对象)-指定的构造方法,系列函数获取到构造方法
c) 调用构造方法Constructor.newInstance(构造方法的参数)生成一个新的初始化了的实例
4. 通过反射获取类的成员变量并使用
a) 获取到Class对象
b) 通过Class类的getFields函数获取到指定的成员变量
c) 首先使用Constructor的newInstance生成一个对象
d) 调用Field.set(对象,字段值)设置成员变量的值
5. 通过反射获取类的成员方法并使用
a) 获取Class对象
b) 通过Class类的getMethods获取到指定的成员方法
c) 首先使用Constructor的newInstance生成一个对象
d) 调用Method.invoke(对象,方法需要的参数)
最后
以上就是追寻芝麻为你收集整理的Java语言基础知识全总结一.Java的优点二、Public static void main(String[] args)解析三、一个Java文件是否可以有多个类?四、面向对象与面向过程五、继承封装多态六、JDK与JRE七、一个Java程序编译、加载、执行全过程八、类加载器九、对象实例化十、变量十一、访问权限十二、构造函数一个实例变量在对象初始化过程中会被赋值几次?十三、重载和重写十四、抽象类和接口十五、序列化Serializable十六、内部类(补充更多)十七、泛型十八、Java数据类型十九的全部内容,希望文章能够帮你解决Java语言基础知识全总结一.Java的优点二、Public static void main(String[] args)解析三、一个Java文件是否可以有多个类?四、面向对象与面向过程五、继承封装多态六、JDK与JRE七、一个Java程序编译、加载、执行全过程八、类加载器九、对象实例化十、变量十一、访问权限十二、构造函数一个实例变量在对象初始化过程中会被赋值几次?十三、重载和重写十四、抽象类和接口十五、序列化Serializable十六、内部类(补充更多)十七、泛型十八、Java数据类型十九所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复