Java 知识点整理：算法、设计模式、语法 精

排序算法 9

P2：直接插入排序

```public void insertionSort(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int insertNum = nums[i];
int insertIndex;
for (insertIndex = i - 1; insertIndex >= 0 && nums[insertIndex] > insertNum; insertIndex--) {
nums[insertIndex + 1] = nums[insertIndex];
}
nums[insertIndex + 1] = insertNum;
}
}```

```public void binaryInsertionSort(int[] nums) {
for (int index = 1; index < nums.length; index++) {
int insertNum = nums[index];
int insertIndex = -1;
int start = 0;
int end = index - 1;
while (start <= end) {
int mid = start + (end - start) / 2;
if (insertNum > nums[mid])
start = mid + 1;
else if (insertNum < nums[mid])
end = mid - 1;
else {
insertIndex = mid + 1;
break;
}
}
if (insertIndex == -1)
insertIndex = start;
if (index - insertIndex >= 0)
System.arraycopy(nums, insertIndex, nums, insertIndex + 1, index - insertIndex);
nums[insertIndex] = insertNum;
}
}```

P3：希尔排序

```public void shellSort(int[] nums) {
for (int d = nums.length / 2; d > 0 ; d /= 2) {
for (int i = d; i < nums.length; i++) {
int insertNum = nums[i];
int insertIndex;
for (insertIndex = i - d; insertIndex >= 0 && nums[insertIndex] > insertNum; insertIndex -= d) {
nums[insertIndex + d] = nums[insertIndex];
}
nums[insertIndex + d] = insertNum;
}
}
}```

P4：直接选择排序

```public void selectSort(int[] nums) {
int minIndex;
for (int index = 0; index < nums.length - 1; index++){
minIndex = index;
for (int i = index + 1;i < nums.length; i++){
if(nums[i] < nums[minIndex])
minIndex = i;
}
if (index != minIndex){
swap(nums, index, minIndex);
}
}
}```

P5：堆排序

```public void add(int[] nums, int i, int num){
nums[i] = num;
int curIndex = i;
while (curIndex > 0) {
int parentIndex = (curIndex - 1) / 2;
if (nums[parentIndex] < nums[curIndex])
swap(nums, parentIndex, curIndex);
else break;
curIndex =parentIndex;
}
}

public int remove(int[] nums, int size){
int result = nums[0];
nums[0] = nums[size - 1];
int curIndex = 0;
while (true) {
int leftIndex = curIndex * 2 + 1;
int rightIndex = curIndex * 2 + 2;
if (leftIndex >= size) break;
int maxIndex = leftIndex;
if (rightIndex < size && nums[maxIndex] < nums[rightIndex])
maxIndex = rightIndex;
if (nums[curIndex] < nums[maxIndex])
swap(nums, curIndex, maxIndex);
else break;
curIndex = maxIndex;
}
return result;
}```

P6：冒泡排序

```public void bubbleSort(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
for (int index = 0; index < nums.length - 1 - i; index++) {
if (nums[index] > nums[index + 1])
swap(nums, index, index + 1)
}
}
}```

```public void betterBubbleSort(int[] nums) {
boolean swap;
for (int i = 0; i < nums.length - 1; i++) {
swap = true;
for (int index = 0; index < nums.length - 1 - i; index++) {
if (nums[index] > nums[index + 1]) {
swap(nums, index ,index + 1);
swap = false;
}
}
if (swap) break;
}
}```

P7：快速排序

```public void quickSort(int[] nums, int start, int end) {
if (start < end) {
int pivotIndex = getPivotIndex(nums, start, end);
quickSort(nums, start, pivotIndex - 1);
quickSort(nums, pivotIndex + 1, end);
}
}

public int getPivotIndex(int[] nums, int start, int end) {
int pivot = nums[start];
int low = start;
int high = end;
while (low < high) {
while (low <= high && nums[low] <= pivot)
low++;
while (low <= high && nums[high] > pivot)
high--;
if (low < high)
swap(nums, low, high);
}
swap(nums, start, high);
return high;
}```

P8：归并排序

```int[] help;

public void mergeSort(int[] arr) {
int[] help = new int[arr.length];
sort(arr, 0, arr.length - 1);
}

public void sort(int[] arr, int start, int end) {
if (start == end) return;
int mid = start + (end - start) / 2;
sort(arr, start, mid);
sort(arr, mid + 1, end);
merge(arr, start, mid, end);
}

public void merge(int[] arr, int start, int mid, int end) {
if (end + 1 - start >= 0) System.arraycopy(arr, start, help, start, end + 1 - start);
int p = start;
int q = mid + 1;
int index = start;
while (p <= mid && q <= end) {
if (help[p] < help[q])
arr[index++] = help[p++];
else
arr[index++] = help[q++];
}
while (p <= mid) arr[index++] = help[p++];
while (q <= end) arr[index++] = help[q++];
}```

设计模式 7

P3：工厂模式

```public class MacDonaldFactory {
public Hamburger eatHamburger(String name) {
if ("beef".equals(name))
return new BeefHamburger();
else if ("pig".equals(name))
return new PigHamburger();
return null;
}
}

interface Hamburger {
void eat();
}

class BeefHamburger implements Hamburger {
@Override
public void eat() {
System.out.println("吃牛肉汉堡");
}
}

class PigHamburger implements Hamburger {
@Override
public void eat() {
System.out.println("吃猪肉汉堡");
}
}```

```public interface HamburgerFactory {
Hamburger build();
}

class MCFactory implements HamburgerFactory {
@Override
public Hamburger build() {
return new MCHamburger();
}
}

class KFCFactory implements HamburgerFactory {
@Override
public Hamburger build() {
return new KFCHamburger();
}
}

interface Hamburger {
void eat();
}

class MCHamburger implements Hamburger {
@Override
public void eat() {
System.out.println("吃麦当劳汉堡");
}
}

class KFCHamburger implements Hamburger {
@Override
public void eat() {
System.out.println("吃肯德基汉堡");
}
}```

```public interface FoodFactory {
Hamburger buildHamburger();
Drink buildDrink();
}

class MCFactory implements FoodFactory {
@Override
public Hamburger buildHamburger() {
return new MCHamburger();
}

@Override
public Drink buildDrink() {
return new MCDrink();
}
}

class KFCFactory implements FoodFactory {
@Override
public Hamburger buildHamburger() {
return new KFCHamburger();
}

@Override
public Drink buildDrink() {
return new KFCDrink();
}
}

interface Hamburger {
void eat();
}

class MCHamburger implements Hamburger {
@Override
public void eat() {
System.out.println("吃麦当劳汉堡");
}
}

class KFCHamburger implements Hamburger {
@Override
public void eat() {
System.out.println("吃肯德基汉堡");
}
}

interface Drink {
void drink();
}

class MCDrink implements Drink {
@Override
public void drink() {
System.out.println("喝麦当劳饮料");
}
}

class KFCDrink implements Drink {
@Override
public void drink() {
System.out.println("喝肯德基饮料");
}
}```

P4：单例模式

```public class HungrySingleton {
private HungrySingleton(){}

private static HungrySingleton instance = new HungrySingleton();

public static HungrySingleton getInstance() {
return instance;
}
}```

```public class LazySingleton {
private LazySingleton(){}

private static LazySingleton instance;

public static LazySingleton getInstance() {
if(instance == null) {
instance = new LazySingleton();
}
return instance;
}
}```

```public class DoubleCheckSingleton {
private DoubleCheckSingleton(){}

private volatile static DoubleCheckSingleton instance;

public static DoubleCheckSingleton getInstance() {
if(instance == null) {
synchronized (DoubleCheckSingleton.class) {
if (instance == null) {
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}```

```public class StaticSingleton {
private StaticSingleton(){}

public static StaticSingleton getInstance() {
return StaticClass.instance;
}

private static class StaticClass {
private static final StaticSingleton instance = new StaticSingleton();
}
}```

```public enum EnumSingleton {
INSTANCE;
}```

P5：代理模式

```public interface Company {
void findWorker();
}

public class Hr implements Company {
@Override
public void findWorker() {
System.out.println("我需要找招聘一个员工");
}
}

public class Proxy implements Company {
private Hr hr;

public Proxy(){
this.hr = new Hr();
}

@Override
public void findWorker() {
hr.findWorker();
System.out.println("找到了员工");
}

}```

• JDK 动态代理：通过 `Proxy`类的 `newInstance` 方法获取一个动态代理对象，需要传入三个参数，被代理对象的类加载器、被代理对象实现的接口，以及一个 `InvocationHandler` 调用处理器实例来指明具体的逻辑，相比静态代理最大的优势是接口中声明的所有方法都被转移到 `InvocationHandler` 中的 `invoke` 方法集中处理。

```public static void main(String[] args) {
Hr hr = new Hr();
Hr proxyHr = (Hr) Proxy.newProxyInstance(hr.getClass().getClassLoader(), hr.getClass().getInterfaces(), (proxy, method, args1) -> {
System.out.println("接收代理请求");
Object obj = method.invoke(hr, args1);
System.out.println("找到了员工，完成请求");
return obj;
});
proxyHr.findWorker();
}```
• CGLib 动态代理：与 JDK 动态代理不同的是，JDK 动态代理要求实现被代理对象的接口，而 CGLib 要求继承被代理对象，如果一个类是 final 类则不能使用 CGLib 动态代理。两种代理都是在运行期生成字节码，JDK 动态代理直接写字节码，而 CGLib 动态代理使用 ASM 框架写字节码，ASM 作用于已编译好的 Class 文件，其目的是生成、转换和分析以字节数组表示的已编译 Java 类。 JDK 动态代理调用代理方法是通过反射机制实现的，而 GCLib 动态代理是通过 FastClass 机制直接调用方法的，为代理类和被代理类各生成一个类，该类为代理类和被代理类的方法会分配一个 int 类型的参数，调用方法时可以直接定位而省去反射，因此调用方法的效率更高。

Java 基础 17

P1：Java 语言的基本概念

• 具有平台无关性，摆脱了硬件平台的束缚，实现了“一次编写，到处运行”的理想。

• 提供了一种相对安全的内存管理和访问机制，避免了绝大部分内存泄漏和指针越界问题。

• 实现了热点代码检测和运行时编译及优化，使得 Java 程序随运行时间增长可以获得更高的性能。

• 有一套完善的应用程序接口，还支持很多第三方类库。

Java 平台无关性原理：

• 编译器生成一个体系结构中立的目标文件格式，这是一种编译后的代码，只要有 Java 运行时系统，这些编译后的代码可以在很多处理器上运行。Java 编译器通过生成与特定计算机体系结构无关的字节码指令来实现这一特性，字节码文件不仅可以很容易地在任何机器上解释执行，还可以动态地转换成本地机器代码，转换是由 JVM 实现的，JVM 是平台相关的，屏蔽了不同操作系统的差异。
• Java 中基本数据类型的大小以及有关运算的行为都有明确的说明，例如 Java 中的 int 类型永远为 32 位的整数，而在 C/C++ 中 int 可能是 16 位整数、32 位整数，也可能是编译器开发商指定的其他任何大小。在 Java 中数值类型有固定的字节数，二进制数据以固定的格式进行存储和传输，字符串则采用标准的 Unicode 格式存储。

• JDK：Java Development Kit，Java 开发工具包。它提供了编译、运行 Java 程序所需的各种工具和资源，包括 Java 编译器、JRE 以及常用的 Java 基础类库等，是 JAVA 的核心。JDK 是编写 Java 程序的程序员使用的软件。
• JRE：Java Runtime Environment，Java 运行时环境，是运行基于 Java 语言编写的程序所不可缺少的运行环境。JRE 是运行 Java 程序的用户使用的软件。
• SE：Standard Edition，标准版，用于桌面或简单服务器应用的 Java 平台。
• EE：Enterprise Edition，企业版，用于复杂服务器应用的 Java 平台。
• ME：Micro Edition，微型版，用于小型设备的 Java 平台。

P2：Java 基本数据类型

byte 1 字节 -2^7^ ~ 2^7^-1
short 2 字节 -2^15^ ~ 2^15^-1
int 4 字节 -2^31^ ~ 2^31^-1
long 8 字节 -2^63^ ~ 2^63^-1
float 4 字节 ±3.4E+38F（有效位数 6~7 位）
double 8 字节 ±1.7E+308（有效位数 15 位）
char 英文在 UTF-8 和 GBK 中均占 1 字节，中文在 UTF-8 占 3 字节，GBK 占 2 字节。 /
boolean 单个变量用 int 代替，占 4 字节，而数组会编码成 byte 数组，占 1 字节。 true、false

P5：面向对象

• 封装：也称数据隐藏，从形式上看就是将数据和行为组合在一个包中，并对对象的使用者隐藏具体的实现方式。
• 继承：可以通过继承来扩展一个类，扩展的子类可以继承父类的属性和方法，并可以添加自己独有的属性和方法。Java 中类只可以单继承，接口之间是可以多继承的。继承是一种"is-a"的关系，可以提高代码的复用性。
• 多态：父类的变量可以引用一个子类的对象，在运行时通过动态绑定来决定调用的方法。
• 重载：是指同一个类中具有多个方法名相同而方法参数列表不同的方法，重载方法的返回值类型不做要求，但方法的参数列表必须不同。重载属于一种编译时多态。
• 重写：是指子类具有和父类方法名和方法参数列表都相同的方法，要求返回值不大于父类方法的返回值，抛出的异常类型不大于父类方法抛出的异常类型，访问修饰符可见性不小于父类方法的访问修饰符可见性。重写属于一种运行时多态。

public
protected ×

private × × ×

P8：Object 类

Object 的类是所有类的父类，Object 类的方法：

• equals：用于检测一个对象是否等于另一个对象，默认使用 `==` 比较两个对象的引用，可以重写 equals 方法自定义比较规则。equals 方法需要满足以下规范：自反性、对称性、传递性、一致性并对于任何非空引用 x，`x.equals(null)` 返回 false。
• hashCode：散列码是由对象导出的一个整型值，是没有规律的，每个对象都有一个默认的散列码，值由对象的存储地址得出。字符串可能有相同的散列码，因为字符串的散列码是由内容导出的。为了在集合中正确使用对象，一般需要同时重写 equals 和 hashCode 方法，要求是 equals 相同是 hashCode 必须相同，但 hashCode 相同时 equals 未必相同，因此 hashCode 是两个对象相同的必要不充分条件。
• toString：打印对象时默认会调用它的 toString 方法，如果没有重写该方法默认打印的是表示对象值的一个字符串，一般需要重写该方法。打印数组时可以使用 `Arrays.toString()` 方法。
• clone：clone 方法声明为 protected，类只能通过该方法克隆它自己的对象，如果希望其他类也能调用该方法必须定义该方法为 public。如果一个对象的类没有实现 Cloneable 接口，该对象调用 clone 方法会抛出一个 CloneNotSupport 异常。默认的 clone 方法是浅拷贝，一般重写 clone 方法需要实现 Cloneable 接口并指定访问修饰符为 public。
• 浅拷贝：如果对象包含子对象的引用，拷贝字段就会得到相同子对象的另一个引用，如果共享的子对象是不可变的则是安全的，通常子对象都是可变的，因此浅拷贝是不安全的，拷贝对象的更改会影响原对象。
• 深拷贝：会完全拷贝基本数据类型和引用数据类型，深拷贝是安全的。
• finalize：在垃圾收集器清理对象之前调用，由于无法确定该对象执行的具体时机因此已经被废弃。
• getClass：返回包含对象信息的类对象。
• wait / notify / notifyAll：阻塞或唤醒持有该对象锁的线程。

P9：内部类

```class OuterClass{

static class StaticInnerClass {}

class NormalInnerClass {}

public void test() {

class LocalClass {}

// 静态内部类创建对象
new OuterClass.StaticInnerClass();

// 成员内部类创建对象
new OuterClass().new NormalInnerClass();

// 局部内部类创建对象
new LocalClass();

// 匿名内部类创建对象
Runnable runnable = () -> {};
}
}```

P10：static

static 关键字主要有两个作用：（1）为某特定数据类型或对象分配单一的存储空间，而与创建对象的个数无关。（2）让某个属性或方法与类而不是对象关联在一起，可以在不创建对象的情况下通过类名来访问。

static 修饰的变量称为静态变量，也叫做类变量，可以直接通过类名来访问，静态变量存储在 JVM 的方法区中。

static 修饰的方法称为静态方法，也叫做类方法，可以直接通过类名来访问，静态方法只能访问静态变量或静态方法。

static 修饰的代码块称为静态代码块，只能定义在类下，会在类加载时执行，只会执行一次。

static 修饰的类称为静态内部类，可以访问外部类的静态变量和方法。

static 也可以用来导入包下的静态变量。

（1）父类静态代码块和静态变量
（2）子类静态代码块和静态变量
（3）父类普通代码块和普通变量
（4）父类构造器
（5）子类普通代码块和普通变量
（6）子类构造器

P11：序列化和反序列化

Java 对象在 JVM 运行时被创建，当 JVM 退出时存活对象都会销毁，如果需要将对象及其状态持久化，就需要通过序列化来实现，将对象及其状态信息保存在字节数组中，在需要时再将这些字节数组反序列化为对象。对象序列化保存的是对象的状态，因此类中的静态变量不会被序列化，因为静态变量是类属性。

P12：反射

Field、Method、Constructor 分别用于描述类的字段、方法和构造器。这三个类都有一个 getName 方法返回字段、方法或构造器的名称。Field 类有一个 getType 方法用来返回描述字段类型的一个对象，这个对象的类型也是 Class。Method 和 Constructor 类有报告参数类型的方法，Method 类还有一个报告返回类型的方法。这三个类都有一个 getModifiers 方法，它返回一个整数，用不同的 0/1 位描述所使用的修饰符。

P13：注解

• `@Target`：用来约束注解作用的位置，值是 ElementType 枚举类实例，包括 METHOD 方法、VARIABLE 变量、TYPE 类/接口、PARAMETER 方法参数、CONSTRUCTORS 构造器和 LOACL_VARIABLE 局部变量等。

• `@Rentention`：用来约束注解的生命周期，值是 RetentionPolicy 枚举类实例，包括：SOURCE 源码、CLASS 字节码和 RUNTIME 运行时。

• `@Documented`：表明这个注解应该被 javadoc 工具记录。

• `@Inherited`：表面某个被标注的类型是被继承的。

P14：异常

Error 类描述了 Java 运行时系统的内部错误和资源耗尽错误，如果出现了这种错误，一般无能为力。

Exception 类又分为 RuntimeException 和其他异常，一般规则是由编程错误导致的异常属于 RuntimeException，如果程序本身没有问题，但由于像 IO 错误这类问题导致的异常属于其他异常。派生于 Error 和 RuntimeException 的异常属于非检查型异常，其余异常都属于检查型异常。

• ClassCastException，错误的强制类型转换。
• ArrayIndexOutOfBoundsException，数组访问越界。
• NullPointerException，空指针异常。

• FileNotFoundException，试图打开不存在的文件。
• ClassNotFoundException，试图根据指定字符串查找 Class 对象，而这个类并不存在。
• IOException，试图超越文件末尾继续读取数据。

P15：泛型

E（Element） 在集合中使用，表示在集合中存放的元素。
T（Type） 表示类，包括基本的类以及自定义类。
K（Key） 表示键，例如 Map 集合中的 Key。
V（Value） 表示值，例如 Map 集合中的 Value。
N（Number） 表示数值类型。

P16：Java 8 新特性

lambda 表达式：lambda 表达式允许把函数作为一个方法的参数传递到方法中，主要用来简化匿名内部类的代码。

Optional 类：用来处理空指针异常，提高代码可读性。

Stream 类：把函数式编程风格引入 Java 语言，提供了很多功能，可以使代码更加简洁。方法包括`forEach()` 遍历、`count()` 统计个数、`filter()` 按条件过滤、`limit()` 取前 n 个元素、`skip()` 跳过前 n 个元素、`map()` 映射加工、`concat()` 合并stream流等。

JavaScript：Java 8 提供了一个新的 Nashorn JavaScript 引擎，它允许我们在 JVM上运行特定的 JavaScript 应用。

P17：Java IO

IO 模型 对应的 Java 版本
BIO（同步阻塞 IO） 1.4 之前
NIO（同步非阻塞 IO） 1.4
AIO（异步非阻塞 IO） 1.7

• 同步 IO 是用户线程发起 I/O 请求后需要等待或者轮询内核 I/O 操作完成后才能继续执行。

• 异步 IO 是用户线程发起 I/O 请求后仍可以继续执行，当内核 I/O 操作完成后会通知用户线程，或者调用用户线程注册的回调函数。

• 阻塞 IO 是指 I/O 操作需要彻底完成后才能返回用户空间 。

• 非阻塞 IO 是指 I/O 操作被调用后立即返回一个状态值，无需等 I/O 操作彻底完成。

BIO：

NIO：

• Selector

选择器或多路复用器，主要作用是轮询检查多个 Channel 的状态，判断 Channel 注册的事件是否发生，即判断 Channel 是否处于可读或可写状态。在使用之前需要将 Channel 注册到 Selector 上，注册之后会得到一个 SelectionKey，通过 SelectionKey 可以获取 Channel 和 Selector 的相关信息。

• Channel

双向通道，替换了 IO 中的 Stream，不能直接访问数据，要通过 Buffer 来读写数据，也可以和其他 Channel 交互。

分类：FileChannel 处理文件、DatagramChannel 处理 UDP 数据、SocketChannel 处理 TCP 数据，用作客户端、ServerSocketChannel 处理 TCP 数据，用作服务器端。

• Buffer

缓冲区，本质是一块可读写数据的内存，这块内存被包装成 NIO 的 Buffer 对象，用来简化数据的读写。Buffer 的三个重要属性：position 表示下一次读写数据的位置，limit 表示本次读写的极限位置，capacity 表示最大容量。

• `flip()` 将写转为读，底层实现原理是把 position 置 0，并把 limit 设为当前的 position 值。
• 通过 `clear()` 将读转为写模式（用于读完全部数据的情况，把 position 置 0，limit 设为 capacity）。
• 通过 `compact()` 将读转为写模式（用于没有读完全部数据，存在未读数据的情况，让 position 指向未读数据的下一个）。
• 通道的方向和 Buffer 的方向是相反的，读取数据相当于向 Buffer 写入，写出数据相当于从 Buffer 读取。

使用步骤：向 Buffer 写入数据，调用 flip 方法将 Buffer 从写模式切换为读模式，从 Buffer 中读取数据，调用 clear 或 compact 方法来清空 Buffer。

AIO：

AsynchronousServerSocketChannel 异步服务器端通道，通过静态方法 `open()` 获取实例，通过 accept 方法获取客户端连接通道。

AsynchronousSocketChannel 异步客户端通道，通过静态方法 `open()` 获取实例，过 connect 方法连接服务器通道。

AsynchronousChannelGroup 异步通道分组管理器，它可以实现资源共享。创建时需要传入一个ExecutorService，也就是绑定一个线程池，该线程池负责两个任务：处理 IO 事件和触发 CompletionHandler 回调接口。

相关热帖

• 回复(9) 发表于 2020-05-15 12:16:06
• 回复(6) 发表于 2020-06-13 20:44:52
• 回复(1) 发表于 2020-07-01 00:21:29
• 回复(1) 发表于 2020-07-03 11:04:55

笔经面经近期热帖

• 回复(10) 发表于 今天 13:29:37
• 回复(9) 发表于 今天 16:49:25
• 回复(8) 发表于 今天 20:44:12
• 回复(2) 发表于 今天 20:56:18
• 回复(9) 发表于 今天 14:23:53
• 回复(19) 发表于 今天 09:09:59
• 回复(10) 发表于 今天 15:11:22
• 回复(19) 发表于 今天 10:22:00
• 回复(12) 发表于 今天 18:13:51
• 回复(5) 发表于 今天 17:42:36

近期精华帖

• 回复(46) 发表于 2020-07-15 21:28:01
• 回复(39) 发表于 2020-07-27 08:49:52
• 回复(17) 发表于 2020-07-28 00:38:24
• 回复(0) 发表于 2020-07-21 15:29:30
• 回复(17) 发表于 2020-07-25 23:24:22

热门推荐

• 扫描二维码，关注牛客网

• 下载牛客APP，随时随地刷题