1. 5个常用的java-api包

a. java.lang 语言包

Java语言的核心类库包含Java语言必不可少的系统类定义,包括Object类、基本数据类型封装类、数学运算、字符串、线程、异常处理等

b. java.util 实用包

工具类库,包含日期类、集合类等

c. java.io 输入/输出流包

提供标准输入/输出及文件操作类库

d. java.net 网络包

提供与网络编程有关的类库,包括Socket通信支持、Internet访问支持等

e. java.sql

提供数据库应用功能的类库

 

2. API接口开发的注意事项

a. 良好的接口说明文档和测试程序

b. 接口和客户端的数据交换格式,对于数据的输出最好用json(xml和json和webservice)

c. 接口安全,一定要增加接口验证

d. 接口路径加上版本,易于区分和维护

 

6. URI和URL

URI 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。

URL在于Locater,一般来说(URL)统一资源定位符,可以提供找到该资源的路径

 

7. 二叉树,前序,中序,后序

前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )

中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)

后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)

 

8. 红黑树

二叉查找树:

若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

若右子树不空,则右子树上所有结点的值均大于它的根结点的值;

左、右子树也分别为二叉排序树;

没有键值相等的结点。

红黑树:

每个节点颜色不是黑色,就是红色

根节点是黑色的

如果一个节点是红色,那么它的两个子节点就是黑色的(没有连续的红节点)

对于每个节点,从该节点到其后代叶节点的简单路径上,均包含相同数目的黑色节点

调整:变色、左旋转、右旋

 

9. TreeSet

基于红黑树,保证键的有序性,迭代时按键大小的排序顺序

结点的增删改查都能在 O(lgn) 时间复杂度内完成,如果按树的中序遍历就能得到一个按 键-key 大小排序的序列

Comparator 不为空,那么就用它维持 key-键 的有序,否则使用 key-键 的自然顺序

 

10. SaaS、PaaS、IaaS

IaaS:基础设施服务,Infrastructure-as-a-service

PaaS:平台服务,Platform-as-a-service

SaaS:软件服务,Software-as-a-service

 

11. Java8新特性

Lambda 表达式

将函数式编程引入了Java,允许把函数作为一个方法的参数

如:集合类(包括List)现在都有一个forEach方法,对元素进行迭代(遍历

list.forEach(o -> {System.out.println(o);}); //forEach函数实现内部迭代

如:异步类CompletableFuture

CompletableFuture.runAsync(()->{
                xxxx;
            })  

注意:

可以用一个λ表达式为一个函数接口赋值:

Runnable r1 = () -> {System.out.println("Hello Lambda!");};

  

通常以一个集合类实例为其数据源,然后在其上定义各种操作

list.stream()

调用流的map方法把每个元素由String转成Integer,得到一个新的流。

.map(e -> new Integer(e))

调用流的filter方法,过滤那些小于10的数字,并得到一个新流。

.filter(e -> e.getNum() > 10)

调用流的distinct方法,去掉重复,并得到一个新流。这本质上是另一个filter操作。 

.distinct()

用collect方法将最终结果收集到一个List里面去

.collect(Collectors.toList());

  

方法引用

直接引用已有Java类或对象的方法

如:

Integer::parseInt //静态方法引用
System.out::print //实例方法引用
Person::new       //构造器引用

举例:

//下面两句是一样的(实例方法引用1)
persons.forEach(e -> System.out.println(e));
persons.forEach(System.out::println);
//下面两句是一样的(实例方法引用2)
persons.forEach(person -> person.eat());
persons.forEach(Person::eat);
//下面两句是一样的(构造器引用)
strList.stream().map(s -> new Integer(s));
strList.stream().map(Integer::new);

  

接口的默认方法与静态方法

在接口中定义默认方法,使用default关键字,并提供默认的实现。所有实现这个接口的类都会接受默认方法的实现,除非子类提供的自己的实现。例如:

public interface DefaultFunctionInterface {
    default String defaultFunction() {
        return "default function";
    }
}    

还可以在接口中定义静态方法,使用static关键字,也可以提供实现。例如:

public interface StaticFunctionInterface {
    static String staticFunction() {
        return "static function";
    }
}

作用:不用在每个实现类中都写重复的代码

 

12. redis

数据淘汰策略

noeviction:返回错误当内存限制达到,并且客户端尝试执行会让更多内存被使用的命令。

allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。

volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存 放。

allkeys-random: 回收随机的键使得新添加的数据有空间存放。

volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。

volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间 存放。

Redis缓存更新

失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

命中:应用程序从cache中取数据,取到后返回。

更新:先把数据存到数据库中,成功后,再让缓存失效。

注意:如果先失效缓存,再更新数据库。如果缓存不被更新或者被过期策略淘汰,那么这个数据将永远是脏数据。

https://blog.csdn.net/u011485472/article/details/109327660

 

如何解决缓存雪崩?

原因:对于“对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。”这种情况,非常好解决:

1、在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。

2、对于“Redis挂掉了,请求全部走数据库”这种情况,我们可以有以下的思路:

事发前:实现Redis的高可用(主从架构+Sentinel(哨兵) 或者Redis Cluster(集群)),尽量避免Redis挂掉这种情况发生。

事发中:万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)

事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。

 

13. 线程池参数

corePoolSize(必需):核心线程数。

maximumPoolSize(必需):线程池所能容纳的最大线程数。

keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。

unit(必需):指定keepAliveTime参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

workQueue(必需):任务队列。通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。

threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。

handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

线程池策略:

corePoolSize:核心线程数;maximunPoolSize:最大线程数

每当有新的任务到线程池时,

第一步: 先判断线程池中当前线程数量是否达到了corePoolSize,若未达到,则新建线程运行此任务,且任务结束后将该线程保留在线程池中,不做销毁处理,若当前线程数量已达到corePoolSize,则进入下一步;

第二步: 判断工作队列(workQueue)是否已满,未满则将新的任务提交到工作队列中,满了则进入下一步;

第三步: 判断线程池中的线程数量是否达到了maxumunPoolSize,如果未达到,则新建一个工作线程来执行这个任务,如果达到了则使用饱和策略来处理这个任务。

注意: 在线程池中的线程数量超过corePoolSize时,每当有线程的空闲时间超过了keepAliveTime,这个线程就会被终止。直到线程池中线程的数量不大于corePoolSize为止。
 

14. maven生命周期

clean 删除target文件夹及以下所有内容

validate -确认项目正确并且所有必要的信息均可用

compile -编译项目的源代码(taget下生成classes文件)

test-使用合适的单元测试框架测试编译后的源代码。这些测试不应要求将代码打包或部署

package -获取编译后的代码,并将其打包为可分发的格式,例如JAR。(target下打包)

verify -对集成测试的结果进行任何检查,以确保符合质量标准

install -将软件包安装到本地存储库中,以作为本地其他项目中的依赖项

deploy -在构建环境中完成后,将最终软件包复制到远程存储库中,以便与其他开发人员和项目共享。

注意:

maven通过install将本地工程打包成jar包,放入到本地仓库中,再通过pom.xml配置依赖引入到当前工程。

 

15. IOC创建对象比new的好处

a. new一个对象必选面临频繁创建和销毁内存实例对象的问题。而ioc管控下实例对象都是单例模式的,

就是在程序运行时始终只有一个对象实例生成不需要频繁创建和销毁,也因为在内存中只有一个实例对象,减少内存开销。

b. spring这个beanFactory就可以在实例化bean的过程中,beanFactory会在bean的生命周期的各个阶段中对bean进行各种管理,并且spring将这些阶段通过各种接口暴露给我们,

让我们可以对bean进行各种处理,我们只要让bean实现对应的接口,那么spring就会在bean的生命周期调用我们实现的接口来处理该bean.

 

16. 类的实例化顺序

比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当new的时候,他们的执行顺序。

父类静态变量、

父类静态代码块、

子类静态变量、

子类静态代码块、

父类非静态变量(父类实例成员变量)、

父类构造函数、

子类非静态变量(子类实例成员变量)、

子类构造函数。

 

17. 反射的原理,反射创建类实例的三种方式是什么。

在运行状态中,对于任意一个类,可以动态的创建类的对象,对于任意一个对象都能调用它的任意一个属性和方法。

步骤:

获得Class对象,就是获取到指定的名称的字节码文件对象。

实例化对象,获得类的属性、方法或构造函数。

访问属性、调用方法、调用构造函数创建对象。

运行时可以拿到new的对象的内容,并对此对象进行改造

三种方式:

// 1
Class<TestClass> class = Class.forName("com.zkw.TestClass");
// 2
Clas<TestClass> class = TestClass.class;
// 3
TestClass tc = new TestClass();
Class<TestClass> class = tc.getClass();

注:

jdk动态代理是由java内部的反射机制来实现的

 

18. error和exception的区别,CheckedException,RuntimeException的区别。

Error类对象由 Java 虚拟机生成并抛出,程序无法处理的错误,比如内存溢出,

因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况

Exception:所有异常的根类为java.lang.Throwable

  运行时异常:RuntimeException,运行时随时可能发生,在编译时被忽略

  编译时异常:Checked Exception,要捕获抛出异常,不捕捉这个异常,则产生编译错误

 

19. 5个运行时异常。5个编译异常

 编译异常:

  SQLException :提供有关数据库访问错误或其他错误的信息的异常。

  IOexception :表示发生了某种I / O异常的信号

  FileNotFoundException :当试图打开指定路径名表示的文件失败时,抛出此异常。(IOexception的子类)

  ClassNotFoundException :找不到具有指定名称的类的定义。

  EOFException :当输入过程中意外到达文件或流的末尾时,抛出此异常。(IOexception的子类)

运行异常:

  NullPointerException:当应用程序试图在需要对象的地方使用 null 时,抛出该异常

  IllegaArguementException :抛出的异常表明向方法传递了一个不合法或不正确的参数

  ArrayIndexOutOfBoundsException :用非法索引访问数组时抛出的异常

  StringIndexOutOfBoundsException :指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出

  ArithmeticException :当出现异常的运算条件时,抛出此异常。

 

20. Stop The World

不管选择哪种GC算法,stop-the-world都是不可避免的。Stop-the-world意味着从应用中停下来并进入到GC执行过程中去。

一旦Stop-the-world发生,除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务。

GC调优通常就是为了改善stop-the-world的时间

 

21. G1对比CMS

CMS:标记--清除 算法

整个过程分为四个步骤:

a. 初始标记 (Stop the World事件 CPU停顿, 很短) ,仅标记一下GC Roots能直接关联到的对象,速度很快;(理解为对象指向的标记)

b. 并发标记 (收集垃圾跟用户线程一起执行) ,仍然需要“stop the world”,进行GC Roots Tracing的过程;(理解为通过初始标记找到了要删除的对象)

c. 重新标记 修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录;(理解为重新执行了一遍 初始标记 和 并行标记  产生标记记录)

d. 并发清理 -清除算法;

缺点:

占用了一部分线程使应用程序变慢

无法处理浮动垃圾(在并发清理阶段线程还在运行, 伴随着程序的运行自然也会产生新的垃圾)

大量的空间碎片产生

G1:标记-整理 算法

a. 初始标记(stop the world事件 CPU停顿只处理垃圾);

b. 并发标记(与用户线程并发执行);(不会触发stop the world事件)

c. 最终标记(stop the world事件 ,CPU停顿处理垃圾);

d. 筛选回收(stop the world事件 根据用户期望的GC停顿时间回收); (注意:CMS 在这一步不需要stop the world)

堆划分为一个个区域。这么做的目的是在进行收集时不必在全堆范围内进行,优先处理那些垃圾多的内存块

优点:

并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间

可预测的停顿:能让使用者明确指定在一个长度为M毫秒的时间片段内

空间整合,减少空间碎片

 

22. CAS机制是什么,如何解决ABA问题

CAS:Compare and Swap

乐观锁用到的机制就是CAS,有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

ABA:

CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。

但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题。

解决ABA问题

使用版本号,通过比较值和版本号才判断是否可以替换。

CAS缺点:

a. CPU开销过大,在并发量比较高的时候,如果许多线程都尝试去更新一个变量的值,

却又一直比较失败,导致提交失败,产生自旋,循环往复,会对CPU造成很大的压力和开销。

b. 不能确保代码块的原子性(注意是代码块),确保的是一个变量的原子性操作,而不能保证整个代码块的原子性,

比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized或者lock了。

23. 数据库索引

INDEX `index_app_id` (`app_id`) USING BTREE) 

索引的值一般使用where频繁的字段

作用:索引用于快速找出在某个列中有一特定值的行,不使用索引,MySQL必须从第一条记录开始读完整个表,直到找出相关的行

类型:MySQL中索引的存储类型有两种:BTREE和HASH

MYISAM和InnoDB存储引擎只支持BTREE索引;MEMORY和HEAP存储引擎可以支持HASH和BTREE索引

索引建立后,查询的时候用到那个字段后自动会引用建立好的索引,索引只需要建立就行了,会自动引用

数据结构:数据库索引采用B+树,B+树只需要去遍历叶子节点就可以实现整棵树的遍历。

 

24. ArrayList容量以及扩容

默认容量:10

扩容:数组长度 = 当前数组长度 + (当前数组长度 * 0.5) ;也就是扩容到当前的 1.5 倍

 

25. 线程安全的集合

Vector:就比Arraylist多了个同步化机制(线程安全)。

Hashtable:就比Hashmap多了个线程安全。

ConcurrentHashMap:是一种高效但是线程安全的集合。

Stack:栈,也是线程安全的,继承于Vector。

 

26. CompletableFuture 使用

异步:

异步调用其实就是实现一个可无需等待被调用函数的返回值而让操作继续运行的方法。

在 Java 语言中,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结果。但调用者仍需要取线程的计算结果。

Future:

JDK5新增了Future接口,用于描述一个异步计算的结果。

但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果

CompletableFuture:

提供了四个静态方法来创建一个异步操作

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。

注意:supplyAsync表示创建带返回值的异步任务的,相当于ExecutorService submit(Callable<T> task) 方法,runAsync表示创建无返回值的异步任务,相当于ExecutorService submit(Runnable task)方法

submit和execute区别:

a. 接收的参数不一样;

  execute()方法的入参为一个Runnable,返回值为void

  sumbit()方法,入参可以为Callable<T>,也可以为Runnable,而且方法有返回值Future<T>;

b. submit()有返回值,而execute()没有;

例如,有个validation的task,希望该task执行完后告诉我它的执行结果,是成功还是失败,然后继续下面的操作。

c. submit()可以进行Exception处理;

例如,如果task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过对Future.get()进行抛出异常的捕获,然后对其进行处理。

 

27. ThreadLocal

Java中提供的线程本地存储机制,将数据缓存在某个线程内部。当线程访问某个变量,每个线程都会有这个变量的一个副本。

设置线程中变量的值:set,获得线程中变量的值:get

ThreadLocal底层是ThreadLocalMap(Thread里的)来实现,key为ThreadLocal对象,value为要缓存的值

如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。

总结:Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离

 

28. 如何查看线程死锁

先用jps查看进程pid信息

再使用jstack -l [pid]查看其中的线程信息

 

29. 如何避免死锁

设置超时时间,超时可以退出防止死锁。tryLock(long timeout, TimeUnit unit)

尽量使用线程安全的类以及原子类

加上synchronized,实现同步。

总结:永远不要在拥有一个资源的情况下请求另一个资源,只要不拥有两个锁就不可能死锁

 

30. 深拷贝和浅拷贝

浅拷贝

①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

②对于数据类型是引用数据类型的成员变量,浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。

实际上这两个对象都指向同一个实例,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

方式:

a. 通过拷贝构造方法实现浅拷贝

b. 通过重写clone()方法进行浅拷贝

  使用clone方法的类必须实现Cloneable接口

深拷贝

不仅要复制对象的所有基本数据类型的成员变量值,还要为所有引用数据类型的成员变量申请存储空间,也就是说,对象进行深拷贝要对整个对象进行拷贝!

 

posted on 2021-06-15 11:16  smile学子  阅读(67)  评论(0编辑  收藏  举报