国企Java,数据库,操作系统,数据结构八股

368 阅读35分钟

Java

1. Java的特点

Java是一门面向对象的编程语言
Java具有平台独立性和移植性

  • Java有一句口号:Write once, run anywhere,一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编译的Java程序可以在任何带有JVM的平台上运行。你可以在windows平台编写代码,然后拿到linux上运行。只要你在编写完代码后,将代码编译成.class文件,再把class文件打成Java包,这个jar包就可以在不同的平台上运行了。

Java具有稳健性

  • Java是一个强类型语言,它允许扩展编译时检查潜在类型不匹配问题的功能。Java要求显式的方法声明,它不支持C风格的隐式声明。这些严格的要求保证编译程序能捕捉调用错误,这就导致更可靠的程序。
  • 异常处理是Java中使得程序更稳健的另一个特征。异常是某种类似于错误的异常条件出现的信号。使用try/catch/finally语句,程序员可以找到出错的处理代码,这就简化了出错处理和恢复的任务。

2. Java 与 C++ 的区别

  • Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 兼容 C ,不但支持面向对象也支持面向过程。
  • Java 通过虚拟机从而实现跨平台特性, C++ 依赖于特定的平台。
  • Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
  • Java 支持自动垃圾回收,而 C++ 需要手动回收。
  • Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。

3. 面向对象和面向过程的区别

面向对象和面向过程是一种软件开发思想。

  • 面向过程就是分析出解决问题所需要的步骤,然后用函数按这些步骤实现,使用的时候依次调用就可以了。
  • 面向对象是把构成问题事务分解成各个对象,分别设计这些对象,然后将他们组装成有完整功能的系统。面向过程只用函数实现,面向对象是用类实现各个功能模块。

4. JDK/JRE/JVM三者的关系

JVM

Java程序运行的地方。英文名称(Java Virtual Machine),Java 虚拟机。Java 能够跨平台运行的核心在于 JVM,所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。也就是说class文件并不直接与机器的操作系统交互,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。针对不同的系统有不同的 jvm 实现,有 Linux 版本的 jvm 实现,也有Windows 版本的 jvm 实现,但是同一段代码在编译后的字节码是一样的。这就是Java能够跨平台,实现一次编写,多处运行的原因所在。

JRE

Java 运行时环境。英文名称(Java Runtime Environment),我们编写的Java程序必须要在JRE才能运行。它主要包含三个部分,JVM,Java 核心类库和运行工具。

JDK

Java开发工具包。JDK包含JRE。

5. 面向对象有哪些特性

面向对象四大特性:封装,继承,多态,抽象

  • 封装就是将类的信息隐藏在类内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。 良好的封装能够减少耦合。
  • 继承是从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩展新的能力,大大增加程序的重用性和易维护性。在Java中是单继承的,也就是说一个子类只有一个父类。
  • 多态是多种状态:同一个行为,不同的子类表现出来不同的形态。实现多态的三要素:继承、重写、父类引用指向子类对象。
// 创建对象:多态方式
Animal al = new Dog();
调用成员变量 编译看左边,运行看左边
javac编译代码的时候,会看左边的父类中有没有这个变量,如果有编译成功,如果没有,编译失败
System.out.println(al.name); // Animal
调用成员方法 编译看左边,运行看右边
javac编译代码的时候,会看左边的父类中有没有这个方法,如果有编译成功,如果没有,编译失败
实际运行的时候,其实运行的是子类的方法
al.show(); // Dog - show方法
  • 抽象。把客观事物用代码抽象出来。

6. Java的基本数据类型有哪些(4种8个基本数据类型)

简单类型booleanbytecharshortIntlongfloatdouble
二进制位数(bit)18161632643264
包装类BooleanByteCharacterShortIntegerLongFloatDouble

为什么需要包装类型?
Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。

为了让基本类型也具有对象的特征,就出现了包装类型。相当于将基本类型包装起来,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

7. 为什么不能用浮点型表示金额

由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。

建议使用BigDecimal或者Long来表示金额。

8. 自动装箱和拆箱

装箱:将基础类型转化为包装类型。
拆箱:将包装类型转化为基础类型。
当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:

  • 赋值操作(装箱或拆箱)
  • 进行加减乘除混合运算 (拆箱)
  • 进行>,<,==比较运算(拆箱)
  • 调用equals进行比较(装箱)
  • ArrayList、HashMap等集合类添加基础类型数据时(装箱)
// 实例代码
Integer x = 1; // 装箱 调⽤ Integer.valueOf(1)
int y = x; // 拆箱 调⽤了 X.intValue()
// 经典面试题
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true

Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false

原因分析:比较的时候会调用Integer.valueOf(),从源码可以看出,这里的实现并不是简单的new Integer,而是
用IntegerCache做一个cach。默认Integer cache 的下限是-128,上限默认127。当赋值100给Integer时,刚好
在这个范围内,所以从cache中取对应的Integer并返回,所以a和b返回的是同一个对象,所以==比较是相等的,当赋值
200给Integer时,不在cache 的范围内,所以会new Integer并返回,当然==比较的结果是不相等的。

9. String 为什么不可变

// Java8 String类的源码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
}
从源码可以看出,String对象其实在内部就是一个个字符,存储在这个value数组里面的,value数组用final修饰,
final修饰的变量,值不能被修改。
String类内部所有的字段都是私有的,也就是被private修饰。而且String没有对外提供修改内部状态的方法,
因此value数组不能改变。
  • 线程安全。同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。
  • 支持hash映射和缓存。 因为String的hash值经常会使用到,比如作为 Map 的键,不可变的特性使得 hash 值也不会变,不需要重新计算。
  • 出于安全考虑。网络地址URL、文件路径path、密码通常情况下都是以String类型保存,假若String不是固定不变的,将会引起各种安全隐患。比如将密码用String的类型保存,那么它将一直留在内存中,直到垃圾收集器把它清除。假如String类不是固定不变的,那么这个密码可能会被改变,导致出现安全隐患。
  • 字符串常量池优化。String对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用。

既然我们的String是不可变的,它内部还有很多substring, replace, replaceAll这些操作的方法。这些方法好像会改变String对象?怎么解释呢?

其实不是的,我们每次调用replace等方法,其实会在堆内存中创建了一个新的对象。然后其value数组引用指向不同的对象。

10. String, StringBuffer 和 StringBuilder区别

可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步

扩展:StringBuilder 相比于 StringBuffer 性能更高

11. StringJoiner()

StringJoiner是 Java 8 新增的一个 API,它基于 StringBuilder 实现,用于实现对字符串之间通过分隔符拼接的场景。
StringJoiner 有两个构造方法,第一个构造要求依次传入分隔符、前缀和后缀。第二个构造则只要求传入分隔符即可(前缀和后缀默认为空字符串)。

StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
StringJoiner(CharSequence delimiter)

// 实例如下:
StringJoiner sj1 = new StringJoiner("---"); // 间隔符
StringJoiner sj2 = new StringJoiner("---", "[", "]"); // 间隔符,开始符,结束符
sj2.add("aaa").add("bbb").add("ccc");

12. String 类的常用方法有哪些

  • indexOf():返回指定字符的索引。
  • charAt():返回指定索引处的字符。
  • replace():字符串替换。
  • trim():去除字符串两端空白。
  • split():分割字符串,返回一个分割后的字符串数组。
  • getBytes():返回字符串的 byte 类型数组。
  • length():返回字符串长度。
  • toLowerCase():将字符串转成小写字母。
  • toUpperCase():将字符串转成大写字符。
  • substring():截取字符串。
  • equals():字符串比较。

13. new String("hsy")会创建几个对象

使用这种方式会创建两个字符串对象(前提是字符串常量池中没有 "hsy" 这个字符串对象)。

  • "hsy" 属于字符串字面量,因此编译时期会在字符串常量池中创建一个字符串对象,指向这个 "hsy" 字符串字面量;
  • 使用 new 的方式会在堆中创建一个字符串对象。

14. 什么是字符串常量池

字符串常量池(String Pool)保存着所有字符串字面量,这些字面量在编译时期就确定。字符串常量池位于堆内存中,专门用来存储字符串常量。在创建字符串时,JVM首先会检查字符串常量池,如果该字符串已经存在池中,则返回其引用,如果不存在,则创建此字符串并放入池中,并返回其引用。

15. Object常用方法有哪些

Object常用方法有:toString()equals()hashCode()clone()等。 \

  • toString

默认输出对象地址,可以重写toString方法,按照重写逻辑输出对象值。

  • equals

默认比较两个引用变量是否指向同一个对象(内存地址)。可以重写equals方法,按照属性值是否相等来判断

  • hashCode

将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来。一般需要重写成按照对象属性值来计算。例如,使用Set等不可重复集合存储自定义对象,一定要重写hashCode()函数和equals()函数,hashSet就是基于hash值去重的,idea有快捷键command + N选择相应重写即可。

  • clone

Java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的。如果使用需要实现Cloneable接口并覆盖clone()方法,可见性提升为public。

  • wait

当前线程调用对象的wait()方法之后,当前线程会释放对象锁,进入等待状态。等待其他线程调用此对象的notify()/notifyAll()唤醒或者等待超时时间wait(long timeout)自动唤醒。线程需要获取obj对象锁之后才能调用 obj.wait()。

1. sleep是线程类(Thread)的方法;wait是Object类的方法
2. sleep是使线程休眠,不会释放对象锁;wait是使线程等待,释放锁
3. sleep让出的是cpu,如果此时代码是加锁的,那么即使让出了CPU,其他线程也无法运行,因为没有得到锁;wait是让自
己暂时等待,放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进
入对象锁定池准备获得对象锁进入运行状态。
4. 调用sleep进入阻塞状态;调用wait进入就绪状态
  • notify

obj.notify()唤醒在此对象上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象上等待的所有线程。

16. 深拷贝和浅拷贝

浅拷贝:拷⻉对象和原始对象的引⽤类型引用同⼀个对象
深拷贝:拷贝对象和原始对象的引用类型引用不同的对象。
区别在于拷贝的是地址引用还是属性值

17. 两个对象的hashCode()相同,则 equals()是否也一定为 true

equals与hashcode的关系:

  • 如果两个对象调用equals比较返回true,那么它们的hashCode值一定要相同;
  • 如果两个对象的hashCode相同,它们并不一定相同。

hashcode方法主要是用来提升对象比较的效率,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。

之所以重写equals()要重写hashcode(),是为了保证equals()方法返回true的情况下hashcode值也要一致,如果重写了equals()没有重写hashcode(),就会出现两个对象相等但hashcode()不相等的情况。这样,当用其中的一个对象作为键保存到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。

18. Java创建对象有几种方式

  • 用new语句创建对象。
  • 使用反射,使用Class.newInstance()创建对象。
  • 调用对象的clone()方法。
  • 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。

19. 说说类实例化的顺序

  1. 静态属性,静态代码块。
  2. 普通属性,普通代码块。
  3. 构造方法。
static:表示静态,是JAVA中的一个修饰符,可以修饰成员方法,成员变量
特点:被该类所有对象共享 调用方式可以通过类名调用(推荐),也可以通过对象名调用
static变量存储在堆内存中的静态区
静态变量是随着类的加载而加载,优先于对象出现的(新建对象之前就有静态变量了)
静态方法只能访问静态变量个静态方法,非静态方法不受限制(原因其实就是静态方法可以不需要对象执行,那只能访问静态区的内容)
静态方法中没有this关键字

20. equals和==有什么区别

  • 对于基本数据类型,==比较的是他们的值。基本数据类型没有equal方法;
  • 对于复合数据类型,==比较的是它们的存放地址(是否是同一个对象)。equals()默认比较地址值,重写的话按照重写逻辑去比较。但是对于String来说,Java已经重写过了,所以对于String比较是否相等一般采用equals()函数
// 字符串是引用数据类型 保存在堆内存字符串串池中 当使用直接赋值时,系统会检查该字符串在串池中是否存在,若不存在则创建新的,若存在则复用
// 若使用new方法创建,则类似数组,都会创建新的
// 基于上述理论做 字符串比较
String s4 = "abc";
String s5 = "abc";
System.out.println(s4 == s5); // true
String s6 = new String("abc");
String s7 = new String("abc");
System.out.println(s6 == s7); // false
System.out.println(s4 == s6); // false
// 通过equals比较字符串值是否相等(严格区分大小写) equalsIgnoreCase比较(不区分大小写)
System.out.println(s4.equals(s6)); // true
// 扩展:通过Scanner输入一个字符串s8 = "abc",然后String s9 = "abc",比较也是不相等的
// 原因:sc.next()也是new出来的String

21. 常见的关键字有哪些

  • static
static 表示静态,是JAVA中的一个修饰符,可以修饰成员方法,成员变量
特点:被该类所有对象共享 调用方式可以通过类名调用(推荐),也可以通过对象名调用
static变量存储在堆内存中的静态区
静态变量是随着类的加载而加载,优先于对象出现的(新建对象之前就有静态变量了)
静态方法只能访问静态变量,非静态方法不受限制(原因其实就是静态方法可以不需要对象执行,那只能访问静态区的内容)
静态方法中没有this关键字
  • final
final关键字
1. 修饰方法:表明该方法是最终方法,不能被重写
2. 修饰类:表明该类是最终类,不能被继承
3. 修饰变量:表明常量,只能被赋值一次,基本数据类型值不变,引用数据类型地址不变
常量的命名规范:单个单词全部大写,多个单词全部大写,中间用下划线隔开
  • super
    super 关键字用于在子类中访问父类的变量和方法,一般在子类构造函数最上面即使不写也会默认加一个super()构造函数
  • this
this.属性名称 指访问类中的成员变量,可以用来区分成员变量和局部变量。
this.方法名称 用来访问本类的方法
this(args) 可以调用本类的构造方法,例如在无参构造里面调用有参构造,用来指定默认值
  • default
default 用于接口中定义默认方法 解决接口改变,所有子类必须相应改变的问题
格式:public default 返回值类型 方法名(参数列表)
注意事项 1. 默认方法不是抽象方法,不强制被重写,如果要重写,重写的时候实现类中去掉default关键字
        2. public 可以省略,default不可以省略
        3. 如果实现多个接口,且出现同名默认方法,实现类必须对该方法进行重写

22. final, finally, finalize 的区别

  • final 用于修饰属性、方法和类, 分别表示属性不能被重新赋值,方法不可被覆盖,类不可被继承。
  • finally 是异常处理语句结构的一部分,一般以try-catch-finally出现,finally代码块表示总是被执行。
  • finalize 是Object类的一个方法,该方法一般由垃圾回收器来调用,当我们调用System.gc()方法的时候,由垃圾回收器调用finalize()方法,回收垃圾,JVM并不保证此方法总被调用。

23. 方法重载和重写的区别

  • 同个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载。 参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。
  • 方法的重写描述的是父类和子类之间的。当父类的功能无法满足子类的需求,可以在子类对方法进行重写。 方法重写时, 方法名与形参列表必须一致。

24. 接口与抽象类区别

抽象类定义整个体系的共性,接口侧重于行为(方法)
eg: 动物抽象类和他的子类猫狗适合通过抽象类来定义,猫狗都会游泳的行为适合用接口来实现

  • 抽象类中的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型
  • 一个类只能继承一个抽象类,而一个类却可以实现多个接口
  • 抽象类可以有构造方法,而接口没有
抽象方法:将共性的行为(方法)抽取到父类以后,由于每一个子类执行的内容不一样,所以在父类中不能确定具体的方法体,该方法就可以定义为抽象方法
抽象类不能实例化,抽象类中不一定有抽象方法,但是有抽象方法的一定是抽象类
抽象类可以有构造方法,抽象类的子类要么重写自己的所有抽象方法(推荐) ,要么也是抽象类
接口关键字 interface eg: public interface 接口名
接口不能实例化
接口和类之间是实现关系,通过implements关键字表示
    1. 接口和类的实现关系,可以单实现,也可以多实现 eg: public class 类名 implements 接口名1, 接口名2 {}
    2. 实现类还可以在继承一个类的同时实现多个接口 eg: public class 类名 extends XXX implements 接口名1, 接口名2 {},如果有重名方法,只要实现一次即可
接口的子类(实现类),要么重写接口中的所有抽象方法(推荐),要么是抽象类
接口中成员的特点
1. 成员变量只能是常量 默认修饰符 public static final
2. 构造方法:没有
3. 成员方法只能是抽象方法,默认修饰符 public abstract
扩展  JDK8新特性:接口中可以定义有方法体的方法  JDK9新特性:接口中可以定义私有方法
接口多态:当一个方法的参数是接口时,可以传递接口所有实现类的对象
适配器接口模式:解决接口与接口实现类之间的矛盾问题
// 具体来说就是A接口定义了n中抽象方法,但是实现类C只用第一个抽象方法,此时可以定义一个接口适配器类B去重写A接口中的所有方法(可以只重写第一个,
// 其他空实现就行),然后C继承B,去重写想用的方法
// public interface A { ...n中抽象方法 }
// public (abstract 可以设置为抽象类,因为只是承接作用,不会实例化) class B implements A { override各种方法 }
// public C extends B { override想用的方法 }
1. 类和类的关系:继承关系,只能单继承,不能多继承,还可以多层继承
2. 类和接口的关系:实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
3. 接口和接口的关系:继承关系,可以单继承,也可以多继承。如果实现类实现了最下面的子接口,那么就需要重写体系当中所有的抽象方法

25. Error和Exception的区别

  • Error:JVM 无法解决的严重问题,如栈溢出StackOverflowError、内存溢出OOM等。程序无法处理的错误。
  • Exception:其它因编程错误或偶然的外在因素导致的一般性问题。可以在代码中进行处理。如:空指针异常、数组下标越界等。

26. BIO/NIO/AIO区别

  • 同步阻塞IO : 用户进程发起一个IO操作以后,必须等待IO操作的真正完成后,才能继续运行。
  • 同步非阻塞IO: 客户端与服务器通过Channel连接,采用多路复用器轮询注册的Channel。提高吞吐量和可靠性。用户进程发起一个IO操作以后,可做其它事情,但用户进程需要轮询IO操作是否完成,这样造成不必要的CPU资源浪费。
  • 异步非阻塞IO: 非阻塞异步通信模式,NIO的升级版,采用异步通道实现异步通信,其read和write方法均是异步方法。用户进程发起一个IO操作,然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知。类似Future模式。

27. 守护线程是什么?

  • 守护线程是运行在后台的一种特殊进程。
  • 它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
  • 在 Java 中垃圾回收线程就是特殊的守护线程。

28. Java支持多继承吗

java中,类不支持多继承。接口才支持多继承。接口的作用是拓展对象功能。当一个子接口继承了多个父接口时,说明子接口拓展了多个功能。当一个类实现该接口时,就拓展了多个的功能。

Java支持多层继承

Java不支持多继承的原因:

  • 出于安全性的考虑,如果子类继承的多个父类里面有相同的方法或者属性,子类将不知道具体要继承哪个。
  • Java提供了接口和内部类以达到实现多继承功能,弥补单继承的缺陷。

29. 如何实现对象克隆

  • 实现Cloneable接口,重写 clone() 方法。这种方式是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。如果对象的属性的Class也实现 Cloneable 接口,那么在克隆对象时也会克隆属性,即深拷贝。
  • 结合序列化,深拷贝。
  • 通过org.apache.commons中的工具类BeanUtilsPropertyUtils进行对象复制。

30. 同步和异步的区别

  • 同步:发出一个调用时,在没有得到结果之前,该调用就不返回。
  • 异步:在调用发出后,被调用者返回结果之后会通知调用者,或通过回调函数处理这个调用。

31. 阻塞和非阻塞的区别

阻塞和非阻塞关注的是线程的状态。

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会恢复运行。

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

32. 什么是序列化和反序列化

  • 序列化:把内存中的对象转换为字节序列的过程。
  • 反序列化:把字节序列恢复为Java对象的过程。 \
如何实现序列化
实现`Serializable`接口即可。序列化的时候(如`objectOutputStream.writeObject(user)`),会判断user
是否实现了`Serializable`,如果对象没有实现`Serializable`接口,在序列化的时候会抛出
`NotSerializableException`异常。

33. transient关键字的作用

Java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。

也就是说被transient修饰的成员变量,在序列化的时候其值会被忽略,在被反序列化后, transient 变量的值被设为初始值, 如 int 型的是 0,对象型的是 null。

34. 什么是泛型

Java泛型是JDK 5中引⼊的⼀个新特性, 允许在定义类和接口的时候使⽤类型参数。声明的类型参数在使⽤时⽤具体的类型来替换。

泛型最⼤的好处是可以提⾼代码的复⽤性。以List接口为例,我们可以将String、 Integer等类型放⼊List中, 如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题。

泛型中不能写基本数据类型
指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
如果不写泛型,类型默认是Object

泛型类,使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类 eg public class ArrayList<E> {}
上面的E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成T, E, K,  V等

泛型方法,使用场景:方法中形参类型不确定时(泛型类里面的方法可以直接使用类中定义的泛型,也可以使用方法中定义的泛型)
public <E> void show(类型 变量名) {}

泛型接口 eg: public interface List<E>
实现方式:1. 实现类给出具体的类型 eg ... implements List<String>  2. 实现类延续泛型,创建实现类对象时再确定类型

泛型不具备继承性,但是数据具备继承性

泛型通配符 ?表示不确定的类型,可以进行类型的限定
? extends E 表示可以传递E或者所有E的子类
? super E 表示可以传递E或者所有E的父类
应用场景: 1. 如果在定义类,方法,接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口          2. 如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型通配符
关键点:可以限定类型的范围
public static<E> void say(ArrayList<? extends Ye> list) {

}

35. 如何停止一个正在运行的线程

结论:interrupt() 加上手动抛异常的方式是目前中断一个正在运行的线程最为正确的方式

  • 使用线程的stop方法

使用stop()方法可以强制终止线程。不过stop是一个被废弃掉的方法,不推荐使用。

使用Stop方法,会一直向上传播ThreadDeath异常,从而使得目标线程解锁所有锁住的监视器,即释放掉所有的对象锁。使得之前被锁住的对象得不到同步的处理,因此可能会造成数据不一致的问题。

  • 使用interrupt方法中断线程

该方法只是告诉线程要终止,但最终何时终止取决于计算机。调用interrupt方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。

接着调用 Thread.currentThread().isInterrupted()方法,可以用来判断当前线程是否被终止,通过这个判断我们可以做一些业务逻辑处理,通常如果isInterrupted返回true的话,会抛一个中断异常,然后通过try-catch捕获。

  • 设置标志位

设置标志位,当标识位为某个值时,使线程正常退出。设置标志位是用到了共享变量的方式,为了保证共享变量在内存中的可见性,可以使用volatile修饰它,这样的话,变量取值始终会从主存中获取最新值。

但是这种volatile标记共享变量的方式,在线程发生阻塞时是无法完成响应的。比如调用Thread.sleep() 方法之后,线程处于不可运行状态,即便是主线程修改了共享变量的值,该线程此时根本无法检查循环标志,所以也就无法实现线程中断。

36. 单列集合 Collection 和双列集合 Map

Collection_Map.png

  • List系列集合: 添加的元素有序,可重复,有索引
  • Set系列集合: 添加的元素无序,不重复,无索引 \

数据机构

  • ArrayList:底层数据结构采用 Object数组
  • LinkedList:双向链表
  • HashSet:基于 HashMap 实现的,底层采用 HashMap 来保存元素
  • LinkedHashMap:内部是通过 LinkedHashMap 来实现的
  • TreeSet:红黑树(自平衡的排序二叉树)
  • HashMap:数组 + 链表 + 红黑树
  • LinkedHashMap:在HashMap的基础上增加一条双向链表
  • TreeMap:红黑树(平衡二叉排序树)

Set区别

  • HashSet: 添加的元素无序,不重复,无索引
  • LinkedSet: 有序,不重复,无索引,如果要求去重且存取有序才使用LinkedList
  • TreeSet: 不重复,无索引,可排序 \

Map区别

  • HashMap: 无序,不重复,无索引
  • LinkedHashMap: 有序,不重复,无索引
  • TreeSet: 不重复,无索引,可排序
Collect单列集合的遍历方式
ArrayList<String> al = new ArrayList<>();
al.add("hsy");
al.add("yt");
al.add("hwh");
al.add("haq");
// 1. 通过迭代器遍历
Iterator<String> it = al.iterator();
// hasNext() 判断当前位置是否有元素
// next() 获取当前位置的元素,并将迭代器对象指向下一个位置
// 迭代器遍历完毕,指针不会复位,要想重新遍历,只能再定义一个迭代器
// 在迭代器遍历的过程中可以通过迭代器的特有方式删除元素(不能通过传统方法,会报错)
while(it.hasNext()) {
    String str = it.next();
    if(str == "hwh") {
        // 迭代器特有的删除方式
        it.remove();
    }
    System.out.println(str);
}
// List 列表具有特有的迭代器:列表迭代器ListIterator,列表迭代器可以在遍历的时候用迭代器特有方法添加元素
ListIterator<String> lit = al.listIterator();
while(lit.hasNext()) {
    String str = lit.next();
    if(str == "haq") {
        // 迭代器特有的删除方式
        lit.add("hwh");
    }
    System.out.println(str);
}

// 2. 通过强化for遍历:可以遍历单列集合和数组,不会改变原来的数据
for(String str:al) {
    System.out.println(str);
}

// 3. forEach结合匿名内部类遍历,可以通过lambda表达式简化
al.forEach(new Consumer<String>() {
    // 原理:依次得到每一个元素,把得到的每一个元素传递给下面的accept方法
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});
// lambda表达式简化
al.forEach((s) -> {
    System.out.println(s);
});
// 再次简化,参考JS语法
al.forEach(s -> System.out.println(s));
// 双列集合 Map
// Map的遍历方式
Map<String, Integer> map = new HashMap<>();
map.put("hsy", 24);
map.put("yt", 25);
map.put("hwh", 18);
map.put("haq", 16);
// 遍历方式一:通过键找值,利用单列集合Set存储key,然后遍历(单列集合的三种遍历方式)
Set<String> set = map.keySet();
Iterator<String> it = set.iterator();
while(it.hasNext()) {
    String str = it.next();
    System.out.println(map.get(str));
}
// 遍历方式二:键值对
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for(Map.Entry<String, Integer> entry : entries) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}
// 遍历方式三:forEach + lambda表达式
map.forEach((key, value) -> {
    System.out.println(key + "=" + value);
});

37. 线程创建的五种方式和线程池创建的七种方式

Java创建线程的五种方式:

  • 继承Thread类创建线程类
  • 实现Runnable接口
  • 通过Callable和Future创建线程
  • 使用lambda表达式
  • 使用线程池创建(常用)

Java线程池创建的七种方式

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。
  4. Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
  7. ThreadPoolExecutor(最核心):手动创建线程池的方式,它创建时最多可以设置 7 个参数。

38. Java的七种垃圾收集器

  • Serial 收集器
  • Parallel 收集器
  • Concurrent Mark Sweep(CMS)收集器
  • G1 收集器
  • Epsilon 收集器
  • Shenandoah 收集器
  • ZGC 收集器

数据库

1. 数据库了解多少

2. 基本的数据库指令

3. 数据库索引了解吗?组合索引是怎么用的

4. 数据库视图了解吗?在数据库应用中是如何使用的

5. 数据库事务在实际应用中是如何使用的

6. 数据库里面常见的表连接方式

7. union和union all区别

8. MySql查询优化方式

9. 事务的隔离级别

操作系统

1. 进程和线程

进程是CPU分配资源的基本单位,线程是CPU能够进行运算调度的基本单位

  • 一个程序至少有一个进程,一个进程至少有一个线程
  • 线程不能独立于进程而存在
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存
  • 线程执行开销小,但不利于资源的管理和保护,而进程正相反
  • 进程间不会相互影响,但一个线程挂掉,会导致整个进程挂掉
  • 线程是需要时创建,完成时结束释放

2. 死锁

在多线程编程中,为了防止多个线程竞争共享资源而导致数据的错乱,一般会在操作共享资源之前加上互斥锁,只有获取到了锁的线程才能操作共享资源,没有获取到锁的线程就只能等待,直到锁被释放。当两个线程为了保护不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等对方释放锁,在没有外力的作用下,这些线程会一直相互等待,没办法继续运行,这种情况就是发生了死锁。

  • 互斥条件:多个线程不能同时使用同一个资源
  • 持有并等待:例如A持有资源1,又想要资源2,但资源2已经被C持有,所以A处于等待状态,同时不会释放自己的资源1
  • 不可剥夺:当线程已经持有了资源,在自己使用完成之前不能被其他线程获取
  • 环路:两个线程获取资源的顺序构成了环形链

3. linux下常用的命令

  • 文件目录
    • cd: 进入目录,回到上一级目录等等
    • ls: 列出目录信息,默认是当前目录
    • mkdir: 创建目录
    • pwd & dirs: 查询当前位置所在目录
    • cp: 将源文件或目录复制到目标文件或目录中
    • mv: 对文件或目录重新命名,或者将文件从一个目录移到另一个目录中
    • touch: 创建新的空文件
  • 查看文件内容
    • cat: 连接多个文件并打印到标准输出
    • head: 显示文件的开头部分
    • tail: 在屏幕上显示指定文件的末尾若干行
    • more: 显示文件内容,每次显示一屏
    • less: 分屏上下翻页浏览文件内容
    • grep: 全面搜索正则表达式并把行打印出来,是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来
    • wc: 获取文件行数,字数,字节数
  • 搜索文件
    • find
    • ll
  • 文本处理
    • vim: vi命令 是UNIX操作系统和类UNIX操作系统中最通用的全屏幕纯文本编辑器
  • 进程
    • ps aux: 报告当前系统的进程状态
    • ps -elf: 与上类似
    • top: 以全屏交互式的界面显示进程排名,及时跟踪包括CPU、内存等系统资源占用情况,默认情况下每三秒刷新一次,其作用基本类似于Windows系统中的任务管理器
    • kill pid: 杀死进程号为pid的进程
    • kill -9 pid: 强制杀死进程
  • 打包,压缩
    • tar
    • gz
    • zip
  • 网络信息
    • netstat -anp | grep mysqld: 网络端口查询
  • 磁盘
    • df: 显示磁盘分区上的可使用的磁盘空间
    • du: 查看使用空间的,但是与df命令不同的是Linux du命令是对文件和目录磁盘使用的空间的查看

数据结构

1. 红黑树

红黑树是一种自平衡的二叉查找树,是一种高效的查找树,可在 O(logN) 时间内完成查找、增加、删除等操作。

红黑树是一个二叉搜索树,它在每个节点增加了一个存储位记录节点的颜色,可以是红色,也可以是黑色;通过任意一条从根到叶子简单路径上颜色的约束,红黑树保证最长路径不超过最短路径的二倍,因而近似平衡(最短路径就是全黑节点,最长路径就是一个红节点一个黑节点,当从根节点到叶子节点的路径上黑色节点相同时,最长路径刚好是最短路径的两倍)。它同时满足以下特性:

  • 节点是红色或黑色
  • 根是黑色
  • 叶子节点(外部节点,空节点)都是黑色,这里的叶子节点指的是最底层的空节点(外部节点),下图中的那些null节点才是叶子节点,null节点的父节点在红黑树里不将其看作叶子节点
  • 红色节点的子节点都是黑色
    • 红色节点的父节点都是黑色
    • 从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点
  • 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点

2. 图的基本介绍

在图中,最基本的单元是顶点(vertex),相当于树中的节点。顶点之间的关联关系,被称为边(edge)

还有一种图,顶点之间的关联并不是完全对称的。还拿微信来举例,你的好友列表里有我,但我的好友列表里未必有你。这样一来,顶点之间的边就有了方向的区分,这种带有方向的图被称为有向图。

图在内存中存储的方式有很多种,如邻接矩阵、邻接表、逆邻接表、十字链表

邻接矩阵:拥有n个顶点的图,它所包含的连接数量最多是n(n-1)个(考虑方向,则是n(n-1)/2,不考虑方向,则是n(n-1))。