最近再换工作,感觉一片萧条,也趁这个时间多复习复习吧,只是单纯的再看一遍感觉记忆不深刻,毕竟看过和理解了还是相差很远的,所以准备看题和复习的时候记录一下复习的一些内容,加深理解
1.抽象类
- abstract 关键字修饰
- 可以同时存在抽象方法(没有方法体,使用abstract修饰)、普通方法,也可以只单独存在任意一个
- 不能被实例化(内存在抽象方法,实例化无意义)
- 被使用需要存在子类,抽象类作为父类以被继承的方式使用,子类只能继承一个抽象类(但如果是接口,可以实现多个接口)。
- 子类必须实现抽象类父类中的抽象方法
- 简单理解抽象类的用处:就是当父类有一部分功能确认统一,无需子类重写,另一部分需要子类重写时可以使用。
- 抽象方法不允许使用[private]修饰(因为要被子类重写)
- 抽象类可以有构造方法,子类对象实例化时,会先执行父类构造,再执行子类构造。
- 抽象类不允许被[final]声明,因为需要被继承
2.重载、重写
- 这两个词其实代表的概念很简单,只是容易混淆
- 重写:发生在子类中,子类对父类的方法进行重写操作(父类有了,子类再写一遍-重写);特点是:权限修饰子类不能比父类小,方法体内容子类可以变动外,方法其他部分和父类一致
- 重载:再同一个类中,方法名字相同,而传参(数量和类型)、返回参数类型、权限修饰,任意不同的就是重载。
3. == or equals
- [==] 比较的是栈中变量对应的值;如果是基础数据类型,栈中变量对应的数据就是这个值本身,使用[==]去比较是没问题的,但是如果是复杂数据类型;例如String,在栈中变量名对应的参数是一串指向堆空间中的地址,可能会存在实际值是一致的,但是在堆中声明了两个单独的变量,他们的地址不同,会导致[==]判断时,判断为不一致
- equals方法是Object中内置的方法,需要注意的是,如果你自己声明的对象没有重写这个方法的话,使用equals方法,本质上内部还是使用[==]进行判断堆空间地址;如下:
- String中的equals方法是重写了object中的equals,会依次比较堆空间地址、字符串长度、转换成char数组比较每个位置的每个字符是否一致。如下图,感兴趣的话可以看一下String这部分的源码,并不复杂
4.异常
- 关键字:try、catch、finaly、throws
- 如下:
5.集合
- List、Set继承Collection接口
- List、Set、Map ,三种集合的接口,分别是List(可重复数组),Set(不可重复数组),Map(键值对集合)
5.1 Map
- Map是 键-值对的集合,它本身是一个接口,具体的实现类有:
- HashMap:最常用的集合,数组+链表,非线程安全
- Hashtable:线程安全,相比HashMap效率较低
- TreeMap:tree结构,元素进行内部排序,不允许key为null
- LinkedHashMap:继承HashMap,增加了双向链表存储顺序,非线程安全
- IdentityHashMap:底层object数组,0位放key,1位放value,以此类推
- WeakHashMap:弱引用集合,主要特点是内部存入的数据,基于弱引用,一旦被GC扫描到,会被认为是垃圾数据进行清理;适用于用作缓存数据存储,避免缓存过多造成内存泄漏
- ConcurrentHashMap:线程安全安全,分段锁,同时兼顾性能和线程安全
5.1.1 HashMap
- 初始数组长度默认16,扩容量默认是0.75f;每次扩容时,都是当前长度*扩容量
- 数据结构采用的是数组+链表方式存储,JDK1.8以后,使用的是位桶+链表/红黑树的方式,主要增加了当单链表长度超过8个且数组长度超过64时,会将该链表会转换成红黑树结构
- 具体的逻辑是:存入的数据取Key值的hash值放入到指定的下标处,以链表结构存入,如果该下标处已存在数据,判断这个链表是否存在该key,如果没有,就在链表下追加数据,当数组长度超过64,链表长度超过8时,会将此链表转换成红黑树(增加查询速度)
- HashMap 实现了Serializable接口,支持序列化,实现了Cloneable接口,能被克隆。
- key和value都允许为null
5.1.2 Hashtable
- 数据存储几乎和hashMap一致,主要区别为:HashTable在方法上加上了synchronized锁,保证了安全,但效率较低
5.2 List
- List集合都是有序的
- LinkedList:双向链表,查询慢,增删快,非线程安全
- ArraryList:数组,查询快,增删慢,非线程安全;默认长度10,每次1.5倍扩容
- Vector:数组,线程安全,效率较低
- Stack:继承Vector,数组栈,后进先出,不推荐使用,如有需要建议用Deque
5.3 Set
- HashSet:底层由hashMap实现,无序(不保证插入顺序,但内部有hash值的顺序),非线程安全,可以存null
- LinkedHashSet:继承HashSet,增加了链表,保证有序
- TreeSet:二叉书结构,内部有序
6.String、StringBuffer、StringBuilder
- String对象由char[]数组构成,final修饰,不可变字符串;在堆空间中有一个字符串常量池的空间,用于存放String字符串
- StringBuffer可变字符串,线程安全
- StringBuilder可变字符串,线程不安全,
7.线程
7.1线程的实现方式
-
- 继承Thread类,并重写run方法后,使用start()方法启动线程;(如果仅仅使用对象调用run()方法,那只是等于调用了一次接口,而非线程调用)
- 继承Thread类,并重写run方法后,使用start()方法启动线程;(如果仅仅使用对象调用run()方法,那只是等于调用了一次接口,而非线程调用)
-
- 实现Runnable接口,重写run方法,启动时需要依赖Thread类
- 实现Runnable接口,重写run方法,启动时需要依赖Thread类
-
- 实现Callable<返回值>接口,重写call方法,可以设置线程的返回值,启动时通过FutureTask、Thread类进行启动,可以通过 futureTask.get() 方法获取返回值
7.3 线程池
- 可以通过线程池去批量维护一批线程,在系统需要使用时进行获取,无需等待,可重复使用,减少资源占用
- java本身可以通过Executors类快速创建指定类型的线程池,分别是如下
- 如上的方法不过是内置的一些线程池场景,他们的核心都是new了一个ThreadPoolExecutor对象,传了对应的值,可以点进上述四个方法的源码内进行查看:
- 所以我们也可以直接通过new ThreadPoolExecutor对象来创建线程池,就需要了解 ThreadPoolExecutor 类创建时传参的所代表的的含义
- corePoolSize:核心线程数,即新创建的线程优先创建核心线程,当超过数量时,才会创建非核心线程,核心线程创建出来后,即使当前没有请求,也不会被销毁;如果设置 allowCoreThreadTimeOut = true,那么核心线程闲置超时也会被销毁
- maximumPoolSize:最大线程数量;该线程池的核心线程+非核心线程的总数,超过这个数量,新的请求就需要等待有空置的线程才能执行
- keepAliveTime:线程空闲时间;一个非核心线程,闲置指定参数时长后,就会被销毁,默认60;如果设置 allowCoreThreadTimeOut = true;那么此配置会作用到核心线程上
- unit:时间单位;默认秒
- workQueue:队列;当核心线程已满,就会先把任务放到队列中,如果队列也已满,才会按照最大线程数去创建非核心线程,如果非核心线程也满了,就拒绝任务
- threadFactory:线程工厂;可以指定自己定义的线程工厂来创建线程,也可以使用默认的
7.2 锁
7.2.1 synchronized 关键字
- 锁变量:当N个线程同时情况此方法时,第一个获取此线程的方法执行期间,后面的其他线程请求需要等待,等当前线程释放后才能下一位执行
-锁方法:当我们使用线程批量调用此方法时,同时只会有一个线程成功调用,其他线程等待
-锁代码块:当我们去多线程调用时,会发现方法内代码块前面的代码可以正常执行,但代码块本身需要等待获取,同时也会导致代码块后面的代码也要等待
- 需要注意的是,上述这些写法,如果多线程调用时都各自创建了对象,不是用的同一个对象的话,是不生效的,因为每个对象都有自己的锁
- 如果有需要可以对静态方法加锁,这样就会作用到所有的对象中;或者直接对类加锁,也有同样的效果
7.2 Lock
- 可以手动获取锁和释放锁,相比于synchronized更加灵活,也增加了一些功能
7.3 线程的五种状态
- New :新创建的线程,还未开始执行
- Runnable:准备就绪,随时可以运行
- Blocked:等待运行,可能是在等待I/O资源,或者方法被锁定了,在等待获取锁
- Waiting:无限期等待,进入这个状态后,必须手动调用方法才能唤醒
- Timed Waiting:有限期等待,等到设置好的时间后,就会自动唤醒
7.4 其他
- Thread.sleep(1000):可以使当前请求等待指定时间,Thread内置方法
- Object.wait():在synchronized中可以用这个方法,来主动释放锁,自己进行休眠,不再竞争获取锁
- Object.notify():当线程进行等待时,可以通过这个方法进行唤醒
- Object.notifyAll():唤醒所有线程,这些线程就可以继续竞争锁
8.JVM
- JVM内存模型主要分为虚拟机栈、本地方法栈、堆内存、方法区、程序计数器
- 虚拟机栈:先入后出,线程结束就会被清理,线程私有,主要存变量名、基本数据类型信息、指向堆内存的引用地址
- 本地方法栈:管理本地方法的调用,是C语言实现的一些native方法,与我们开发的代码无关
- 堆内存:非线程私有,是最大的内存空间,存储变量信息;GC主要就是在这部分空间进行工作
- 方法区:jdk1.8以后使用元空间,非线程私有,主要存一些类信息、方法信息、常量池等
- 程序计数器:比较小的空间,记录执行的行数,是线程私有的
8.2 垃圾回收算法
- Mark-Sweep(标记-清除)算法 标记需要回收的对象,然后清除,会造成许多内存碎片。
- Copying(复制)算法 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。
- Mark-Compact(标记-整理)算法(压缩法) 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。
- Generational Collection(分代收集)算法 分为年轻代和老年代,年轻代时比较活跃的对象,使用复制算法做垃圾回收。老年代每次回收只回收少量对象,使用标记整理法。
- GC算法就是对堆内存空间进行算法处理,尽量减少内存占用,减轻性能使用,以及尽快清理垃圾数据。
9.类加载机制
- 1.加载:加载字节码
- 2.链接-验证:验证字节码文件正确性
- 3.链接-准备:为静态变量分配内存
- 4.链接-解析:将符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址)
- 5.初始化:为静态变量赋值