Java面经(2.0)

Java基础:

1.final,finally,finallize的区别

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.==和equals的区别

在这里插入图片描述
在这里插入图片描述

3.两个对象的hashCode()相同,则equals()也一定为true吗?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.&和&&, ||和|的区别

在这里插入图片描述
在这里插入图片描述

5.Java中的参数传递时是传值还是传引用

在这里插入图片描述

6.什么是Java的序列化,如何实现序列号

在这里插入图片描述
在这里插入图片描述

7.八种基本数据类型的大小,以及他们的封装类

在这里插入图片描述

9、instanceof 关键字的作用

  1. 判断对象类型instanceof 可以用来检查一个对象是否属于某个类的实例或实现了某个接口。这对于在运行时根据对象的类型来执行不同的操作非常有用。

    if (object instanceof MyClass) {
        // 对象是MyClass的实例
    } else if (object instanceof MyInterface) {
        // 对象实现了MyInterface接口
    } else {
        // 对象不是指定类型
    }
    
  2. 类型转换安全:在进行强制类型转换之前,通常可以使用 instanceof 进行类型检查,以确保转换是安全的,避免发生 ClassCastException 异常。

    if (object instanceof MyClass) {
        MyClass myObject = (MyClass) object;
        // 安全的类型转换
    } else {
        // 类型不匹配,不进行转换
    }
    
  3. 多态性检查instanceof 在多态性(Polymorphism)的场景中也很有用,可以检查一个父类引用是否指向子类的对象。

    ParentClass parent = new ChildClass();
    if (parent instanceof ChildClass) {
        // parent引用指向ChildClass的对象
    } else {
        // parent引用不指向ChildClass的对象
    }
    

10、Java自动装箱与拆箱

  1. 自动装箱(Autoboxing)

    自动装箱是指将基本数据类型(如int、double等)的值自动包装到对应的包装类型(如Integer、Double等)中,而不需要显式地创建包装类型对象。编译器会自动完成这个工作。

    示例:

    int num = 42;
    Integer numObj = num; // 自动装箱,等效于 Integer numObj = Integer.valueOf(num);
    

    自动装箱让我们可以像操作对象一样操作基本数据类型,这在某些情况下非常方便。

  2. 自动拆箱(Unboxing)

    自动拆箱是指将包装类型对象自动转换为对应的基本数据类型的值,而不需要显式调用包装类型的方法进行转换。编译器会自动完成这个工作。

    示例:

    Integer numObj = 42;
    int num = numObj; // 自动拆箱,等效于 int num = numObj.intValue();
    

    自动拆箱让我们可以像操作基本数据类型一样操作包装类型对象,也非常方便。

11、 重载和重写的区别

重载(Overloading)

  1. 定义:方法重载指的是在同一个类中定义多个方法,具有相同的名称但具有不同的参数列表(参数类型、参数个数、参数顺序)。

  2. 关系:重载方法之间的关系是在同一个类中的方法之间,方法名相同但参数不同。

  3. 返回值:重载方法可以具有相同或不同的返回值类型。

  4. 编译时决定:重载是在编译时进行决定的,根据方法调用时传递的参数来选择合适的方法。这称为编译时多态或静态多态。

  5. 示例

    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
    

重写(Overriding)

  1. 定义:方法重写指的是子类定义了一个与其父类中的方法具有相同名称、参数列表和返回类型的方法,以覆盖父类的方法实现。

  2. 关系:重写方法之间的关系是子类和父类之间的关系,子类继承了父类的方法,并重新定义了该方法。

  3. 返回值:重写方法必须具有相同的返回值类型或其子类型(协变返回类型)。

  4. 运行时决定:重写是在运行时决定的,根据对象的实际类型来选择调用哪个方法。这称为运行时多态或动态多态。

  5. 示例

    class Animal {
        void makeSound() {
            System.out.println("Animal makes a sound");
        }
    }
    
    class Dog extends Animal {
        @Override
        void makeSound() {
            System.out.println("Dog barks");
        }
    }
    

总结:

  • 重载是在同一个类中,方法名相同但参数不同,编译时决定。

  • 重写是子类覆盖父类的方法,方法名、参数列表和返回类型相同,运行时决定。

  • 重写是实现多态性的一种方式,用于实现运行时多态。

  • 重载用于实现编译时多态,允许同一个方法名在不同的上下文中有不同的行为。

12、 Hashcode的作用

hashCode方法可以这样理解:

它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合 要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上 已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地 址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

hashCode() 是Java中的一个方法,它用于计算对象的哈希码(散列码)。哈希码是一个整数值,用于标识对象的唯一性。hashCode() 方法的主要作用如下:

  1. 在哈希表中查找对象:哈希表是一种常见的数据结构,用于快速查找和存储对象。哈希表使用对象的哈希码作为索引来存储和检索对象。每个不同的对象应该具有不同的哈希码,以确保在哈希表中可以正确地定位对象。

  2. 用于集合数据结构:在集合类(如HashSetHashMapHashtable等)中,哈希码用于确定对象的存储位置和查找速度。例如,HashSet使用对象的哈希码来检查是否已存在某个元素,从而确保元素的唯一性。

  3. 性能优化:在某些情况下,计算对象的哈希码可以提高性能。例如,当大量对象存储在哈希表中时,使用哈希码来快速定位对象可以加速查找和插入操作。

  4. equals方法配合使用:根据Java规范,如果两个对象的equals方法返回true,那么它们的哈希码必须相等。因此,通常需要在重写equals方法时也同时重写hashCode方法,以确保对象在哈希表中的一致性行为。

13、String、String StringBuffer 和 StringBuilder 的区别是什 么?

StringStringBufferStringBuilder都用于处理字符串,但它们在性能、可变性和线程安全性等方面有不同的特点。以下是它们之间的主要区别:

1. String(字符串常量):

  • String 是不可变的(Immutable)。一旦创建了一个String对象,它的值不能被修改。

  • 由于不可变性,对字符串的操作(如拼接、替换)会创建新的字符串对象,而不会修改原始字符串。

  • 因为字符串是不可变的,所以它们是线程安全的,可以在多线程环境中共享,而不需要额外的同步。

  • 适用于存储不会变化的字符串,如常量字符串和配置信息。

String str = "Hello";
str = str + " World"; // 创建新的字符串对象

2. StringBuffer(线程安全的可变字符串):

  • StringBuffer 是可变的,允许在字符串中进行插入、追加、删除等修改操作。

  • 由于可变性,StringBuffer 的性能通常比 String 好,特别是在需要大量字符串拼接操作时。

  • StringBuffer 是线程安全的,它的方法都是同步的,因此可以在多线程环境中使用,但性能开销较大。

  • 适用于多线程环境下需要动态修改字符串的情况。

StringBuffer buffer = new StringBuffer("Hello");
buffer.append(" World"); // 修改原始字符串

3. StringBuilder(非线程安全的可变字符串):

  • StringBuilder 也是可变的,允许在字符串中进行插入、追加、删除等修改操作。

  • StringBuilder 不是线程安全的,因此在多线程环境中使用时需要自行处理同步问题,但性能通常比 StringBuffer 更好,因为没有同步开销。

  • 适用于单线程环境下需要动态修改字符串的情况。

StringBuilder builder = new StringBuilder("Hello");
builder.append(" World"); // 修改原始字符串

总结:

  • 如果需要在多线程环境中处理字符串,建议使用 StringStringBuffer

  • 如果不需要多线程安全性,并且需要频繁地修改字符串,建议使用 StringBuilder,因为它通常具有更好的性能。

  • 如果字符串不需要修改,或者只需要少量修改,使用 String 是一个更安全和合适的选择,因为它是不可变的。

14、ArrayList和linkedList的区别

  1. 内部数据结构:

    • ArrayList内部使用一个数组来存储元素,当元素超出数组容量时,需要进行扩容操作,这可能会导致性能损耗。

    • LinkedList内部使用一个双向链表来存储元素,每个元素都包含对前一个和后一个元素的引用。插入和删除元素时,LinkedList通常比ArrayList更高效,因为它不需要移动大量元素。

  2. 访问元素的效率:

    • ArrayList通过索引可以快速访问元素,时间复杂度为O(1)。

    • LinkedList在访问元素时需要从头或尾部开始遍历链表,时间复杂度为O(n),其中n是链表的长度。

  3. 插入和删除元素的效率:

    • ArrayList在中间插入或删除元素时,需要移动后续元素,时间复杂度为O(n)。

    • LinkedList在中间插入或删除元素时,只需要修改相邻元素的引用,时间复杂度为O(1)。

  4. 内存消耗:

    • ArrayList通常比LinkedList占用更少的内存,因为它只需要一个数组,而LinkedList需要存储额外的链表节点对象。
  5. 迭代效率:

    • ArrayList在迭代(遍历)元素时比较高效,因为可以利用索引进行快速访问。

    • LinkedList在迭代元素时由于需要从头或尾部遍历链表,效率较低。

15、 HashMap和HashTable的区别

  1. 线程安全性:

    • HashMap是非线程安全的,不同线程可以同时访问和修改HashMap,如果不采取额外的同步措施,可能会导致并发问题。

    • HashTable是线程安全的,它的方法都是同步的,多个线程可以安全地访问和修改HashTable。然而,这种同步会导致在多线程环境下的性能损失。

  2. null键和null值:

    • HashMap允许键和值都为null,即可以存储键或值为null的键值对。

    • HashTable不允许键或值为null,如果尝试存储null键或值,会抛出NullPointerException。

  3. 继承关系:

    • HashMap是HashMap类的一部分,属于Java集合框架的一部分,位于java.util包中。

    • HashTable是Hashtable类的一部分,它在较早的Java版本中引入,已经被Java集合框架中的更现代的替代品取代。

  4. 性能:

    • 由于HashTable的方法都是同步的,它在单线程环境下的性能可能比HashMap差。

    • HashMap在多线程环境下可能需要额外的同步措施来确保线程安全,但在单线程环境下通常更快。

16、 Collection包结构,与Collections的区别

Java中有两个与集合相关的概念:Collection包(或Java集合框架)和Collections工具类,它们之间存在重要的区别。

  1. Collection包(Java集合框架):

    • Collection包是Java中用于存储和操作对象集合的框架。

    • 它包括了一系列接口和类,用于表示和操作不同类型的集合数据结构,如列表、集合、队列和映射。

    • 一些核心接口包括CollectionListSetMap等。

    • 这个包提供了一系列具体的集合类,如ArrayListHashSetLinkedListHashMap等。

    • 开发者可以根据需求选择合适的集合类来存储和处理数据。

  2. Collections工具类:

    • Collections是Java.util包中的一个工具类,提供了一些静态方法,用于操作和处理集合对象。

    • 这些静态方法包括排序、查找、同步、反转等操作,用于对集合进行各种通用的操作。

    • Collections工具类的方法是静态的,可以在不创建新集合的情况下对现有集合进行操作。

    • 例如,Collections.sort(list)方法用于对列表进行排序,Collections.shuffle(list)用于打乱列表中的元素顺序。

总结区别:

  • Collection包是Java集合框架的一部分,用于定义集合的接口和类,而Collections是一个工具类,用于对集合执行各种操作。

  • Collection包关注的是定义和表示不同类型的集合数据结构,而Collections关注的是操作已经存在的集合对象。

  • 开发者可以使用Collection包中的类来创建不同类型的集合,然后使用Collections工具类来操作这些集合,例如排序、反转、查找等。

17、 Java的四种引用,强弱软虚

  1. 强引用(Strong Reference):

    • 强引用是最常见的引用类型,它通过普通的对象引用来引用对象。

    • 只有当没有任何强引用指向一个对象时,该对象才会被垃圾回收器回收。

    • 强引用通常用于保持对象的存活状态,因此只有在不再需要对象时才应该手动释放引用。

  2. 软引用(Soft Reference):

    • 软引用用于描述那些有用但不是必需的对象。

    • 当系统内存不足时,垃圾回收器可能会回收软引用对象,以释放内存。

    • 可以使用SoftReference类来创建软引用,通常用于实现缓存机制。

  3. 弱引用(Weak Reference):

    • 弱引用用于描述那些非必需的对象,当没有强引用指向它们时,它们可能会被垃圾回收器回收。

    • 弱引用的生命周期更短暂,比软引用更容易被回收。

    • 可以使用WeakReference类来创建弱引用,通常用于解决一些特定问题,如避免内存泄漏。

  4. 虚引用(Phantom Reference):

    • 虚引用是最弱的引用类型,几乎没有直接的访问权限。

    • 虚引用主要用于跟踪对象被垃圾回收的情况。当一个对象被垃圾回收器回收时,与之关联的虚引用会被添加到一个引用队列中。

    • 可以使用PhantomReference类来创建虚引用,通常用于一些高级的内存管理和资源管理情景。

18、 泛型常用特点

  1. 类型安全(Type Safety):

    • 泛型允许在编译时检查类型一致性,从而避免了在运行时出现类型错误。

    • 编译器能够确保只有兼容的数据类型可以被放入泛型容器中,提高了代码的稳定性和可靠性。

  2. 代码重用性(Code Reusability):

    • 泛型允许你编写通用的代码,可以应用于多种数据类型,从而减少了代码的重复编写。

    • 你可以创建通用的数据结构和算法,如集合类或排序算法,而无需为每种数据类型创建一个新的版本。

  3. 避免类型强制转换(Avoiding Type Casting):

    • 泛型消除了大量的类型强制转换,使代码更加清晰和简洁。

    • 在使用泛型容器时,不再需要使用像(Type)这样的强制类型转换操作。

  4. 增强代码可读性(Improved Code Readability):

    • 泛型使代码更容易阅读和理解,因为它们可以提供有意义的类型信息。

    • 使用泛型,你可以明确地指定数据类型,使代码更加自解释。

  5. 集合框架中的应用(Application in Collections Framework):

    • Java集合框架(如List、Set、Map等)中广泛使用了泛型。

    • 泛型允许你创建类型安全的集合,从而更容易管理和操作集合中的元素。

  6. 自定义泛型类和方法(Custom Generic Classes and Methods):

    • 你可以创建自己的泛型类和方法,以满足特定的需求。

    • 这使得你能够设计出更加灵活、通用的组件和工具。

  7. 编译时类型检查(Compile-Time Type Checking):

    • 泛型允许编译器在编译时检查类型错误,而不是在运行时出现错误。

    • 这有助于减少在程序运行期间出现的潜在错误。

19.内部类有哪几种形式

Java 内部类有四种主要形式,分别是:

  1. 成员内部类(Member Inner Class):成员内部类是定义在另一个类内部的类,它是外部类的成员之一,可以访问外部类的所有成员,包括私有成员。成员内部类可以有访问修饰符,例如public、private、protected等。
public class Outer {
    private int outerField;

    public class Inner {
        public void innerMethod() {
            outerField = 10; // 可以访问外部类的成员变量
        }
    }
}
  1. 静态内部类(Static Nested Class):静态内部类是定义在另一个类内部的类,但它被声明为静态。静态内部类不持有对外部类实例的引用,因此可以在没有外部类实例的情况下被实例化。静态内部类可以访问外部类的静态成员,但不能访问非静态的外部类成员。
public class Outer {
    private static int outerStaticField;

    public static class Nested {
        public void nestedMethod() {
            outerStaticField = 20; // 可以访问外部类的静态成员
        }
    }
}
  1. 局部内部类(Local Inner Class):局部内部类是定义在方法内部的类,它的作用域仅限于声明它的方法内部。局部内部类可以访问外部类的成员,但通常不会持有对外部类实例的引用。
public class Outer {
    public void methodWithLocalInnerClass() {
        class LocalInner {
            public void localMethod() {
                // 内部类方法的实现
            }
        }
        LocalInner localInner = new LocalInner();
        localInner.localMethod();
    }
}
  1. 匿名内部类(Anonymous Inner Class):匿名内部类是一种没有显式类名的内部类,通常用于创建一个只需使用一次的类。它通常在类的实例化过程中直接定义和使用,常见于事件处理、线程创建等场景。
public class Outer {
    public void methodWithAnonymousInnerClass() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 匿名内部类的实现
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

这些不同形式的内部类各自适用于不同的场景,可以根据需求选择合适的内部类形式来实现代码逻辑。

20.重载和重写

重载 总结:

1.重载Overload是一个类中多态性的一种表现

2.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序)

3.重载的时候,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准

重写 总结:

1.发生在父类与子类之间

2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同

3.访问修饰符的限制一定要大于被重写方法的访问修饰(public>protected>default>private)

4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常

21.接口和抽象类区别

接口(Interface)和抽象类(Abstract Class)都是在面向对象编程中用来定义抽象类型的机制,但它们之间有一些关键的区别:

  1. 抽象类可以包含成员变量,接口不能

    • 抽象类可以包含实例变量,而接口只能包含常量(静态常量)。

    • 在抽象类中,可以定义实例变量并提供默认值,而接口中的变量总是隐式为 public static final,并且必须在声明时初始化。

  2. 类可以继承一个抽象类,但可以实现多个接口

    • 一个类可以继承自一个抽象类,这意味着它只能继承一个类的结构和行为。

    • 一个类可以实现多个接口,这允许它获得多个接口的行为,从而实现多继承的效果。

  3. 抽象类可以有构造方法,接口不能

    • 抽象类可以有构造方法,用于初始化实例变量和执行其他初始化操作。

    • 接口不能有构造方法,因为接口不能被实例化,它只能由类来实现。

  4. 抽象类可以包含非抽象方法,接口只能包含抽象方法

    • 抽象类可以包含非抽象方法,这些方法可以有默认实现,子类可以选择性地覆盖它们。

    • 接口只能包含抽象方法,所有的方法都是抽象的,子类必须实现接口中定义的所有方法。

  5. 抽象类用于共享代码和状态,接口用于定义契约

    • 抽象类通常用于表示一组相关的类的通用行为,可以包含共享的代码和状态。

    • 接口通常用于定义契约,规定实现类必须提供的方法,而不关心实现细节。

  6. 多态性体现方式不同

    • 通过继承抽象类,子类可以实现父类的多态性,而且可以继续扩展父类的功能。

    • 通过实现接口,类可以实现多个接口的多态性,但接口之间没有继承关系,只有类之间才有继承关系。

根据具体的需求和设计目标,你可以选择使用抽象类、接口或两者的组合来定义抽象类型。通常,如果你需要多继承或希望定义一组相关的方法签名,可以使用接口;如果你想要提供一些共享的实现代码,可以使用抽象类。有时候,你可能会将抽象类和接口一起使用,以充分发挥它们的优势。

22.Java中常见的数据结构

Java 提供了丰富的数据结构来处理不同类型的数据和解决各种编程问题。以下是一些 Java 中常见的数据结构:

  1. 数组(Array)

    • 数组是一组相同类型的数据元素的集合,通过索引访问。

    • 数组的大小在创建时固定,不能动态改变。

  2. 列表(List)

    • 列表是一个有序的集合,允许重复元素。

    • Java 中常见的列表包括 ArrayListLinkedListVector

  3. 集合(Set)

    • 集合是一种不允许重复元素的数据结构。

    • Java 中常见的集合包括 HashSetLinkedHashSetTreeSet 等。

  4. 映射(Map)

    • 映射是一种键值对的数据结构,用于存储键值对的关联关系。

    • Java 中常见的映射包括 HashMapLinkedHashMapTreeMapHashtable 等。

  5. 队列(Queue)

    • 队列是一种先进先出(FIFO)的数据结构,用于存储元素并支持在一端插入元素,在另一端移除元素。

    • Java 中常见的队列包括 LinkedListPriorityQueue 等。

  6. 栈(Stack)

    • 栈是一种后进先出(LIFO)的数据结构,用于存储元素并支持在顶部插入和删除元素。

    • Java 中可以使用 Deque 接口来实现栈,例如 LinkedList

  7. 链表(Linked List)

    • 链表是一种数据元素的集合,元素通过指针相互连接,可以是单向链表或双向链表。

    • Java 中常见的链表包括 LinkedList

  8. 堆(Heap)

    • 堆是一种特殊的树状数据结构,常用于实现优先队列。

    • Java 中的 PriorityQueue 是基于堆实现的。

  9. 树(Tree)

    • 树是一种层次结构,常用于搜索和排序。

    • Java 中常见的树包括二叉树、AVL 树、红黑树等。

  10. 图(Graph)

    • 图是一种包含节点和边的数据结构,用于表示各种关系。

    • Java 中通常需要自己实现图数据结构,或者使用图库。

  11. 哈希表(Hash Table)

    • 哈希表是一种使用哈希函数来映射键到值的数据结构,用于快速查找。

    • Java 中的 HashMapHashtable 是常见的哈希表实现。

  12. 位集合(Bit Set)

    • 位集合是一种用于存储位值(0 或 1)的数据结构,通常用于位操作。

    • Java 中的 BitSet 类用于处理位集合。

  13. 队列双端队列(Deque)

    • 双端队列是一种具有两端插入和删除操作的数据结构,可以在前端和后端执行插入和删除操作。

    • Java 中的 Deque 接口支持双端队列。

这些数据结构在 Java 编程中经常被使用,根据具体的问题和需求选择合适的数据结构非常重要。不同的数据结构适用于不同的场景,可以提高代码的效率和可读性。

JVM:

1.JVM的基本原理:

在这里插入图片描述

2.类的加载过程

在Java中,类的加载过程是Java虚拟机(JVM)将类的字节码文件加载到内存中,以便在程序中被使用的过程。类加载是Java程序运行的一个重要步骤,它包括以下几个阶段:

  1. 加载(Loading)

    • 类加载的第一个阶段是加载,它负责查找和加载类的字节码文件。

    • 类加载器根据类的全限定名(包括包路径)来查找类文件,并将类的字节码加载到内存中。

    • 加载阶段不会执行类的静态初始化代码。

  2. 链接(Linking)
    类加载的链接阶段包括以下三个步骤:

    a. 验证(Verification)

    • 在验证阶段,虚拟机会验证类的字节码文件是否合法,以确保它不会导致虚拟机崩溃或产生安全问题。

    • 验证包括检查字节码的结构、类型检查等。

    b. 准备(Preparation)

    • 准备阶段负责为类的静态变量分配内存并设置初始值,这些变量被存储在类的静态存储区域中。

    • 这些静态变量的初始化值是在类加载时确定的,通常是默认值(0、false、null等)。

    c. 解析(Resolution)

    • 解析阶段是可选的,它用于将符号引用转换为直接引用,以便于在运行时访问类、方法和字段。

    • 例如,将类或接口的全限定名转换为直接引用,以确定其位置。

  3. 初始化(Initialization)

    • 初始化阶段是类加载的最后一个阶段,它负责执行类的静态初始化代码块和静态变量初始化。

    • 静态初始化代码块和静态变量初始化会按照在类中的顺序执行。

    • 初始化阶段只会执行一次,并且是在类被首次主动使用时触发的。

类加载是延迟加载的,只有在程序需要使用某个类时才会触发它的加载过程。类加载器负责加载类,可以是系统类加载器、扩展类加载器或自定义类加载器。如果类加载过程中发生错误(如找不到类文件、字节码文件格式不正确等),将抛出类加载异常(ClassNotFoundException)或其他相关异常。

总结:

  • 类加载过程包括加载、链接和初始化阶段。

  • 加载阶段将类的字节码加载到内存中。

  • 链接阶段包括验证、准备和解析。

  • 初始化阶段执行类的静态初始化代码块和静态变量初始化,只会执行一次。

3.内部类有哪几种形式

在Java中,内部类有以下几种形式:

  1. 成员内部类(Member Inner Class):成员内部类是定义在另一个类内部的类,它是外部类的成员之一,可以访问外部类的所有成员,包括私有成员。成员内部类可以有访问修饰符,例如public、private、protected等。

  2. 静态内部类(Static Nested Class):静态内部类是定义在另一个类内部的类,但它被声明为静态。静态内部类不持有对外部类实例的引用,因此可以在没有外部类实例的情况下被实例化。静态内部类可以访问外部类的静态成员,但不能访问非静态的外部类成员。

  3. 局部内部类(Local Inner Class):局部内部类是定义在方法内部的类,它的作用域仅限于声明它的方法内部。局部内部类可以访问外部类的成员,但通常不会持有对外部类实例的引用。

  4. 匿名内部类(Anonymous Inner Class):匿名内部类是一种没有显式类名的内部类,通常用于创建一个只需使用一次的类。它通常在类的实例化过程中直接定义和使用,常见于事件处理、线程创建等场景。

这些是Java中内部类的主要形式,它们各自具有不同的用途和特性。在面试中,您可以进一步探讨每种内部类的用法、优缺点和示例。如果您有任何关于内部类的具体问题或需要更深入的讨论,请随时提问。

4.JVM中类加载器的种类以及核心功能

引导类加载器:引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如t.jar、charsets.jar等;

扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包;

应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类;

自定义加载器:负责加载用户自定义路径下的类包。

5.加载机制-双亲委派模式

在这里插入图片描述

双亲委派模式,即加载器加载类时先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器.父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载.

优点:

  1. 避免类的重复加载

  2. 避免Java的核心API被篡改

6.GC Root

GC Root(垃圾回收根)是在垃圾收集(Garbage Collection)过程中,用于确定哪些对象是存活的、哪些对象可以被回收的关键元素。GC Root 是从它们开始追踪的根对象,垃圾收集器从这些根对象出发,递归地遍历对象图,标记存活对象,将未标记的对象标记为可回收。以下是常见的 GC Root 类型:

  1. 虚拟机栈中引用的对象

    • 当一个方法执行时,它会创建一个栈帧,栈帧中包含局部变量表,局部变量表中包含了对对象的引用。

    • 这些引用被认为是 GC Root,因为它们直接与当前方法的执行状态相关联。

  2. 方法区中静态变量引用的对象

    • 静态变量存储在方法区(永久代或元空间),如果一个静态变量引用了一个对象,那么这个对象也被视为 GC Root。
  3. 本地方法栈中 JNI 引用的对象

    • 如果通过 JNI(Java Native Interface)调用本地方法,本地方法可以创建对象,并持有对这些对象的引用。

    • 这些对象也被视为 GC Root。

  4. 活动线程(Active Threads)

    • 正在执行的线程通常被视为 GC Root,因为它们的栈帧中包含了局部变量表,其中可能包含对象引用。
  5. JNI 全局引用(JNI Global References)

    • JNI 全局引用是一种特殊的引用类型,它们被认为是 GC Root。

    • JNI 全局引用可以被持久地存储在本地方法中,因此需要特别注意避免内存泄漏。

  6. 系统类加载器

    • 类加载器通常也被视为 GC Root,因为它们加载了类和静态变量。

GC Root 对象是垃圾收集器确定对象存活性的起始点。只有从 GC Root 开始的对象引用链被认为是存活的,其他对象会被标记为可回收。这有助于垃圾回收器识别和回收不再使用的对象,以释放内存并确保程序的性能和稳定性。

设计模式

1.常见的设计模式:

设计模式是在软件工程中解决常见问题的可重用解决方案。它们是经过广泛测试和验证的最佳实践,可以帮助开发人员更容易地设计可维护、可扩展和可重用的代码。以下是一些常见的设计模式:

  1. 单例模式(Singleton Pattern)

    • 保证一个类只有一个实例,并提供全局访问点。
  2. 工厂模式(Factory Pattern)

    • 用于创建对象的模式,通过工厂方法或抽象工厂来创建对象。
  3. 抽象工厂模式(Abstract Factory Pattern)

    • 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
  4. 建造者模式(Builder Pattern)

    • 用于创建一个复杂对象,将对象的构建过程和其表示分离。
  5. 原型模式(Prototype Pattern)

    • 通过复制现有对象来创建新对象的模式,通常用于创建成本较高的对象。
  6. 适配器模式(Adapter Pattern)

    • 允许将一个接口转换为另一个接口,以满足客户端的需求。
  7. 装饰器模式(Decorator Pattern)

    • 用于动态地添加新功能或责任到对象,通过组合方式实现。
  8. 观察者模式(Observer Pattern)

    • 定义对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。
  9. 策略模式(Strategy Pattern)

    • 定义一系列算法,将每个算法封装起来,使它们可以相互替换,使算法的变化不会影响到使用算法的客户端。
  10. 命令模式(Command Pattern)

    • 将请求封装成一个对象,从而可以参数化客户端对象,队列或记录请求,以及支持可撤销的操作。
  11. 状态模式(State Pattern)

    • 允许对象在内部状态改变时改变其行为,使对象看起来好像改变了其类。
  12. 备忘录模式(Memento Pattern)

    • 用于捕获对象的内部状态,并在以后恢复该状态。
  13. 桥接模式(Bridge Pattern)

    • 将抽象部分与实现部分分离,使它们可以独立地变化。
  14. 迭代器模式(Iterator Pattern)

    • 提供一种顺序访问一个聚合对象中各个元素的方法,而不需要暴露该对象的内部表示。
  15. 组合模式(Composite Pattern)

    • 允许将对象组合成树形结构以表示"部分-整体"的层次结构。
  16. 享元模式(Flyweight Pattern)

    • 用于共享可能被多个对象频繁使用的相似对象,以减少内存占用和提高性能。
  17. 访问者模式(Visitor Pattern)

    • 允许在不改变对象结构的前提下定义对象对元素的新操作。

这些设计模式可以帮助开发人员解决各种不同类型的问题,提高代码的可维护性、可读性和可重用性。不同的设计模式适用于不同的情况,开发人员可以根据实际需求选择合适的设计模式来解决问题。

2.代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,它允许你提供一个代理或占位符,以控制对另一个对象的访问。代理模式常用于需要对对象的访问进行控制、延迟加载、权限验证、记录日志等场景。代理模式通过引入一个代理对象来实现这些功能,而不直接访问目标对象。

代理模式通常涉及以下角色:

  1. 抽象主题(Subject)

    • 定义了目标对象和代理对象的共同接口,这使得代理对象可以替代目标对象。

    • 抽象主题可以是一个接口或抽象类,其中声明了目标对象的方法。

  2. 具体主题(Real Subject)

    • 具体主题是实际的目标对象,它实现了抽象主题接口定义的方法。

    • 具体主题是代理对象所代表的真实业务对象。

  3. 代理(Proxy)

    • 代理是一个包装了具体主题的对象,它与具体主题实现了相同的接口,以便能够代替具体主题。

    • 代理控制了对具体主题的访问,可以在访问前后执行一些额外的逻辑,例如权限检查、延迟加载等。

    • 代理对象可以在需要时创建和销毁具体主题对象,以实现懒加载。

代理模式的一些常见应用包括:

  • 远程代理(Remote Proxy):控制对远程对象的访问,例如远程服务器上的对象。

  • 虚拟代理(Virtual Proxy):用于控制访问开销较大的对象,例如大文件的加载。

  • 保护代理(Protection Proxy):用于实施访问控制,例如权限验证。

  • 缓存代理(Cache Proxy):用于缓存对象,以提高性能。

  • 智能引用代理(Smart Reference Proxy):用于实现一些额外的逻辑,例如引用计数。

代理模式的优点包括:

  • 控制访问:代理可以控制对目标对象的访问,可以实施访问控制和权限验证。

  • 延迟加载:代理可以延迟加载目标对象,提高了性能,特别是在资源消耗较大的情况下。

  • 额外功能:代理可以在访问前后执行额外的逻辑,例如记录日志、缓存、验证等。

然而,代理模式也有一些缺点,如引入了额外的代码复杂性,并且可能导致性能损失,特别是在频繁创建和销毁代理对象时。因此,代理模式需要根据具体的应用场景谨慎使用。

多线程&并发:

1.创建线程的方式

在Java中,有多种方式可以创建线程。以下是常见的几种创建线程的方式:

  1. 继承Thread类
  • 创建一个类,继承自Thread类。

  • 重写run()方法,在其中定义线程的执行逻辑。

  • 创建该类的实例,并调用start()方法启动线程。

class MyThread extends Thread {
  public void run() {
      // 线程的执行逻辑
  }
}

MyThread thread = new MyThread();
thread.start();
  1. 实现Runnable接口
  • 创建一个类,实现Runnable接口。

  • 实现run()方法,在其中定义线程的执行逻辑。

  • 创建Thread对象,将实现了Runnable接口的对象传递给Thread的构造函数。

  • 调用start()方法启动线程。

class MyRunnable implements Runnable {
  public void run() {
      // 线程的执行逻辑
  }
}

MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
  1. 使用匿名内部类
  • 可以通过匿名内部类的方式来创建线程,特别适合简单的线程任务。
Thread thread = new Thread(new Runnable() {
  public void run() {
      // 线程的执行逻辑
  }
});
thread.start();
  1. 使用Java 8的Lambda表达式
  • 在Java 8及以后的版本,可以使用Lambda表达式来创建线程。
Thread thread = new Thread(() -> {
  // 线程的执行逻辑
});
thread.start();
  1. 使用线程池
  • Java提供了Executor框架,可以使用线程池来管理和重用线程。这种方式更加高效和灵活。
ExecutorService executor = Executors.newFixedThreadPool(5); // 创建一个包含5个线程的线程池
executor.execute(() -> {
  // 线程的执行逻辑
});
executor.shutdown(); // 关闭线程池

不同的线程创建方式适用于不同的情况。通常,使用实现Runnable接口或使用Lambda表达式创建线程更加灵活,因为Java支持单继承,这种方式可以避免类继承的限制。使用线程池可以提高线程的管理和重用效率。选择合适的方式取决于您的具体需求。

2.线程池创建方式

在Java中,您可以使用java.util.concurrent包中的线程池来管理线程的创建和执行。线程池提供了一种重用线程的机制,可以降低线程创建和销毁的开销,并且可以限制并发线程的数量,以防止资源耗尽。以下是创建线程池的几种方式:

  1. 使用Executors工厂类创建线程池

Executors类提供了一些静态方法来创建不同类型的线程池。以下是一些常见的线程池创建方式:

  • newFixedThreadPool(int nThreads):创建一个固定大小的线程池,其中包含指定数量的线程。
ExecutorService executor = Executors.newFixedThreadPool(5);
  • newCachedThreadPool():创建一个可以根据需要自动调整大小的线程池。
ExecutorService executor = Executors.newCachedThreadPool();
  • newSingleThreadExecutor():创建一个只包含一个线程的线程池。
ExecutorService executor = Executors.newSingleThreadExecutor();
  • newScheduledThreadPool(int corePoolSize):创建一个定时执行任务的线程池。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
  1. 使用ThreadPoolExecutor类自定义线程池

如果需要更多的自定义选项,可以使用ThreadPoolExecutor类创建线程池,该类提供了更多的参数来配置线程池的行为,如核心线程数、最大线程数、线程存活时间等。

int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 60L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();

ExecutorService executor = new ThreadPoolExecutor(
  corePoolSize,
  maxPoolSize,
  keepAliveTime,
  unit,
  workQueue
);
  1. 使用Java 8的ForkJoinPool

如果您需要执行递归或分治任务,可以使用ForkJoinPool,它是Java 7引入的,并在Java 8中进一步改进。

ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();

一旦您创建了线程池,可以使用execute()方法将任务提交给线程池执行。在任务执行完毕后,可以通过调用shutdown()方法来关闭线程池。

请根据您的应用程序需求和性能要求选择适当的线程池类型和配置参数。线程池是管理并发的重要工具,可以帮助您更有效地利用计算资源。

3.线程池工作流程

线程池是一种用于管理和复用线程的机制,它能够提高多线程应用程序的性能和资源利用率。以下是线程池的工作流程:

  1. 线程池的创建
  • 首先,您需要创建一个线程池,可以使用Executors工厂类提供的静态方法来创建不同类型的线程池,或者使用ThreadPoolExecutor构造函数自定义线程池的配置。
  1. 任务提交
  • 当有任务需要执行时,应用程序将任务提交给线程池。

  • 任务可以是实现了Runnable接口的对象或者实现了Callable接口的对象,具体取决于线程池的类型和需求。

  1. 任务队列
  • 线程池通常会包含一个任务队列(也称为工作队列),用于存储等待执行的任务。

  • 当任务被提交到线程池时,它们首先进入任务队列排队等待执行。

  1. 线程管理
  • 线程池内部维护一组工作线程,这些线程可用于执行任务。

  • 线程池会根据配置中的核心线程数来启动一些初始线程,这些线程会在处理任务后保持活动状态,以减少线程的创建和销毁开销。

  1. 任务执行
  • 当有空闲线程可用时,线程池会从任务队列中取出一个任务,将其分配给一个空闲线程执行。

  • 执行任务的线程会调用任务对象的run()方法或call()方法,具体取决于任务是Runnable还是Callable

  1. 线程复用
  • 执行完任务后,线程并不会被销毁,而是返回线程池中,以供后续任务使用。

  • 这种线程的复用避免了频繁地创建和销毁线程,提高了性能。

  1. 线程池扩展
  • 如果任务的数量超过了核心线程数,并且任务队列已满,线程池可以根据配置中的最大线程数创建额外的线程来处理任务,以应对高负载情况。
  1. 任务完成
  • 当任务执行完毕后,线程池会返回执行结果(如果任务是Callable类型),并将线程标记为空闲状态,等待下一个任务。
  1. 线程池关闭
  • 当应用程序完成了所有任务时,或者在需要关闭线程池时,可以调用线程池的shutdown()方法来优雅地关闭线程池,停止所有线程。

线程池的工作流程允许有效地管理和控制多线程应用程序中的线程,确保了线程的复用和资源的高效利用。这有助于提高应用程序的性能和可维护性。在使用线程池时,需要根据应用程序的需求和性能考虑合适的线程池类型、核心线程数、最大线程数、任务队列类型等配置参数。

4.线程池拒绝策略

线程池的拒绝策略定义了在任务提交到线程池时,如果无法执行任务的处理方式。当线程池中的工作线程已满,并且任务队列也已满,而且不能创建更多的线程来执行任务时,拒绝策略就会生效。Java的ThreadPoolExecutor类支持不同的拒绝策略,可以根据需求进行配置。以下是常见的拒绝策略:

  1. AbortPolicy(默认策略)
  • AbortPolicy是默认的拒绝策略,它会抛出一个RejectedExecutionException异常,通知调用者任务无法执行。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
  corePoolSize,
  maxPoolSize,
  keepAliveTime,
  TimeUnit.SECONDS,
  workQueue,
  new ThreadPoolExecutor.AbortPolicy()
);
  1. CallerRunsPolicy
  • CallerRunsPolicy策略会让提交任务的线程自己执行该任务,这样可以保证任务一定会被执行,但会阻塞调用者线程。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
  corePoolSize,
  maxPoolSize,
  keepAliveTime,
  TimeUnit.SECONDS,
  workQueue,
  new ThreadPoolExecutor.CallerRunsPolicy()
);
  1. DiscardPolicy
  • DiscardPolicy策略会默默地丢弃无法处理的任务,不会抛出异常,也不会执行任务。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
  corePoolSize,
  maxPoolSize,
  keepAliveTime,
  TimeUnit.SECONDS,
  workQueue,
  new ThreadPoolExecutor.DiscardPolicy()
);
  1. DiscardOldestPolicy
  • DiscardOldestPolicy策略会丢弃队列中等待时间最长的任务,然后尝试重新提交当前任务。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
  corePoolSize,
  maxPoolSize,
  keepAliveTime,
  TimeUnit.SECONDS,
  workQueue,
  new ThreadPoolExecutor.DiscardOldestPolicy()
);
  1. 自定义拒绝策略
  • 您可以自定义拒绝策略,实现RejectedExecutionHandler接口,并实现rejectedExecution()方法来定义自己的拒绝行为。
class CustomRejectionPolicy implements RejectedExecutionHandler {
  public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
      // 自定义拒绝策略的处理逻辑
  }
}

ThreadPoolExecutor executor = new ThreadPoolExecutor(
  corePoolSize,
  maxPoolSize,
  keepAliveTime,
  TimeUnit.SECONDS,
  workQueue,
  new CustomRejectionPolicy()
);

选择合适的拒绝策略取决于您的应用程序需求和业务逻辑。例如,如果您希望任务一定会被执行,可以选择CallerRunsPolicy,但要注意可能会导致调用者线程阻塞。如果您希望忽略无法执行的任务,可以选择DiscardPolicy。自定义拒绝策略可以根据特定需求实现特定的拒绝逻辑。

计算机网络:

1.三次握手,四次挥手

TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输协议,它使用三次握手建立连接和四次挥手终止连接。下面分别解释三次握手和四次挥手的过程:

三次握手(Three-Way Handshake):

三次握手是用于建立TCP连接的过程,确保客户端和服务器之间的通信能够正常开始。它的步骤如下:

  1. 客户端向服务器发送连接请求(SYN)

    • 客户端首先向服务器发送一个TCP报文,其中设置了SYN标志位,表示请求建立连接。

    • 客户端选择一个随机的初始序列号(ISN)。

  2. 服务器确认连接请求(SYN-ACK)

    • 服务器收到客户端的连接请求后,如果同意建立连接,会发送一个带有SYN和ACK标志位的TCP报文作为回应。

    • 服务器也会选择一个随机的初始序列号。

  3. 客户端确认连接请求(ACK)

    • 客户端收到服务器的响应后,会发送一个带有ACK标志位的TCP报文,表示已收到服务器的确认。

    • 此时,TCP连接已建立,客户端和服务器可以开始进行数据传输。

四次挥手(Four-Way Handshake):

四次挥手是用于终止TCP连接的过程,确保客户端和服务器都知道连接已经结束。它的步骤如下:

  1. 客户端发起连接关闭(FIN)

    • 客户端完成数据传输后,希望终止连接,会发送一个带有FIN标志位的TCP报文,表示不再发送数据。
  2. 服务器确认关闭请求(ACK)

    • 服务器收到客户端的FIN后,会发送一个带有ACK标志位的TCP报文,表示已经接收到了关闭请求,但仍允许发送数据。
  3. 服务器发起连接关闭(FIN)

    • 服务器在完成数据传输后,也希望终止连接,会发送一个带有FIN标志位的TCP报文。
  4. 客户端确认关闭请求(ACK)

    • 客户端收到服务器的FIN后,会发送一个带有ACK标志位的TCP报文,表示已经接收到了服务器的关闭请求。

    • 此时,TCP连接被完全终止。

总结:

  • 三次握手用于建立TCP连接,确保双方都准备好进行通信。

  • 四次挥手用于终止TCP连接,确保双方都知道连接已经结束。在四次挥手过程中,关闭连接的一方需要等待一段时间(TIME_WAIT状态)以确保任何延迟的数据包都能到达目的地。

2.HTTP和HTTPS

HTTP(Hypertext Transfer Protocol)和HTTPS(HTTP Secure)是用于在网络上传输数据的两种不同的协议,它们之间主要区别在于安全性:

HTTP(Hypertext Transfer Protocol):

  • HTTP是一种用于传输超文本的协议,通常用于在Web浏览器和Web服务器之间传输HTML页面、图片、视频等数据。

  • HTTP是明文传输协议,意味着在传输过程中数据是未加密的,容易受到中间人攻击或窃听。

  • 因此,HTTP在处理敏感信息(如登录凭据、信用卡号等)时不安全,不适合用于保护隐私和敏感数据。

HTTPS(HTTP Secure):

  • HTTPS是HTTP的安全版本,通过加密通信来保护数据的机密性和完整性。

  • HTTPS使用TLS/SSL协议(Transport Layer Security/Secure Sockets Layer)来加密HTTP通信的内容,确保数据在传输过程中不被窃听或篡改。

  • 通常,网站通过在服务器上安装SSL证书来启用HTTPS,这会导致浏览器和服务器之间建立安全的通信通道。

  • HTTPS在保护用户隐私、在线支付、电子邮件等领域中广泛使用,提供了更高级别的安全性。

总结:

  • HTTP和HTTPS都是用于在Web上传输数据的协议,但HTTPS提供了更高级别的安全性,适用于需要保护隐私和敏感数据的场景。

  • 在使用HTTPS时,浏览器地址栏通常会显示一个锁定图标或网站名称的绿色文本,以指示连接是安全的。

  • 在互联网上,大多数敏感信息的传输都应使用HTTPS来确保安全性。

3.DNS

DNS(Domain Name System)是互联网中用于将人类可读的域名(例如,www.example.com)映射到计算机可识别的IP地址(例如,192.168.1.1)的分布式命名系统。DNS充当了互联网的"电话簿",它使我们可以通过友好的域名来访问网站、发送电子邮件等,而不必记住复杂的IP地址。

以下是DNS的主要组成部分和功能:

  1. 域名(Domain Name)

    • 域名是互联网上的唯一标识符,用于标识特定的资源或计算机。

    • 域名通常由多个部分组成,例如顶级域名(TLD,例如.com、.org、.net)、二级域名(例如example.com)和子域名(例如www.example.com)。

  2. DNS服务器(DNS Server)

    • DNS服务器存储了大量的域名和与之关联的IP地址信息。

    • DNS服务器按层次结构组织,从根域名服务器开始,然后是顶级域名服务器、权威域名服务器和本地域名服务器。

    • 根域名服务器位于DNS层次结构的顶部,它们包含全球顶级域名(如.com、.org)的信息。

  3. 解析(Resolution)

    • 当用户在浏览器中输入一个域名时,计算机首先将查询发送到本地DNS服务器。

    • 本地DNS服务器尝试查找域名对应的IP地址。如果找到,它将返回给用户计算机。

    • 如果本地DNS服务器不知道域名对应的IP地址,它会向更高级别的DNS服务器发出查询请求,依次进行递归查询,直到找到对应的IP地址或者确定域名不存在。

  4. 缓存(Caching)

    • DNS服务器通常会缓存已解析的域名和IP地址对,以提高查询性能。

    • 当多个用户查询相同的域名时,DNS服务器可以直接从缓存中返回结果,而不必进行递归查询。

  5. TTL(Time to Live)

    • TTL是DNS记录中的一个字段,表示该记录在缓存中的存活时间。

    • 一旦记录被缓存,它将在TTL到期之前不会被更新。

    • TTL的设置由域名所有者控制,通常用于控制DNS记录的更新频率。

  6. 反向DNS解析(Reverse DNS Lookup)

    • 除了将域名解析为IP地址,DNS还支持将IP地址解析为域名,这称为反向DNS解析。

    • 反向DNS解析通常用于验证IP地址的合法性和跟踪垃圾邮件等网络活动。

总之,DNS是互联网的关键组成部分,它使用户可以使用易记的域名来访问网络资源,而不必记住复杂的IP地址。DNS服务器将域名映射到IP地址,使互联网通信更加便捷和可访问。

操作系统:

MySQL数据库:

1、数据库的三范式是什么

第一范式:列不可再分 

第二范式:行可以唯一区分,主键约束 

第三范式:表的非主属性不能依赖与其他表的非主属性  外键约束 

且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立第一第二范式上。

2.数据库引擎有哪些

MySQL数据库支持多种存储引擎,每个引擎都有其自己的特性和用途。以下是一些常见的MySQL数据库引擎:

  1. InnoDB

    • InnoDB 是MySQL默认的事务性存储引擎。

    • 它支持事务、外键约束、行级锁、崩溃恢复等高级特性。

    • InnoDB 适用于需要事务支持和数据完整性的应用程序,如电子商务和在线银行系统。

  2. MyISAM

    • MyISAM 是MySQL的另一个常见存储引擎,不支持事务。

    • 它适用于只读或读写不频繁的应用程序,如博客、新闻网站等。

    • MyISAM比InnoDB更适合用于存储大量的非事务性数据。

  3. MEMORY

    • MEMORY 存储引擎将数据存储在内存中,提供快速的读写性能。

    • 数据在服务器重启时会丢失,因此不适合长期存储。

    • 适用于缓存表和临时表等情况。

  4. Archive

    • Archive 存储引擎用于存储大量的归档数据,支持高压缩率。

    • 不支持索引和事务,适用于只读的历史数据存储。

  5. CSV

    • CSV 存储引擎允许将数据存储为逗号分隔的文本文件。

    • 可以方便地导入和导出CSV格式的数据。

  6. NDB(Cluster)

    • NDB 存储引擎是MySQL Cluster的一部分,支持分布式数据库。

    • 适用于高可用性和高扩展性的应用,如电信和网络设备。

  7. Federated

    • Federated 存储引擎允许在不同的MySQL服务器之间创建虚拟表,实现分布式查询。

    • 适用于需要在多个数据库服务器之间共享数据的情况。

  8. TokuDB

    • TokuDB 存储引擎具有高性能的写入、更新和压缩数据的能力。

    • 适用于大规模数据的应用,如日志处理和数据仓库。

每个存储引擎都有自己的优点和局限性,选择正确的引擎取决于应用程序的需求和性能要求。通常,事务性应用程序使用InnoDB,而只读或非事务性应用程序可能会选择MyISAM或其他适当的引擎。

3.ACID

ACID 是数据库管理系统(DBMS)中用于确保事务完整性和可靠性的关键特性。ACID 是四个属性的首字母缩写,每个字母代表一种特性:

  1. 原子性(Atomicity)

    • 原子性确保一个事务(Transaction)中的所有操作要么全部成功完成,要么全部失败回滚,没有中间状态。

    • 如果事务的任何一部分失败,系统会撤销所有已经完成的操作,将数据库恢复到事务开始前的状态。

  2. 一致性(Consistency)

    • 一致性确保事务在执行之前和之后,数据库的状态都必须满足一些预定义的一致性规则。

    • 一致性规则通常由数据库设计者定义,以确保数据的完整性和约束条件。

  3. 隔离性(Isolation)

    • 隔离性规定多个并发事务之间应该相互隔离,使它们看起来像是串行执行的,即每个事务不应该感知到其他事务的存在。

    • 隔离性可防止并发事务之间的相互干扰和数据不一致。

  4. 持久性(Durability)

    • 持久性确保一旦事务提交成功,其结果将永久保存在数据库中,即使系统崩溃或断电也不会丢失。

    • 持久性通常通过将事务日志记录到磁盘上的持久存储来实现。

ACID 特性对于数据库操作非常重要,尤其是在需要高数据完整性和可靠性的应用程序中,如金融系统、医疗系统等。然而,ACID 特性也可能会导致性能损失,因为它们要求数据库引擎在事务执行期间进行额外的工作来保证这些特性。

有些数据库系统支持灵活的事务隔离级别,允许开发人员根据应用程序的需求来调整事务隔离级别,以平衡性能和数据完整性。最常见的隔离级别包括读未提交、读已提交、可重复读和串行化。选择合适的隔离级别需要考虑应用程序的需求和性能要求。

4.mysql事务隔离级别

MySQL 支持多个事务隔离级别,事务隔离级别决定了在并发情况下事务之间的可见性和隔离程度。以下是MySQL支持的四个事务隔离级别:

  1. 读未提交(Read Uncommitted)

    • 这是最低级别的事务隔离,允许一个事务可以读取其他事务尚未提交的修改。

    • 这意味着一个事务可以看到未提交的脏数据,可能导致不一致的读取结果。

    • 读未提交隔离级别通常不建议使用,因为它牺牲了数据的一致性。

  2. 读已提交(Read Committed)

    • 这是MySQL的默认事务隔离级别。

    • 在该级别下,一个事务只能读取已提交的其他事务的修改,不会看到未提交的脏数据。

    • 读已提交级别提供了较好的数据一致性,但在某些情况下可能导致幻读问题。

  3. 可重复读(Repeatable Read)

    • 在这个级别下,一个事务在整个事务过程中看到的数据都是一致的,即使其他事务插入新数据也不会对当前事务可见。

    • 这可以防止幻读问题,但在某些情况下可能导致长时间的行锁定和性能问题。

  4. 串行化(Serializable)

    • 这是最高级别的事务隔离级别,它完全隔离了事务,确保并发事务不会相互干扰。

    • 串行化级别提供了最高的数据一致性和隔离性,但通常会带来性能开销,因为它会导致大量的行锁定。

开发人员可以使用以下SQL语句来设置事务隔离级别:

SET TRANSACTION ISOLATION LEVEL isolation_level;

其中,isolation_level 可以是 “READ UNCOMMITTED”、“READ COMMITTED”、“REPEATABLE READ” 或 “SERIALIZABLE” 中的一个。

选择合适的事务隔离级别取决于应用程序的需求。通常情况下,大多数应用程序可以使用默认的 “READ COMMITTED” 级别,但在需要更高隔离性和一致性的情况下,可以考虑使用 “REPEATABLE READ” 或 “SERIALIZABLE” 级别。需要注意的是,更高级别的隔离通常会导致更多的锁定和性能开销,因此需要谨慎使用。

5.隔离级别能解决的问题

数据库事务隔离级别决定了在多个并发事务同时执行时,各个事务之间的可见性和隔离程度。不同的隔离级别可以解决不同类型的并发问题。以下是隔离级别能够解决的主要问题:

  1. 脏读(Dirty Read)

    • 脏读是指一个事务读取到了另一个未提交事务中的数据,如果未提交事务后来被回滚,读取的数据就是无效的。

    • 隔离级别可以通过禁止脏读来解决这个问题。在较高隔离级别下,事务只能读取已提交的数据。

  2. 不可重复读(Non-Repeatable Read)

    • 不可重复读是指在同一个事务中,两次读取同一行数据的结果不一致,通常是因为在两次读取之间另一个事务修改了数据。

    • 隔离级别可以通过锁定读取的数据来解决这个问题。在较高隔离级别下,事务在读取数据时会对其进行锁定,阻止其他事务修改该数据。

  3. 幻读(Phantom Read)

    • 幻读是指在同一个事务中,两次查询条件相同的查询返回不同数量的数据行,通常是因为在两次查询之间另一个事务插入或删除了数据。

    • 隔离级别可以通过锁定范围内的数据来解决这个问题。在较高隔离级别下,事务在查询数据时会锁定整个范围,以防止其他事务修改或插入数据。

  4. 丢失更新(Lost Update)

    • 丢失更新是指两个事务同时读取同一数据,然后进行修改并提交,其中一个事务的修改会被另一个事务覆盖,导致数据丢失。

    • 隔离级别可以通过锁定数据来解决这个问题。在较高隔离级别下,事务在修改数据时会锁定该数据,阻止其他事务修改。

不同的隔离级别提供了不同程度的数据隔离和并发控制,因此可以根据应用程序的需求选择合适的隔离级别。更高的隔离级别通常提供了更强的数据一致性和隔离性,但可能导致性能下降,因此需要权衡隔离级别和性能需求。不同数据库管理系统可能支持不同的隔离级别,开发人员需要了解并选择适合自己应用程序的隔离级别。

6、SQL优化

1、查询语句中不要使用select * 

2、尽量减少子查询,使用关联查询(left join,right join,inner join)替代 

3、减少使用IN或者NOT IN ,使用exists,not exists或者关联查询语句替代 

4、or 的查询尽量用 union或者union all 代替(在确认没有重复数据或者不用剔除重复数据时,union all会更好) 5、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

6、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫 描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null 值,然后这样查询: select id from t where num=0

spring:

1.如何将Bean注入到IOC里:

将JavaBean(也称为POJO,Plain Old Java Object)注入到IoC(Inversion of Control)容器中,通常使用Spring框架进行管理和依赖注入。以下是一些常见的方法来将JavaBean注入到Spring IoC容器中:

  1. 使用XML配置文件进行注入:
    在Spring的XML配置文件中,你可以通过 <bean> 元素来定义和配置JavaBean,并指定其属性值或引用其他Bean。例如:
<!-- 定义一个简单的JavaBean -->
<bean id="myBean" class="com.example.MyBean">
  <property name="propertyName" value="propertyValue" />
</bean>

在这个例子中,<bean> 元素定义了一个名为 myBean 的JavaBean,并设置了它的属性 propertyName 的值。

  1. 使用Java配置类进行注入:
    你可以使用Spring的Java配置来定义和配置Bean,而不是使用XML文件。通常,你需要创建一个Java配置类,使用 @Configuration 注解标记,然后在该类中使用 @Bean 注解定义Bean,如下所示:
@Configuration
public class AppConfig {
  @Bean
  public MyBean myBean() {
      MyBean bean = new MyBean();
      bean.setPropertyName("propertyValue");
      return bean;
  }
}

这个配置类中的 myBean() 方法定义了一个名为 myBean 的Bean,并设置了它的属性。

  1. 使用注解进行注入:
    Spring提供了各种注解来简化Bean的定义和注入,其中最常见的是 @Component@Service@Repository@Autowired。你可以将 @Component 或其他相关注解添加到JavaBean类上,并使用 @Autowired 来注入依赖的Bean。例如:
@Component
public class MyBean {
  private String propertyName;

  // 构造函数或setter方法

  // 使用@Autowired注解注入依赖的Bean
  @Autowired
  private AnotherBean anotherBean;

  // ...
}

Spring会自动扫描并注册标记有 @Component 注解的类,然后使用 @Autowired 将依赖注入到相关的Bean中。

  1. 使用XML命名空间进行注入:
    在XML配置文件中,你还可以使用Spring的命名空间(如 contextaop)来定义Bean,并进行注入。例如:
<context:component-scan base-package="com.example" />

<bean id="myBean" class="com.example.MyBean">
  <property name="propertyName" value="propertyValue" />
</bean>

context:component-scan 命名空间用于扫描指定包下的所有带有 @Component 注解的类,并将它们注册为Spring Bean。

这些是将JavaBean注入到Spring IoC容器的一些常见方法。你可以根据项目需求和个人偏好选择适合你的方法。注入的方式可以根据Bean之间的关系和项目的复杂性而变化。

2.SpringBoot starter

Spring Boot Starter 是 Spring Boot 框架的一项重要功能,用于简化和加速项目的配置和依赖管理。它可以帮助你快速引入一组特定功能或组件的依赖,而无需手动配置大量的XML或Java代码。

Spring Boot Starter 通常包含以下几个关键部分:

  1. 依赖管理:Starter 包含了所需的库和依赖项,这些依赖项通常被预配置为适用于特定的用例或功能。

  2. 自动配置:Starter 提供了自动配置类,这些类会根据项目的需要自动配置各种Bean和组件。这意味着你不需要手动编写大量的配置代码,Spring Boot会为你完成这些工作。

  3. 示例代码和文档:Starter 包通常提供示例代码和文档,以帮助你快速上手并了解如何使用特定功能。

Spring Boot Starter 的好处包括:

  • 快速启动:你可以通过引入适当的 Starter 来快速启动一个特定类型的应用程序,无需手动配置。

  • 约定大于配置:Spring Boot 的设计原则之一是 “约定大于配置”,Starter 符合这个原则,可以根据项目的需要提供合适的默认配置,减少了开发人员的配置工作。

  • 可扩展性:你可以根据需要创建自定义 Starter,以适应特定项目或组织的需求。

一些常见的 Spring Boot Starter 包括:

  • spring-boot-starter-web:用于构建Web应用程序的Starter,包括Spring MVC、Tomcat、Jackson等依赖。

  • spring-boot-starter-data-jpa:用于访问和操作数据库的Starter,包括Spring Data JPA、Hibernate等依赖。

  • spring-boot-starter-security:用于添加安全性功能的Starter,包括Spring Security等依赖。

  • spring-boot-starter-test:用于测试的Starter,包括JUnit、Spring Test等依赖。

要使用 Spring Boot Starter,你只需在项目的构建文件(如Maven或Gradle)中添加相应的依赖,Spring Boot会自动处理剩余的配置和依赖项。例如,在Maven项目中,你可以通过以下方式引入 spring-boot-starter-web

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

这将自动包含所有与构建Web应用程序相关的依赖项,并启用相应的自动配置。

3.IOC和AOP

IoC(Inversion of Control)和AOP(Aspect-Oriented Programming)是两个关键概念,通常与Spring框架紧密相关。它们分别解释如下:

IoC(Inversion of Control,控制反转):

IoC 是一种设计原则,它反转了传统的程序控制流程,使得控制权从程序本身转移到了外部容器(通常是框架或容器),这个容器负责管理和组装组件。Spring框架是IoC的一个典型实现。

IoC的关键思想包括

  1. 依赖注入(Dependency Injection,DI):组件之间的依赖关系由外部容器负责注入,而不是组件自己创建或查找依赖。

  2. 容器管理:外部容器(Spring容器)负责创建、配置和管理应用程序中的组件,这些组件通常被称为Bean。

  3. 松耦合:IoC通过降低组件之间的直接耦合度,使代码更容易维护、测试和扩展。

在Spring中,IoC通常通过XML配置文件、Java注解或Java配置类来实现。开发者将组件的定义交给Spring容器,容器负责创建和管理这些组件,以及处理它们之间的依赖关系。这使得应用程序更加灵活、可维护和可测试。

AOP(Aspect-Oriented Programming,面向切面编程):

AOP是一种编程范式,它允许开发者在应用程序的核心业务逻辑之外定义和管理横切关注点(cross-cutting concerns)。横切关注点是指那些不属于核心业务逻辑但在整个应用程序中广泛存在的功能,例如日志记录、事务管理、权限控制等。

AOP的关键思想包括

  1. 切面(Aspect):切面是定义了横切关注点的模块。它包含了切入点和通知。切入点定义了在何处应用通知,通知定义了在何时、何地、以何种方式应用额外的行为。

  2. 连接点(Join Point):连接点是在应用程序中可能被通知的点。例如,方法调用是一个连接点。

  3. 通知(Advice):通知是在连接点上执行的额外行为。常见的通知类型包括前置通知、后置通知、环绕通知、异常通知和最终通知。

  4. 切入点(Pointcut):切入点是一组连接点的定义,它决定了哪些连接点应该被通知。

Spring AOP是一种基于代理的AOP实现,它通过动态代理或字节码生成来在运行时将通知织入到连接点上,而不需要修改源代码。这使得开发者可以将横切关注点的逻辑从核心业务逻辑中分离出来,提高了代码的模块性和可维护性。

总结:

  • IoC是一种控制权反转的设计原则,它将组件的创建和管理权交给外部容器,如Spring容器。

  • AOP是一种编程范式,它允许将横切关注点(如日志、事务、权限控制)从核心业务逻辑中分离出来,通过切面定义和通知将其应用到连接点上。Spring AOP是Spring框架的AOP实现。

4.jdk和cglib

JDK动态代理和CGLIB(Code Generation Library)都是Java中用于实现动态代理的技术,但它们有不同的实现方式和适用场景:

JDK动态代理:

  1. 基于接口:JDK动态代理是基于接口的代理。它要求目标类实现一个或多个接口,代理对象会实现这些接口并委托方法调用给被代理的目标对象。

  2. 代理对象生成:JDK动态代理使用Java的反射机制,在运行时动态生成代理类,代理类实现了目标接口,并包含了代理逻辑。

  3. 适用场景:JDK动态代理适用于只能代理实现了接口的类。它是Java标准库的一部分,不需要额外的依赖,通常用于基于接口的代理需求。

  4. 代理类的局限性:由于JDK动态代理是基于接口的,因此不能代理没有实现接口的类。此外,代理类的生成过程比较慢,因为涉及到运行时字节码生成和加载。

CGLIB动态代理:

  1. 基于继承:CGLIB动态代理是基于继承的代理。它不要求目标类实现接口,而是直接继承目标类,并覆盖目标方法以添加代理逻辑。

  2. 代理对象生成:CGLIB使用字节码生成库,如ASM,来生成代理类。它在运行时生成代理类,因此比JDK动态代理快。

  3. 适用场景:CGLIB动态代理适用于代理没有实现接口的类。它通常需要额外的库支持,如ASM,因此需要添加依赖。

  4. 代理类的特点:CGLIB生成的代理类通常比JDK动态代理的代理类更庞大,因为它继承了目标类,并覆盖了所有方法。

总结:

  • JDK动态代理适用于代理实现了接口的类,它不需要额外的库支持,但不能代理没有实现接口的类。

  • CGLIB动态代理适用于代理没有实现接口的类,它需要额外的库支持,但通常比JDK动态代理更快。它生成的代理类是目标类的子类。

  • 选择哪种代理方式取决于项目需求和目标类的特点。如果目标类已经实现了接口,首选JDK动态代理;如果目标类没有实现接口,可以考虑使用CGLIB动态代理。

5.Spring、Spring MVC 和 Spring Boot

Spring、Spring MVC 和 Spring Boot 都是Java开发中常用的框架,但它们的定位和功能有所不同:

  1. Spring

    • Spring是一个综合性的框架,用于构建Java应用程序的各个层面,包括应用程序的基础设施、业务逻辑、数据访问和集成等。

    • Spring提供了IoC(控制反转)容器,用于管理组件的生命周期和依赖关系。它还提供了AOP(面向切面编程)支持,用于处理横切关注点。

    • Spring可以与多种其他框架和技术集成,例如Hibernate、JPA、JDBC、RESTful Web Services等。

    • Spring框架需要在项目中进行详细的配置,通常使用XML配置文件、Java注解或Java配置类来定义Bean和配置依赖关系。

  2. Spring MVC

    • Spring MVC是Spring框架的一部分,专注于构建Web应用程序的MVC(模型-视图-控制器)层。

    • Spring MVC提供了一组类和组件,用于处理Web请求、管理URL路由、处理表单提交、渲染视图等任务。

    • 它采用基于注解的控制器来简化请求处理,通常与JSP、Thymeleaf等视图技术结合使用。

    • Spring MVC的特点包括强大的REST支持和可扩展性,可以与各种视图解析器、模板引擎和前端技术集成。

  3. Spring Boot

    • Spring Boot是Spring团队开发的项目,旨在简化和加速Spring应用程序的开发和部署。

    • Spring Boot基于约定大于配置的原则,提供了自动配置,减少了开发者的配置工作。

    • 它包含了嵌入式的Web服务器(如Tomcat、Jetty、Undertow),使得构建独立的、自包含的Web应用程序变得更加容易。

    • Spring Boot可以用于构建各种类型的应用程序,包括Web应用、RESTful服务、批处理应用、微服务等。

    • Spring Boot通常使用"starter"依赖来管理项目的依赖关系,从而简化了项目的构建。

总结:

  • Spring是一个全功能的应用程序框架,提供IoC和AOP等功能。

  • Spring MVC是Spring的一部分,专注于构建Web应用程序的MVC层。

  • Spring Boot是一个用于简化和加速Spring应用程序开发的项目,提供了自动配置和内嵌式Web服务器等功能。Spring Boot通常与Spring框架或Spring MVC一起使用。

6.bean的作用域

在Spring框架中,你可以定义Bean的作用域,以控制Bean实例的创建和销毁方式。Spring支持以下五种Bean作用域:

  1. Singleton(单例)

    • 默认作用域。

    • 在整个应用程序的生命周期内只创建一个Bean实例。

    • 所有对该Bean的请求都返回同一个实例。

    • 适用于需要共享的Bean,通常是状态无关的,如服务类、工具类等。

  2. Prototype(原型)

    • 每次请求时都创建一个新的Bean实例。

    • 不会缓存Bean实例,每次都会返回一个新的实例。

    • 适用于需要状态隔离的Bean,每个客户端都拥有自己的实例,如Web请求的控制器。

  3. Request(请求)

    • 在每个HTTP请求中创建一个新的Bean实例。

    • 该作用域仅适用于Web应用程序中的Bean。

    • 每个HTTP请求都有自己的实例,可用于在请求处理期间共享数据。

  4. Session(会话)

    • 在每个HTTP会话中创建一个Bean实例。

    • 该作用域同样只适用于Web应用程序中的Bean。

    • 每个用户会话都有自己的实例,可用于存储用户特定的数据。

  5. Global Session(全局会话)

    • 在一个全局HTTP会话中创建一个Bean实例。

    • 这个作用域通常只在分布式Web应用程序中使用,其中多个HTTP会话可以共享一个Bean实例。

要定义Bean的作用域,可以在Spring的XML配置文件中使用 scope 属性,或在Java配置类中使用 @Scope 注解。例如,在XML中定义一个单例作用域的Bean:

<bean id="myBean" class="com.example.MyBean" scope="singleton" />

或者在Java配置类中:

@Configuration
public class AppConfig {
    @Bean
    @Scope("prototype")
    public MyBean myBean() {
        return new MyBean();
    }
}

选择适当的Bean作用域取决于你的应用程序的需求和设计。单例和原型是最常见的作用域,但在Web应用程序中,请求、会话和全局会话作用域也非常有用,可以根据需要选择。

7.spring生命周期

Spring框架管理的Bean对象具有生命周期,从创建到销毁都经历了一系列的阶段。以下是Spring Bean的典型生命周期阶段:

  1. 实例化(Instantiation)

    • Spring容器首先通过类的构造方法或工厂方法来创建Bean的实例。

    • 这是Bean生命周期的第一步,通常在容器初始化时完成。

  2. 属性注入(Dependency Injection)

    • 在Bean实例化后,Spring容器会注入Bean的依赖属性,这通常通过构造方法、Setter方法或字段注入实现。
  3. Bean的初始化方法(Initialization)

    • 在属性注入后,Spring容器会调用Bean的初始化方法,通常是通过配置文件中的init-method@PostConstruct注解指定的方法。

    • 初始化方法用于执行一些必要的初始化操作,例如建立数据库连接、加载资源等。

  4. Bean可用(Bean Available)

    • 完成初始化后,Bean实例就可以被应用程序使用了。
  5. Bean的使用(Bean in Use)

    • 此时Bean被应用程序使用,执行业务逻辑。
  6. Bean的销毁方法(Destruction)

    • 当Bean不再需要时(例如,容器关闭或Bean被销毁),Spring容器会调用Bean的销毁方法。

    • 销毁方法通常通过配置文件中的destroy-method@PreDestroy注解指定的方法来定义。

  7. Bean的销毁(Destruction)

    • 最后,Bean实例被销毁并释放资源,从内存中移除。

    • 这是Bean生命周期的最后一个阶段。

需要注意的是,不是所有的Bean都需要定义初始化方法和销毁方法,这取决于Bean的实际需求。如果需要,你可以通过配置文件或注解来定义这些方法。

另外,Spring还提供了Bean后处理器(BeanPostProcessor)接口,它允许你在Bean的初始化前后执行自定义逻辑,例如在初始化后执行一些额外的操作或修改Bean的属性。这可以通过实现BeanPostProcessor接口来实现。

总结:

总结:
Spring Bean的生命周期包括实例化、属性注入、初始化方法、Bean可用、Bean的使用、销毁方法和Bean的销毁。可以通过配置文件或注解来定义初始化方法和销毁方法,也可以使用Bean后处理器来扩展Bean的生命周期。

8.spring循环依赖

Spring中的循环依赖是指两个或多个Bean之间相互依赖,形成一个循环引用的情况。当Spring容器尝试解析这种循环依赖时,可能会导致应用程序无法启动或产生不稳定的行为。Spring容器会尽力处理循环依赖,但仍然需要开发者小心处理。

以下是一些关于Spring循环依赖的注意事项和解决方法:

  1. 构造方法循环依赖

    • 如果Bean之间的循环依赖发生在构造方法参数上,Spring容器无法解决这种循环依赖。

    • 为了解决这个问题,可以考虑通过Setter方法注入依赖,或者通过使用@Lazy注解延迟加载Bean。

  2. 使用@Lazy注解

    • @Lazy注解可以用于Bean的属性上,告诉Spring容器在需要的时候再创建Bean,从而避免循环依赖。

    • 但要注意,使用@Lazy可能会导致性能问题,因为它会延迟Bean的创建。

  3. 使用ApplicationContextAware

    • 可以通过实现ApplicationContextAware接口来访问ApplicationContext对象,并手动获取所需的Bean,从而避免循环依赖。

    • 这种方式需要谨慎使用,因为它可能导致紧密的耦合。

  4. 使用@Autowired@Qualifier

    • 可以使用@Autowired@Qualifier注解来明确指定依赖的Bean的名称,从而避免循环依赖。

    • 这种方式需要在Bean的属性上使用@Autowired@Qualifier注解。

  5. 重构应用程序结构

    • 最好的解决方法是重新设计应用程序的结构,尽量减少循环依赖的发生。

    • 这可能需要考虑拆分Bean或引入中间层。

  6. 使用代理

    • 在某些情况下,可以使用代理来解决循环依赖问题,例如使用@Lazy注解或AopProxy

总之,循环依赖是应该避免的,因为它可能导致应用程序的不稳定性。在设计和开发过程中,应该尽量减少循环依赖的发生,并谨慎处理那些无法避免的循环依赖情况。使用Spring的一些特性和注解可以帮助你更好地处理循环依赖问题。

9.Async注解有什么问题

@Async 注解是 Spring 框架中用于实现异步方法调用的注解。它允许你将一个方法标记为异步执行,以便方法的调用可以在独立的线程中进行,而不会阻塞调用线程。尽管 @Async 提供了很多便利,但在使用时需要注意一些潜在的问题:

  1. 线程管理

    • 异步方法会在独立的线程中执行,因此需要合理管理线程。如果异步方法的数量过多,可能会导致线程池资源耗尽,引发性能问题或内存泄漏。

    • 可以通过配置 Spring 的线程池来控制异步方法的线程数量和行为。

  2. 异常处理

    • 异步方法中的异常默认不会传递给调用线程,因此调用者可能无法立即感知到异步方法的异常。

    • 如果需要处理异步方法中的异常,可以使用 Future@Async 注解的 AsyncUncaughtExceptionHandler 属性来捕获异常。

  3. 方法可见性

    • 异步方法通常不能修改调用者的局部变量或实例变量,因为它们运行在不同的线程中。如果需要在异步方法之间共享数据,应该使用线程安全的方式,如使用 synchronized 关键字或并发数据结构。
  4. Spring AOP限制

    • @Async 注解通常与 Spring AOP 配合使用,但要注意它对方法的调用必须是通过 Spring 的代理对象进行的。如果在同一个类中直接调用被 @Async 注解修饰的方法,异步特性可能不会生效,因为 Spring AOP 无法截获本地方法调用。
  5. Spring事务限制

    • 在异步方法内部,默认情况下,Spring 事务不会传播到新的线程中。这可能导致异步方法不在同一个事务上下文中执行。

    • 如果需要异步方法在同一个事务上下文中执行,可以使用 @Transactional 注解进行修饰。

总之,@Async 注解是实现异步编程的有力工具,但在使用时需要注意线程管理、异常处理、可见性等方面的问题。合理地配置线程池和异常处理机制,以及正确处理共享数据,可以减轻潜在的问题。

10.JWT

JWT(JSON Web Token)是一种用于在网络上安全传输信息的开放标准(RFC 7519),它是一种紧凑且自包含的方式来表示信息。JWT通常用于在不同的系统之间传递身份验证和授权信息。它由三个部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

JWT 的三个部分的结构如下:

  1. 头部(Header):头部通常由两部分组成,标明令牌的类型(JWT)和所使用的签名算法(例如 HMAC SHA256 或 RSA)。头部通常采用 Base64 编码后的 JSON 格式表示。

  2. 载荷(Payload):载荷包含了一些声明(Claims),它们是关于实体(通常是用户)和其他数据的声明。有三种类型的声明:

    • 注册声明(Registered Claims):这些是一组预定义的声明,包括 iss(发行者)、exp(过期时间)、sub(主题)等。

    • 公共声明(Public Claims):这些声明可以自由定义,但建议定义在注册声明中。

    • 私有声明(Private Claims):这些是自定义的声明,用于在令牌的发送方和接收方之间共享信息。

  3. 签名(Signature):签名用于验证发送方是否合法以及令牌是否被篡改。签名的计算需要使用头部中指定的签名算法以及一个密钥。只有知道密钥的一方才能验证令牌的签名。

JWT 的工作流程通常如下:

  1. 用户进行身份验证,服务器验证用户的身份后生成一个 JWT。

  2. 服务器将 JWT 发送给客户端。

  3. 客户端在以后的请求中将 JWT 包含在请求头或请求体中。

  4. 服务器收到请求后验证 JWT 的签名,以确保令牌的完整性和合法性。

  5. 如果 JWT 有效,服务器会根据令牌中的信息对请求进行授权或其他操作。

JWT 的主要优点包括:

  • 紧凑性:JWT 是一种轻量级的令牌,易于在网络上传输。

  • 自包含性:JWT 包含了所有必要的信息,无需额外的查询数据库。

  • 可扩展性:可以添加自定义的声明以满足特定需求。

  • 安全性:签名保护令牌的完整性和机密性,防止篡改和信息泄露。

然而,需要谨慎使用 JWT,避免在令牌中包含敏感信息,因为令牌可以被解码并读取。此外,密钥的安全性至关重要,因为泄漏密钥可能导致令牌被篡改。

Redis:

1.Redis内存的持久化

Redis 是一种内存数据库,数据存储在内存中,因此它需要一种机制来确保数据在服务重启或发生故障时不会丢失。Redis 使用持久化机制来满足这一需求,有两种主要的持久化方式:

  1. RDB 持久化

    • RDB 持久化是将 Redis 数据集的快照存储在磁盘上的一种方式。

    • 它周期性地将整个数据集保存到磁盘上的二进制文件(.rdb 文件)中。

    • RDB 持久化是通过一个快照来实现的,可以配置 Redis 每隔一段时间自动创建快照,也可以手动触发快照的生成。

    • RDB 持久化是比较紧凑和高效的,适用于备份和灾难恢复。

  2. AOF 持久化

    • AOF(Append Only File)持久化是通过将每个写操作追加到日志文件中来实现的。

    • Redis 服务器启动时,会重新执行 AOF 文件中记录的写操作,从而恢复数据集的状态。

    • AOF 持久化的优点是可以提供更好的持久性和数据恢复,因为它记录了每个写操作。

    • AOF 持久化可以配置为同步或异步方式,以控制写操作的执行时机。

    • AOF 持久化通常比 RDB 持久化更耗磁盘空间,但在数据安全性方面更可靠。

Redis 还支持混合使用 RDB 持久化和 AOF 持久化。在这种配置下,如果 Redis 重启时同时存在 RDB 文件和 AOF 文件,Redis 会选择使用 AOF 文件进行数据恢复,因为 AOF 记录的写操作通常更完整。

持久化是 Redis 中非常重要的特性,它确保了数据的持久性和可恢复性。开发者可以根据应用程序的需求选择合适的持久化方式或将两种方式结合使用。

zookeeper:

1. ZooKeeper 是什么?

ZooKeeper 是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群中各个节点的状态根 据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给 用户。

分布式应用程序可以基于 Zookeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通 知、集群管理、Master 选举、分布式锁和分布式队列等功能。

Zookeeper 保证了如下分布式一致性特性:

(1)顺序一致性

(2)原子性

(3)单一视图

(4)可靠性

(5)实时性(最终一致性)

客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也 是由所连接的 zookeeper 机器来处理。对于写请求,这些请求会同时发给其他 zookeeper 机器并且达 成一致后,请求才会返回成功。因此,随着 zookeeper 的集群机器增多,读请求的吞吐会提高但是写 请求的吞吐会下降。

有序性是 zookeeper 中非常重要的一个特性,所有的更新都是全局有序的,每个更新都有一个唯一的 时间戳,这个时间戳称为 zxid(Zookeeper Transaction Id)。而读请求只会相对于更新有序,也就是 读请求的返回结果中会带有这个zookeeper 最新的 zxid。

2. ZooKeeper 提供了什么?

(1)文件系统 

(2)通知机制

3.Zookeeper 文件系统

Zookeeper 提供一个多层级的节点命名空间(节点称为 znode)。与文件系统不同的是,这些节点都 可以设置关联的数据,而文件系统中只有文件节点可以存放数据而目录节点不行。

Zookeeper 为了保证高吞吐和低延迟,在内存中维护了这个树状的目录结构,这种特性使得 Zookeeper 不能用于存放大量的数据,每个节点的存放数据上限为1M。

Kafka:

1.Kafka架构介绍

Kafka 是一个分布式流数据平台,用于处理和传输大规模的数据流。它的架构设计旨在具备高可用性、可扩展性和容错性,使其成为处理实时数据流的强大工具。以下是 Kafka 的主要架构组件和概念:

  1. 生产者(Producer)

    • 生产者是将数据发布到 Kafka 主题(Topic)的应用程序或服务。

    • 它将数据写入 Kafka 主题的一个分区(Partition)中。

    • 生产者可以选择性地将数据分配到指定分区,或者由 Kafka 选择分区(根据分区策略)。

  2. 主题(Topic)

    • 主题是 Kafka 数据流的分类,它是消息的逻辑容器。

    • 生产者将消息发布到特定的主题,而消费者从特定的主题订阅消息。

    • 主题可以有多个分区,每个分区都是消息的存储单元。

  3. 分区(Partition)

    • 主题可以被分为多个分区,每个分区是消息的有序序列。

    • 分区允许 Kafka 实现水平扩展,因为不同分区可以分布在不同的服务器上。

    • 分区中的消息以顺序方式写入和读取,但不同分区之间的消息并不保证全局有序性。

  4. 消费者(Consumer)

    • 消费者是订阅 Kafka 主题并从中读取消息的应用程序或服务。

    • 消费者可以以不同的方式进行消息订阅,包括指定特定主题和分区,或者使用消费者组(Consumer Group)进行负载均衡。

    • 消费者可以实现自动位移(offset)管理,以记录其消费进度。

  5. 消费者组(Consumer Group)

    • 消费者组是一组消费者的集合,它们共同消费一个或多个主题的消息。

    • Kafka 通过将分区分配给不同的消费者来实现负载均衡和水平扩展。

    • 每个分区只能由同一消费者组内的一个消费者进行消费,确保消息的顺序性。

  6. 代理服务器(Broker)

    • 代理服务器是 Kafka 集群中的服务器节点,负责存储和管理消息。

    • 每个代理服务器都有自己的主题分区副本,以确保数据冗余和可用性。

    • Kafka 集群由多个代理服务器组成,它们协同工作以提供高可用性和容错性。

  7. ZooKeeper

    • ZooKeeper 是一个分布式协调服务,通常与 Kafka 配合使用来管理 Kafka 集群的元数据信息和协调分布式操作。

    • ZooKeeper 用于跟踪 Kafka 代理服务器的健康状况、分区分配和领导者选举等任务。

  8. 生产者和消费者 API

    • Kafka 提供了多种编程语言的客户端库,用于开发生产者和消费者应用程序。

    • 生产者 API 用于将数据发送到 Kafka 集群,而消费者 API 用于从 Kafka 集群中读取数据。

Kafka 的架构允许处理大规模的实时数据流,并且可以轻松扩展以满足不断增长的数据需求。它在许多大规模数据处理场景中广泛应用,包括日志收集、事件流处理、监控和指标收集等。通过适当的配置和管理,Kafka 可以实现高吞吐量、低延迟和高可用性的数据传输和处理。

MQ消息队列:

SpringCloud微服务:

Linux:

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值