Java语言基础知识全总结

一.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.             举例:OutOfMemoryErrorThreadDeath线程死锁,VirtualMachineError虚拟机错误

b)        Exception

                        i.             分类:根据是否能在编译阶段检查,分为检查异常和非检查异常

                      ii.             检查异常必须程序员手动在代码中捕获,否则编译不通过

                     iii.             非检查异常编译阶段不会发现,一般是代码有问题,由JVM自动捕获和抛出,例如空指针异常NullPointerException,数组越界异常ArrayIndexOutOfBoundsException,类型转换异常ClassCastException

3.        检查异常

a)        处理方式:try/catch代码块中处理异常,或者函数签名中声明throws,将异常抛给上层调用者处理

b)        分类:IOExceptionSQLException

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(对象,方法需要的参数)

猜你喜欢

转载自blog.csdn.net/NNnora/article/details/80384107