复习记录:一.Java基础部分

75 阅读10分钟

最近再换工作,感觉一片萧条,也趁这个时间多复习复习吧,只是单纯的再看一遍感觉记忆不深刻,毕竟看过和理解了还是相差很远的,所以准备看题和复习的时候记录一下复习的一些内容,加深理解

1.抽象类

  • abstract 关键字修饰
  • 可以同时存在抽象方法(没有方法体,使用abstract修饰)、普通方法,也可以只单独存在任意一个
  • 不能被实例化(内存在抽象方法,实例化无意义)
  • 被使用需要存在子类,抽象类作为父类以被继承的方式使用,子类只能继承一个抽象类(但如果是接口,可以实现多个接口)。
  • 子类必须实现抽象类父类中的抽象方法
  • 简单理解抽象类的用处:就是当父类有一部分功能确认统一,无需子类重写,另一部分需要子类重写时可以使用。
  • 抽象方法不允许使用[private]修饰(因为要被子类重写)
  • 抽象类可以有构造方法,子类对象实例化时,会先执行父类构造,再执行子类构造。
  • 抽象类不允许被[final]声明,因为需要被继承

2.重载、重写

  • 这两个词其实代表的概念很简单,只是容易混淆
  • 重写:发生在子类中,子类对父类的方法进行重写操作(父类有了,子类再写一遍-重写);特点是:权限修饰子类不能比父类小,方法体内容子类可以变动外,方法其他部分和父类一致
  • 重载:再同一个类中,方法名字相同,而传参(数量和类型)、返回参数类型、权限修饰,任意不同的就是重载。

3. == or equals

  • [==] 比较的是栈中变量对应的值;如果是基础数据类型,栈中变量对应的数据就是这个值本身,使用[==]去比较是没问题的,但是如果是复杂数据类型;例如String,在栈中变量名对应的参数是一串指向堆空间中的地址,可能会存在实际值是一致的,但是在堆中声明了两个单独的变量,他们的地址不同,会导致[==]判断时,判断为不一致
  • equals方法是Object中内置的方法,需要注意的是,如果你自己声明的对象没有重写这个方法的话,使用equals方法,本质上内部还是使用[==]进行判断堆空间地址;如下: 1678686272661.png
  • String中的equals方法是重写了object中的equals,会依次比较堆空间地址、字符串长度、转换成char数组比较每个位置的每个字符是否一致。如下图,感兴趣的话可以看一下String这部分的源码,并不复杂

1678686362506.png

4.异常

  • 关键字:try、catch、finaly、throws
  • 如下:

image.png

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锁,保证了安全,但效率较低

1678694657876.png

5.2 List

  • List集合都是有序的
  • LinkedList:双向链表,查询慢,增删快,非线程安全
  • ArraryList:数组,查询快,增删慢,非线程安全;默认长度10,每次1.5倍扩容
  • Vector:数组,线程安全,效率较低
  • Stack:继承Vector,数组栈,后进先出,不推荐使用,如有需要建议用Deque

5.3 Set

  • HashSet:底层由hashMap实现,无序(不保证插入顺序,但内部有hash值的顺序),非线程安全,可以存null 1678700927174.png
  • LinkedHashSet:继承HashSet,增加了链表,保证有序
  • TreeSet:二叉书结构,内部有序

6.String、StringBuffer、StringBuilder

  • String对象由char[]数组构成,final修饰,不可变字符串;在堆空间中有一个字符串常量池的空间,用于存放String字符串
  • StringBuffer可变字符串,线程安全
  • StringBuilder可变字符串,线程不安全,

7.线程

7.1线程的实现方式

    1. 继承Thread类,并重写run方法后,使用start()方法启动线程;(如果仅仅使用对象调用run()方法,那只是等于调用了一次接口,而非线程调用) 1678702117144.png
    1. 实现Runnable接口,重写run方法,启动时需要依赖Thread类 1678702426457.png
    1. 实现Callable<返回值>接口,重写call方法,可以设置线程的返回值,启动时通过FutureTask、Thread类进行启动,可以通过 futureTask.get() 方法获取返回值

1678702816595.jpg

7.3 线程池

  • 可以通过线程池去批量维护一批线程,在系统需要使用时进行获取,无需等待,可重复使用,减少资源占用
  • java本身可以通过Executors类快速创建指定类型的线程池,分别是如下 1678716999920.png
  • 如上的方法不过是内置的一些线程池场景,他们的核心都是new了一个ThreadPoolExecutor对象,传了对应的值,可以点进上述四个方法的源码内进行查看:

1678716844656.png

  • 所以我们也可以直接通过new ThreadPoolExecutor对象来创建线程池,就需要了解 ThreadPoolExecutor 类创建时传参的所代表的的含义

1678717126685.png

  • corePoolSize:核心线程数,即新创建的线程优先创建核心线程,当超过数量时,才会创建非核心线程,核心线程创建出来后,即使当前没有请求,也不会被销毁;如果设置 allowCoreThreadTimeOut = true,那么核心线程闲置超时也会被销毁
  • maximumPoolSize:最大线程数量;该线程池的核心线程+非核心线程的总数,超过这个数量,新的请求就需要等待有空置的线程才能执行
  • keepAliveTime:线程空闲时间;一个非核心线程,闲置指定参数时长后,就会被销毁,默认60;如果设置 allowCoreThreadTimeOut = true;那么此配置会作用到核心线程上
  • unit:时间单位;默认秒
  • workQueue:队列;当核心线程已满,就会先把任务放到队列中,如果队列也已满,才会按照最大线程数去创建非核心线程,如果非核心线程也满了,就拒绝任务
  • threadFactory:线程工厂;可以指定自己定义的线程工厂来创建线程,也可以使用默认的

7.2 锁

7.2.1 synchronized 关键字

  • 锁变量:当N个线程同时情况此方法时,第一个获取此线程的方法执行期间,后面的其他线程请求需要等待,等当前线程释放后才能下一位执行 1678761001280.png -锁方法:当我们使用线程批量调用此方法时,同时只会有一个线程成功调用,其他线程等待 1678761297780.png -锁代码块:当我们去多线程调用时,会发现方法内代码块前面的代码可以正常执行,但代码块本身需要等待获取,同时也会导致代码块后面的代码也要等待 1678761880274.png

1678761980157.png

  • 需要注意的是,上述这些写法,如果多线程调用时都各自创建了对象,不是用的同一个对象的话,是不生效的,因为每个对象都有自己的锁
  • 如果有需要可以对静态方法加锁,这样就会作用到所有的对象中;或者直接对类加锁,也有同样的效果

1678762474718.png

7.2 Lock

  • 可以手动获取锁和释放锁,相比于synchronized更加灵活,也增加了一些功能

1678763584328.png

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.初始化:为静态变量赋值