JAVA知识

基础

基础数据类型

Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean
基本数据类型:数据直接存储在栈上
引用数据类型区别:数据存储在堆上,栈上只存储引用地址

反射

反射是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法
这种动态获取信息以及动态调用对象方法的功能称为反射机制

深拷贝和浅拷贝

浅拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变
深拷贝:对于基础数据类型:直接复制数据值;对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象

初始化

1)静态变量只会初始化(执行)一次
2)当有父类时,完整的初始化顺序为:父类静态变量(静态代码块)->子类静态变量(静态代码块)->父类非静态变量(非静态代码块)->父类构造器 ->子类非静态变量(非静态代码块)->子类构造器

final 关键字

修饰类:该类不能再派生出新的子类,不能作为父类被继承。因此,一个类不能同时被声明为abstract 和 final。
修饰方法:该方法不能被子类重写。
修饰变量:该变量必须在声明时给定初值,而在以后只能读取,不可修改。 如果变量是对象,则指的是引用不可修改,但是对象的属性还是可以修改的。

浮点数运算精度丢失

示例:
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false
为什么会出现这样的问题?
这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。
如何解决?
BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。

多线程实现方式

1)继承 Thread 类;2)实现 Runnable 接口;3)实现 Callable 接口。

synchronized 各种加锁场景的作用范围

1.作用于非静态方法,锁住的是对象实例(this),每一个对象实例有一个锁。
2.作用于静态方法,锁住的是类的Class对象
3.作用于 Lock.class,锁住的是 Lock 的Class对象,也是全局只有一个。
4.作用于 this,锁住的是对象实例,每一个对象实例有一个锁。
5.作用于静态成员变量,锁住的是该静态成员变量对象,由于是静态变量,因此全局只有一个。

线程池

降低资源消耗。通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
增加线程的可管理型。线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。

CAS

CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,主要是解决原子操作的问题
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题:
  1. 循环时间长开销很大。
  2. 只能保证一个共享变量的原子操作。
  3. ABA问题。

泛型

extends与super的本质

  1. List<? extends Animal>并不是用来约束里面元素类型的,而是用来约束 List 本身的
  2. 类的继承关系都是表示 is-a 的关系,List和 List之间是不具有 is-a 关系
  3. List和 List之间不存在 is-a 关系,这就叫做不变(invariant),假设规定 List is-a List,这种关系就叫做协变(covariant),反之,如果规定 List is-a List,这种关系就叫做逆变(contravariant)。Java 就是用的 extends 和 super
  4. PECS 原则:上界生产,下界消费

乐观锁 VS 悲观锁

悲观锁:在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
Java中,synchronized关键字和Lock的实现类都是悲观锁。
乐观锁:自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

集合

HashMap

底层结构:数组+链表+红黑树
特点:
  1. 数组:查找容易;插入与删除困难
  2. 链表:查找困难;插入与删除容易
  3. 红黑树:解决链表过长时,查找慢的问题

链表的查找性能是 O(n),而使用红黑树是 O(logn)

Hashtable 是怎么加锁的

Hashtable 通过 synchronized 修饰方法来加锁,从而实现线程安全。

LinkedHashMap 和 TreeMap 排序的区别

LinkedHashMap 和 TreeMap 都是提供了排序支持的 Map,区别在于支持的排序方式不同
LinkedHashMap:保存了数据的插入顺序,也可以通过参数设置,保存数据的访问顺序。
TreeMap:底层是红黑树实现。可以指定比较器(Comparator 比较器),通过重写 compare 方法来自定义排序;如果没有指定比较器,TreeMap 默认是按 Key 的升序排序(如果 key 没有实现 Comparable接口,则会抛异常)。

ArrayList 和 LinkedList 的区别

ArrayList 底层基于动态数组实现,LinkedList 底层基于双向链表实现。

CopyOnWriteArrayList

在读操作时不加锁,跟ArrayList类似;
在写操作时,复制出一个新的数组,在新数组上进行操作,操作完了,将底层数组指针指向新数组。适合使用在读多写少的场景。

Redis

单线程

redis 的核心操作是单线程的。
因为 redis 是完全基于内存操作的,通常情况下CPU不会是redis的瓶颈,redis 的瓶颈最有可能是机器内存的大小或者网络带宽。

快的原因

主要有以下几点:
  1. 基于内存的操作
  2. 使用了 I/O 多路复用模型,select、epoll 等,基于 reactor 模式开发了自己的网络事件处理器
  3. 单线程可以避免不必要的上下文切换和竞争条件,减少了这方面的性能消耗。
  4. 以上这三点是 redis 性能高的主要原因,其他的还有一些小优化,例如:对数据结构进行了优化,简单动态字符串、压缩列表等。

数据结构

基础的5种:
  • String:字符串,最基础的数据类型。
  • List:列表。
  • Hash:哈希对象。
  • Set:集合。
  • Sorted Set:有序集合,Set 的基础上加了个分值。

持久化

Redis 的持久化机制有:RDB、AOF、混合持久化
RDB:类似于快照。在某个时间点,将 Redis 在内存中的数据库状态(数据库的键值对等信息)保存到磁盘里面。RDB 持久化功能生成的 RDB 文件是经过压缩的二进制文件。
AOF:保存 Redis 服务器所执行的所有写操作命令来记录数据库状态,并在服务器启动时,通过重新执行这些命令来还原数据集。
混合持久化:混合持久化只发生于AOF重写过程。使用了混合持久化,重写后的新AOF文件前半段是RDB格式的全量数据,后半段是AOF格式的增量数据。

高可用