本文脑图来源
https://www.bilibili.com/read/cv6238451?spm_id_from=333.788.b_636f6d6d656e74.6#reply2963501648
Java 八股文
访问权限比较–public,protected,默认,private
权限 | 同一个类 | 同一个包子类 | 不同包的子类 | 不同包的非子类 |
---|---|---|---|---|
public | 是 | 是 | 是 | 是 |
protected | 是 | 是 | 是 | 否 |
默认 | 是 | 是 | 否 | 否 |
private | 是 | 否 | 否 | 否 |
Java中final关键字详解及实例
class Value {
int v;
public Value(int v) {
this.v = v;
}
}
public class FinalTest {
final int f1 = 1;
final int f2;
public FinalTest() {
f2 = 2;
}
public static void main(String[] args) {
final int value1 = 1;
// value1 = 4;
final double value2;
value2 = 2.0;
final Value value3 = new Value(1);
value3.v = 4;
}
}
- main方法中被final修饰的数据,在给value1赋初始值之后,我们无法再对value1 的值进行修改,final关键字起到了常量的作用。
- 从value2我们可以看到,final修饰的变量可以不在声明时赋值,即可以先声明,后赋值。
- value3是一个引用变量,这里我们可以看到final修饰引用变量时,只是限定了引用变量的引用不可改变,即不能将value3再次引用另一个Value对象,但是引用的对象的值是可以改变的。
静态代码块、构造代码块、构造函数、普通代码块
Java(静态变量、实例变量、局部变量)
- 静态变量----线程不安全,类的所有实例共享同一个static变量,对象间共享值时使用
- 实例变量----实例变量属于类对象的,也就是说,属于对象实例私有,在虚拟机的堆中分配。
- 局部变量----线程安全,定义在方法内部的变量
继承中 子、父类的构造方法
1 不写构造方法,类中的第一行代码事实上有一个默认的无参构造(系统会隐式为你写好)
2 只写带参构造方法,相当于只有该带参构造方法(隐式的无参构造会被屏蔽无视掉,视为无效)
3 若想同时拥有无参和带参构造,必须显式地写出无参和带参构造方法
4 在子类的构造方法(无论是无参和有参)中,方法中的第一行代码事实上都隐式地包含了父类的无参构造方法即: super();此时若写一个有参构造,super(xx)会把隐式的super() 屏蔽掉
java内部类有什么作用?
【重要】使用匿名内部类我们必须要继承一个父类或者实现一个接口
public abstract class Bird {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract int fly();
}
public class Test {
public void test(Bird bird){
System.out.println(bird.getName() + "能够飞 " + bird.fly() + "米");
}
public static void main(String[] args) {
Test test = new Test();
test.test(new Bird() {
public int fly() {
return 10000;
}
public String getName() {
return "大雁";
}
});
}
}
------------------
// Output:
// 大雁能够飞 10000米
// 相当于
public class WildGoose extends Bird{
public int fly() {
return 10000;
}
public String getName() {
return "大雁";
}
}
WildGoose wildGoose = new WildGoose();
test.test(wildGoose);
枚举Enum
Java集合框架图
https://www.runoob.com/java/java-collections.html
数组和集合区别
数组 | 集合 | |
---|---|---|
存储元素 | 可以存储基本数据类型,引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址值 | 只能存储引用数据类型(对象)集合中也可以存储基本数据类型,但是在存储的时候会自动装箱变成对象 |
长度 | 长度是固定 声明数组必须指明长度 | 长度的是可变 |
map
Java中Map的 entrySet() 详解以及用法(四种遍历map的方式)
HashMap
1 数组 + 链表 + 红黑树 组合构成的数据结构
put插入的时候会根据key的hash去计算一个index值
数组长度是有限的,在有限的长度里面我们使用哈希,哈希本身就存在概率性,就是”帅丙“和”丙帅“我们都去hash有一定的概率会一样,就像上面的情况我再次哈希”丙帅“极端情况也会hash到一个值上,那就形成了链表
2 java8之后,尾部插入
3 HashMap的扩容机制: 初始化长度16 为了实现均匀分布
数组容量是有限的,数据多次插入的,到达一定的数量就会进行扩容,也就是resize。什么时候resize呢?有两个因素:
Capacity:HashMap当前长度。
LoadFactor:负载因子,默认值0.75f怎么扩容的呢?分为两步
扩容:创建一个新的Entry空数组,长度是原数组的2倍。
ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。
为什么要重新Hash呢,直接复制过去不香么
因为长度扩大以后,Hash的规则也随之改变。
Hash的公式—> index = HashCode(Key) & (Length - 1)
原来长度(Length)是8你位运算出来的值是2 ,新的长度是16你位运算出来的值明显不一样了。
线程不安全
Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
我们重写equals方法的时候需要重写hashCode方法
https://www.cnblogs.com/ouym/p/8963219.html
HashMap常见面试题:
HashMap的底层数据结构?
HashMap的存取原理?
Java7和Java8的区别?
为啥会线程不安全?
有什么线程安全的类代替么?
默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
HashMap的扩容方式?负载因子是多少?为什是这么多?
HashMap的主要参数都有哪些?
HashMap是怎么处理hash碰撞的?
hash的计算规则?
接口
接口和类
Java 接口(interface)的用途和好处
https://blog.csdn.net/qq_19782019/article/details/80259836
https://blog.csdn.net/nvd11/article/details/41129935
接口, 就是抽象方法和常量值的集合 也有普通方法(有返回值的方法)
java接口可以多继承。interface C extends A, B {}是可以的。
interface 的定义跟java有点类似. 大概如下:
[public] interace interface_name [extends <Superinterface_list>]{
//public static final members
//public abstract methods
}
一个类可以实现多个接口:class D implements A,B,C{}。但是一个类只能继承一个类,不能继承多个类:class B extends A{}。在继承类的同时,也可以继承接口:class E extends D implements A,B,C{}。
接口的使用原则如下:
1、接口必须要有子类,但此时一个子类可以使用implements关键字实现多个接口。
2、接口的子类(如果不是抽象类),那么必须要覆写接口中的全部抽象方法。
3、接口的对象可以利用子类对象的向上转型进行实例化。
抽象类和接口
众所周知,在实现类中实现接口时,必须全部重写接口的抽象方法,如
public interface MyInter{
function abstract void A();
function abstract voidB();
}
实现类中,必须全部重写A和B方法
public class MyClass implements MyInter{
public void A(){}
public void B(){}
}
如果不重写接口的方法可以吗?可以,但实现类一定要是一个抽象类
public abstract class MyClass implements MyInter{
public void A(){}
}
所以总结如下:
由普通的类来实现接口,必须将接口所有抽象方法重写
由抽象类来实现接口,则不必重写接口的方法。可以全部不重写或只重写一部分方法。
用的时候找到对应的实现了接口方法的抽象类的方法
Java 泛型
Java 反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
https://blog.csdn.net/qq_42537795/article/details/106017092
先读这个图,然后读廖雪峰的https://www.liaoxuefeng.com/wiki/1252599548343744/1264799402020448
学习java应该如何理解反射? - CaryTseng的回答 - 知乎
类class(包括interface)的本质是数据类型(Type)
Class 对象 实例 class是Class的一个属性
知识点一
### 如何获取一个class的Class实例?有三个方法:
// 方法1:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
// 最常用
Class clazz = Class.forName("java.lang.String");
//创建此Class 对象所表示的类的一个新实例
Object o = clazz.newInstance(); //调用了String的无参数构造方法.
// 方法2:直接通过一个class的静态变量class获取:
Class clazz = String.class;
// 方法3:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
String s = "Hello";
Class clazz = s.getClass();
##### 注意:在运行期间,一个类,只有一个Class对象产生
知识点二
//Class类提供了以下几个方法来获取**字段**:
Field getField(name):根据字段名获取某个public的field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
Field[] getFields():获取所有public的field(包括父类)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
//Class类提供了以下几个方法来获取Method:
Method getMethod(name, Class...):获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
//通过Class实例获取Constructor的方法如下:
getConstructor(Class...):获取某个public的Constructor;
getDeclaredConstructor(Class...):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。
https://blog.csdn.net/sinat_38259539/article/details/71799078
Java中的getGenericSuperclass的基本用法
// 通过getGenericSuperclass方法可以获取当前对象的直接超类的Type,
// 使用该方法可以获取到泛型T的具体类型
getSuperclass:class java.util.HashMap
getGenericSuperclass:java.util.HashMap<java.lang.String, java.lang.Integer>
class java.lang.String
class java.lang.Integer
getSuperclass:class java.util.AbstractMap
getGenericSuperclass:java.util.AbstractMap<K, V>
K
V
// https://www.cnblogs.com/chenmingjun/p/9734239.html
// 父类
package com.itheima.mytest;
public class Person<T1, T2> {
}
// 子类
package com.itheima.mytest;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class Student extends Person<Integer, String> {
@SuppressWarnings("rawtypes")
public static void main(String[] args) {
Student student = new Student();
// getClass() 获得该类的类类型(即类型变量)
Class clazz = student.getClass();
System.out.println(clazz);
// getSuperclass() 获得该类的父类
System.out.println(clazz.getSuperclass());
// getGenericSuperclass() 获得该类带有泛型的父类
Type type = clazz.getGenericSuperclass();
System.out.println(type);
// Type是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、
// 参数化类型、数组类型、类型变量和基本类型。
// ParameterizedType 参数化类型,即泛型
// 将Type转化为参数化类型(即泛型)
ParameterizedType p = (ParameterizedType) type;
// getActualTypeArguments() 获取参数化类型的数组,泛型可能有多个
Type[] actualTypeArguments = p.getActualTypeArguments();
// 将Type转化为类型变量(即Class)
Class c1 = (Class) actualTypeArguments[0];
Class c2 = (Class) actualTypeArguments[1];
System.out.println(c1);
System.out.println(c2);
}
}
class TestReflect.Student
class TestReflect.Person
TestReflect.Person<java.lang.Integer, java.lang.String>
class java.lang.Integer
class java.lang.String
IO流
System.in是个字节流
InputStreamReader是个字符流和字节流之间的转换中介
BufferedReader是个字符流
整体意思就是用InputStreamReader这个中介把System.in这个字节流转换成字符流BufferedReader
这样输入的时候就可以不是一个一个字节读,而是一个一个字符读,再加上是个Buffer,效率会高很多。
BufferedReader reader= new BufferedReader(new InputStreamReader(System.in))解读
public Properties reloadProperties() {
Properties props = new Properties();
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try {
inputStreamReader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
bufferedReader = new BufferedReader(inputStreamReader);
props.load(bufferedReader);
if (props.isEmpty()) {
log.error("The {} file cannot be load", file.getPath());
}
} catch (FileNotFoundException e) {
log.error("construct file input stream caused exception, {}", e.getMessage());
} catch (IOException e) {
log.error("load properties caused exception, {}", e.getMessage());
} finally {
try {
if (null != bufferedReader) {
bufferedReader.close();
}
} catch (IOException e) {
log.error("close buffer reader failed, {}", e.toString());
}
try {
if (null != inputStreamReader) {
inputStreamReader.close();
}
} catch (IOException e) {
log.error("close input stream reader failed, {}", e.toString());
}
}
log.info("reload properties =" + props);
return prop = props;
}
Java多线程
多线程
创建一个新线程非常容易,我们需要实例化一个Thread实例,然后调用它的start()方法
方法一:从Thread派生一个自定义类,然后覆写run()方法
方法二:创建Thread实例时,传入一个Runnable实例
线程池
https://www.cnblogs.com/hsug/p/13303018.html @EnableAsync @Async
https://www.cnblogs.com/uniqueDong/p/10944632.html
线程池的几个灵魂拷问?
api文档
https://www.matools.com/api/java8
https://movie.blog.csdn.net/article/details/82904688
https://www.liaoxuefeng.com/wiki/1252599548343744/1306581130018849
https://www.cnblogs.com/Cubemen/p/10818895.html
Executors 线程池执行器类 :
1. newCachedThreadPool(); 缓存数量线程池.
2. newFixedThreadPool(); 固定数量线程池.
3. newSingleThreadExecutor(); 单个可复用线程池对象.
ExecutorService 执行器服务类 :
1. submit(Runnable task); 向线程池对象提交任务.
2. shutdown(); 关闭线程池对象.
int corePoolSize = 10; //核心线程数
int maximumPoolSize = 15; //最大线程数
long keepAliveTime = 10; //线程空闲时间
TimeUnit unit = TimeUnit.SECONDS;
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
ExecutorService executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit,
workQueue);
executor.execute(funcxxx);
executor.shutdown();
public class ThreadPool {
public final static String MY_QUEUE_THREAD_POOL_NAME = "myTaskExecutor";
@Bean(MY_QUEUE_THREAD_POOL_NAME)
public Executor getPacketInAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(notifyThreadPoolProperties.getCoreSize());
executor.setMaxPoolSize(notifyThreadPoolProperties.getMaxSize());
executor.setQueueCapacity(notifyThreadPoolProperties.getQueueSize());
executor.setKeepAliveSeconds(notifyThreadPoolProperties.getTimeout());
executor.setThreadNamePrefix("My-task-");
// 拒绝策略
// 如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
多线程执行同一任务,所有线程返回值后执行下一步操作测试(可用于多线程查询)
https://juejin.cn/post/6989201934699741191 CountDownLatch的使用场景
https://blog.csdn.net/mhqyr422/article/details/115306773
package com.lx.learn.testConcurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @Description:多线程执行同一任务,所有线程返回值后执行下一步操作测试(可用于多线程查询)
* @Auther:
* @Date:
*/
public class MultiThread {
private static ExecutorService threadPool = null;
static {
threadPool = Executors.newFixedThreadPool(20);
}
public static void main(String[] args) {
try {
// getNumArg(1);
// getNumArg(2);
getForNum();
System.out.println("方法执行完毕");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description: 简单多线程执行(参数利用)
* @Param: []
* @return: void
* @Author: MaoHQ
* @Date: 2021/3/25
*/
public static void getNumArg(int num) throws InterruptedException, ExecutionException {
List<String> tmplist = new ArrayList<String>();
CountDownLatch latch = new CountDownLatch(2);
Future<Integer> lnt = threadPool.submit(new TestThread1(1, tmplist, latch));
Future<Integer> ykt = threadPool.submit(new TestThread2(2, tmplist, latch));
//等待线程均完成
latch.await();
//获取线程返回值
Integer i1 = lnt.get();
Integer i2 = ykt.get();
//线程均执行完后,继续执行
System.out.println(i1 + i2 + "数据:" + num);
for (String string : tmplist) {
System.out.println(string + "数据:" + num);
}
}
/**
* @Description: 多线程执行
* @Param: []
* @return: void
* @Author: MaoHQ
* @Date: 2021/3/25
*/
public static void getNum() throws InterruptedException, ExecutionException {
Future<Integer> t1 = threadPool.submit(new TestThread1(1));
Future<Integer> t2 = threadPool.submit(new TestThread2(2));
//获取线程返回值
Integer i1 = t1.get();
Integer i2 = t2.get();
//线程均执行完后,继续执行
System.out.println(i1 + i2);
}
/**
* @Description: 多线程执行(循环)
* @Param: []
* @return: void
* @Author: MaoHQ
* @Date: 2021/3/29
*/
public static void getForNum() throws InterruptedException, ExecutionException {
int total = 5;
List<Future<Integer>> allFuture = new ArrayList();
CountDownLatch latch = new CountDownLatch(total);
for (int i = 0; i < total; i++) {
Future<Integer> tmpData = threadPool.submit(new TestThread1(1, latch));
allFuture.add(tmpData);
}
latch.await();
for (Future future : allFuture) {
Integer num = (Integer) future.get();
System.out.println(num);
}
}
/***
* @Description: 线程2(执行中有睡眠)
* @Param:
* @return:
* @Author: MaoHQ
* @Date: 2021/3/25
*/
private static class TestThread2 implements Callable<Integer> {
private int num;
private List<String> tmplist;
private CountDownLatch latch;
public TestThread2(int num) {
this.num = num;
}
public TestThread2(int num, List<String> tmplist) {
this.num = num;
this.tmplist = tmplist;
}
public TestThread2(int num, List<String> tmplist, CountDownLatch latch) {
this.num = num;
this.tmplist = tmplist;
this.latch = latch;
}
@Override
public Integer call() throws Exception {
try {
System.out.println("正在运行2");
Thread.sleep(5000);
if (tmplist != null)
tmplist.add("a");
return num;
} finally {
latch.countDown();
}
}
}
/***
* @Description: 线程1(执行中无睡眠)
* @Param:
* @return:
* @Author: MaoHQ
* @Date: 2021/3/25
*/
private static class TestThread1 implements Callable<Integer> {
private int num;
private List<String> tmplist;
private CountDownLatch latch;
public TestThread1(int num, List<String> tmplist) {
this.num = num;
this.tmplist = tmplist;
}
public TestThread1(int num) {
this.num = num;
}
public TestThread1(int num, List<String> tmplist, CountDownLatch latch) {
this.num = num;
this.tmplist = tmplist;
this.latch = latch;
}
public TestThread1(int num, CountDownLatch latch) {
this.num = num;
this.latch = latch;
}
@Override
public Integer call() throws Exception {
try {
if (tmplist != null)
tmplist.add("b");
System.out.println("正在运行");
return num;
} finally {
//线程执行数-1
latch.countDown();
}
}
}
}
Thread.start() ,它是怎么让线程启动的呢?
synchronized 与 lock
Java锁
更多锁 阅读自己的java 锁以及分布式锁文章
可重入锁
演示
1.同一个锁 未释放,其它人拿不到
2.可重入锁:step1:create
step2: acquire 几次就要release几次,否则别人拿不到
public void getReentrantLock() {
System.out.println(CommonUtil.getCurrentThreadTrace());
InterProcessMutex globalLock = ZkFunProvider.createReentrantLock(LockType.VPP_MANAGE_IP_LOCK, "1234");
try {
globalLock.acquire(500, TimeUnit.MILLISECONDS);
} catch (Exception e) {
System.out.println(CommonUtil.getCurrentThreadTrace() + "first acquire caused exception.");
}
InterProcessMutex lock1 = ZkFunProvider.getReentrantLock(LockType.VPP_MANAGE_IP_LOCK, "1234", 500);
if (null == lock1) {
System.out.println("get lock1 failed.");
} else {
System.out.println("get lock1 succeed.");
}
try {
globalLock.acquire(500, TimeUnit.MILLISECONDS);
} catch (Exception e) {
System.out.println(CommonUtil.getCurrentThreadTrace() + "second acquire caused exception.");
}
ZkFunProvider.releaseLock(globalLock);
System.out.println("release first time.");
lock1 = ZkFunProvider.getReentrantLock(LockType.VPP_MANAGE_IP_LOCK, "1234", 500);
if (null == lock1) {
System.out.println("get lock1 failed.");
} else {
System.out.println("get lock1 succeed.");
}
ZkFunProvider.releaseLock(globalLock);
System.out.println("release second time.");
lock1 = ZkFunProvider.getReentrantLock(LockType.VPP_MANAGE_IP_LOCK, "1234", 500);
if (null == lock1) {
System.out.println("get lock1 failed.");
} else {
System.out.println("get lock1 succeed.");
}
}
get lock1 failed.
release first time.
get lock1 failed.
release second time.
get lock1 succeed.
不可重入锁
ZkFunProvider.getReentrantLock 这个用法其实是一个不可重入锁的用法 别被名字误导
JVM 垃圾回收
JVM内存区域
本地方法栈:
jvm的native方法:一个Native Method就是一个java调用非java代码的接口
本地内存不懂 ??
https://www.bilibili.com/video/BV1HQ4y1P7hE
38:58 JVM 面试
— JVM内存区域
— 如何识别垃圾
--------- 引用计数 循环引用问题而不用
--------- 可达性算法 GC Root到底是什么东西呢,哪些对象可以作为 GC Root 呢,有以下几类
1 虚拟机栈(栈帧中的本地变量表)中引用的对象
2 方法区中类静态属性引用的对象
3 方法区中常量引用的对象
4 本地方法栈中 JNI(即一般说的Native 方法)引用的对象
—垃圾回收算法 4种
---------标记清除算法
---------标记复制算法
---------标记整理法
---------分代收集算法:新生代和老生代(Java8以前还有个永久代),默认比例为 1 : 2,新生代又分为 Eden 区, from Survivor 区(简称S0),to Survivor 区(简称 S1),三者的比例为 8: 1 : 1,这样就可以根据新老生代的特点选择最合适的垃圾回收算法,我们把新生代发生的 GC 称为 Young GC(也叫 Minor GC),老年代发生的 GC 称为 Old GC(也称为 Full GC)。
1、对象在新生代的分配与回收
2、对象何时晋升老年代 (阈值 大对象 )
3、Stop The World 即在 GC(minor GC 或 Full GC)期间,只有垃圾回收器线程在工作,其他工作线程则被挂起。
—垃圾收集器种类
---------CMS Concurrent Mark-Sweep 核心是支持工作线程和GC线程并发,工作在老年代 主要使用标记清除法 初始标记和重新标记两个阶段会发生 STW,造成用户线程挂起
主要有以下四个步骤
初始标记
并发标记
重新标记
并发清除
---------G1 Garbage First 会发生 STW,造成用户线程挂起 STW可控制
— OOM排查过程
— CPU飙高排查过程 top命令
CMS Concurrent Mark-Sweep
G1
FullGC问题排查
原创 | Java堆内存是线程共享的!面试官:你确定吗?
GC (Allocation Failure) 那些事