1.静态方法可以被重写吗
静态方法不能被重写。静态方法是属于类的,而不是属于实例的。当子类继承一个父类时,子类会继承父类的静态方法,但是子类不能重写父类的静态方法。如果子类定义了一个与父类静态方法同名的静态方法,那么它只是隐藏了父类的静态方法,而不是重写了它。因此,静态方法是不能被重写的。
2.抽象方法和接口的区别
抽象方法和接口都是面向对象编程中的概念,它们有一些相似之处,但也有一些区别。
抽象方法是在抽象类中声明的方法,它没有具体的实现代码。抽象方法用关键字 "abstract" 来修饰,并且必须在抽象类中声明。抽象类本身不能被实例化,只能被继承,并且子类必须实现抽象方法才能被实例化。
接口是一种完全抽象的类,它只包含方法的声明,没有方法的实现。接口使用关键字 "interface" 来定义,并且可以被类实现。一个类可以实现多个接口,通过实现接口中的所有方法来达到接口的要求。
主要区别如下:
-
抽象方法必须在抽象类中声明,而接口只包含方法的声明。
-
抽象类可以包含非抽象方法和属性,而接口只能包含方法的声明。
-
一个类只能继承一个抽象类,但可以实现多个接口。
-
抽象类的子类必须实现抽象方法,否则子类也必须声明为抽象类。而实现接口的类必须实现接口中的所有方法。
-
接口可以被多个类实现,从而实现了多态性的特性。 总结来说,抽象类提供了一种将相关类的共同行为进行抽象的机制,而接口则提供了一种定义类之间契约的方式。
3.md5
MD5(MessageDigest Algorithm 5)是一种广泛使用的密码散列函数,它产生128位(16字节)哈希值.它通常用于通过为给定输入生成唯一的散列值来验证数据的完整性。
MD5是一个单向函数,这意味着从计算上不可能逆转进程并从哈希值中获取原始输入。
但是,需要注意的是,由于长期发现的漏洞,MD5在加密方面被认为是不安全的。 建议使用更安全的哈希函数,如SHA-256加密的目的。
4.快排
快排,即快速排序(Quicksort),是一种常用的排序算法。
它的基本思想是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程递归进行,以此达到整个数据变成有序序列的目的。
快速排序的步骤如下:
-
从待排序的数据中选择一个元素作为基准(通常选择第一个或最后一个元素)。
-
将所有小于基准的元素放在基准的左边,将所有大于基准的元素放在基准的右边,相同大小的元素可以放在任意一边。
-
对基准左边和右边的子序列分别递归地应用快速排序算法。 快速排序的平均时间复杂度为O(nlogn),其中n为待排序数据的数量。它是一种高效的排序算法,但在最坏情况下(例如已经有序的序列),时间复杂度可达到O(n^2)。在实践中,快速排序通常比其他排序算法(如冒泡排序和插入排序)更快。
5.事务隔离级别以及加锁方式
事务隔离级别和加锁方式是数据库中用来处理并发操作的重要概念。
事务隔离级别定义了多个事务之间的隔离程度,它决定了一个事务对于其他事务的可见性和影响。常见的事务隔离级别包括:
-
读未提交(Read Uncommitted):最低级别的隔离级别,一个事务可以读取到其他事务未提交的数据,可能会导致脏读、不可重复读和幻读的问题。
-
读已提交(Read Committed):一个事务只能读取到已经提交的数据,解决了脏读的问题,但仍可能出现不可重复读和幻读的问题。
-
可重复读(Repeatable Read):保证在同一个事务中多次读取同一数据时,其结果始终一致。其他事务对该数据的修改在该事务提交之前不可见,解决了不可重复读的问题,但仍可能出现幻读的问题。
-
串行化(Serializable):最高级别的隔离级别,通过对事务进行串行执行来避免并发问题,可以解决所有并发问题,但会牺牲并发性能。 加锁方式是指在事务隔离级别的基础上,数据库系统采用的具体锁策略。
常见的加锁方式包括:
-
共享锁(Shared Lock):多个事务可以同时获取共享锁,用于读取操作,不阻塞其他事务的共享锁。
-
排他锁(Exclusive Lock):一个事务获取排他锁后,其他事务无法获取共享锁或排他锁,用于写入操作。
-
行级锁(Row-Level Lock):对数据的每一行进行锁定,可以最大程度地提高并发性。
-
表级锁(Table-Level Lock):对整个表进行锁定,会导致并发性能下降,一般在特定情况下使用。 具体的事务隔离级别和加锁方式会根据数据库管理系统的不同而有所差异,开发人员需要根据实际需求选择适合的隔离级别和加锁方式。
6.怎么实现线程安全
要实现线程安全,可以采取以下几种方法:
-
加锁:使用锁机制(如互斥锁、读写锁、自旋锁等)来保护共享资源,确保同一时间只有一个线程能够访问或修改该资源。锁的使用可以通过关键字(如synchronized关键字)或显式地调用锁的相关方法来实现。
-
原子操作:使用原子操作来保证对共享资源的操作是不可中断的,即在执行期间不会被其他线程干扰。原子操作可以通过使用原子类(如AtomicInteger、AtomicLong等)或使用关键字(如volatile关键字)来实现。
-
并发容器:使用线程安全的并发容器(如ConcurrentHashMap、ConcurrentLinkedQueue等)来替代非线程安全的容器,这些容器内部实现了线程安全的机制,可以在多线程环境下安全地进行操作。
-
同步代码块/方法:使用同步代码块或同步方法来限制同时只有一个线程可以执行该代码块或方法,确保对共享资源的访问是串行的。
-
使用线程安全的第三方库:如果需要处理复杂的并发场景,可以使用已经经过充分测试和验证的线程安全的第三方库,这些库提供了各种并发控制的机制和工具。 需要根据具体的需求和场景选择适合的线程安全方法。同时,也需要注意避免死锁、活锁和性能问题等并发编程中常见的陷阱。
7.创建线程的方式
1.使用Threads/Thread类:这是创建线程的传统方式。您可以创建一个扩展Thread类的新类,并重写run (方法来定义将在线程中执行的代码。然后,您可以创建这个类的一个实例,并调用start () 方法来启动线程。
Java中的例子
class MyThread extends Thread {
public void run() {
// code to be executed in the thread
}
}
// Creating and starting the thread
MyThread thread = new MyThread();
thread.start();
2使用Runnable接口:这种方法包括实现Runnable接口和定义run (方法)。然后,您可以创建实现接口的类的实例,并将其传递给Thread对象以启动线程。
Java中的例子
class MyRunnable implements Runnable {
public void run() {
// code to be executed in the thread
}
}
// Creating and starting the thread
Thread thread = new Thread(new MyRunnable());
thread.start();
还可以用线程池、使用Callable和Future
8.自动拆箱与自动装箱
自动拆箱(Unboxing)和自动装箱(Boxing)是Java中的两个特性,用于在基本数据类型和对应的包装类之间进行自动转换。
自动装箱是指将基本数据类型自动转换为对应的包装类。例如,将int类型的数据自动转换为Integer类型的对象。这样可以方便地将基本数据类型作为对象来处理,可以使用对象的方法和属性。 示例代码:
int num = 10;
Integer obj = num; // 自动装箱,将int类型的num转换为Integer类型的对象
自动拆箱是指将包装类对象自动转换为对应的基本数据类型。例如,将Integer类型的对象自动转换为int类型的数据。这样可以方便地将包装类对象转换为基本数据类型进行计算或其他操作。 示例代码:
Integer obj = 20;
int num = obj; // 自动拆箱,将Integer类型的obj转换为int类型的数据
自动拆箱和自动装箱使得基本数据类型和对应的包装类之间的转换更加方便,提供了更灵活的编程方式。但在使用时需要注意空指针异常(NullPointerException)的问题,因为在自动拆箱时可能会出现空对象的情况。
9.说下面向对象
面向对象(Object-oriented programming,简称OOP)是一种编程范式,它以对象作为程序的基本单元,将数据和操作封装在对象中,并通过对象之间的交互来实现程序的功能。
面向对象的编程思想主要包括以下几个核心概念:
-
类(Class):类是对象的模板或蓝图,用于定义对象的属性(成员变量)和行为(方法)。
-
对象(Object):对象是类的实例,它具有类定义的属性和行为。
-
封装(Encapsulation):封装是将数据和对数据的操作封装在一个对象中,对象对外部隐藏其内部的实现细节,只提供特定的接口供外部访问。
-
继承(Inheritance):继承是一种机制,允许一个类继承另一个类的属性和方法。通过继承,子类可以重用父类的代码,并可以在其基础上进行扩展或修改。
-
多态(Polymorphism):多态是指同一类型的对象在不同情境下表现出不同的行为。通过多态,可以实现方法的重写和方法的重载,提供更灵活和通用的代码结构。 面向对象编程具有可维护性、可扩展性、代码重用性等优点,它能够更好地模拟真实世界的问题和关系,提供了一种更直观和易于理解的编程方式。常见的面向对象编程语言包括Java、C++、Python等。
10.token
token的意思是"令牌",是服务端生成的一串字符串,作为客户端进行请求的一个标识。
当用户第一次登录后,服务器生成一个token并将此token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。
简单token的组成;uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token的前几位以哈希算法压缩成的一定长度的十六进制字符串。为防止token泄露)。
为什么要用Token?
Token 完全由应用管理,所以它可以避开同源策略
Token 可以避免 CSRF 攻击
Token 可以是无状态的,可以在多个服务间共享
基于token机制的身份认证
使用token机制的身份验证方法,在服务器端不需要存储用户的登录记录。大概的流程:
客户端使用用户名和密码请求登录。
服务端收到请求,验证用户名和密码。
验证成功后,服务端会生成一个token,然后把这个token发送给客户端。
客户端收到token后把它存储起来,可以放在cookie或者Local Storage(本地存储)里。
客户端每次向服务端发送请求的时候都需要带上服务端发给的token。
服务端收到请求,然后去验证客户端请求里面带着token,如果验证成功,就向客户端返回请求的数据。(如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。)
11.什么是链表,什么是树
链表是一种线性数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表的特点是节点之间不一定是连续存储的,而是通过指针进行连接。
链表分为单向链表和双向链表两种形式。在单向链表中,每个节点只有一个指向下一个节点的指针;而在双向链表中,每个节点既有指向下一个节点的指针,也有指向前一个节点的指针。
链表的优点是插入和删除节点的操作效率较高,但访问节点的效率较低,需要从头节点开始遍历。
树是一种非线性的数据结构,它由一组节点组成,节点之间存在层级关系。树的一个节点称为根节点,其他节点分为父节点、子节点和兄弟节点。树的节点可以有任意数量的子节点,但每个节点最多只能有一个父节点。
树的一种常见形式是二叉树(Binary Tree),其中每个节点最多有两个子节点:左子节点和右子节点。二叉搜索树(Binary Search Tree)是一种特殊的二叉树,它满足左子节点的值小于父节点的值,右子节点的值大于父节点的值。
树的优点是能够高效地进行搜索和插入操作,常用于实现各种算法和数据结构,如二叉搜索树、堆、哈夫曼树等。
12.树和图的区别
树和图的主要区别在于它们的结构和元素之间的关系。
-
结构:树是一种分层的、无环(无环)数据结构的图。它由由边连接的节点组成,顶部有一个根节点,每个节点有零个或多个子节点。在树中,任意两个节点之间都有一条唯一的路径。另一方面,图是一种更通用的数据结构,由由边连接的节点(顶点)组成。在图中,可能存在循环,并且没有特定的根节点。
-
连通性:在树中,所有节点都是连接的,节点之间有明确的父子关系。除了根节点,每个节点都有一个父节点。在图中,节点可以有任意的连接,并且没有特定的父子关系。
3.方向性:树通常被认为是有向无环图(dag),这意味着从父节点到子节点的边具有特定的方向。图可以是有向的(边有方向),也可以是无向的(边没有特定的方向)。
- 用例:树通常用于表示层次结构,如文件系统、组织图或家族树。它们提供了高效的搜索和层次关系。另一方面,图用于表示更复杂的关系,如社交网络、交通网络或计算机网络,其中节点可以具有任意连接。
总而言之,树是一种具有层次结构的特定类型的图,而图更通用,可以具有任意的连接和环。树通常用于表示层次关系,而图用于更复杂和相互关联的关系。
13.java基本数据类型
Java中的基本数据类型有:
-
byte:表示有符号的8位整数值。取值范围为-128 ~ 127。
-
short: 16位有符号整数值。其范围为-32,768 ~ 32,767。
3.int:代表一个签署了32位的整数值。其取值范围为-2,147,483,648 ~ 2,147,483,647。
-
long:有符号64位整型值。其范围从-9,223,372,036,854,775,808到9,223,372,036,854,775,807。
-
浮点:代表一个单精度32位浮点值。它可以存储十进制数,范围约为±3.40282347E+38F。
-
double:表示双精度64位浮点值。它可以存储十进制数,范围约为±1.79769313486231570e+308。
-
boolean:表示布尔值,可以是true或false。
-
char:表示单个16位Unicode字符。
这些数据类型用于在Java程序中声明变量和存储不同类型的值。
14.redis原理
Redis (Remote Dictionary Server,远程字典服务器)是一个开源的内存数据结构存储,可以用作数据库、缓存和消息代理。它以高性能、可扩展性和多功能性而闻名。
Redis基于键值存储模型工作,其中数据以键值对的形式存储。它将整个数据集保存在内存中,允许快速的读写操作。数据可以定期持久化到磁盘中以保持持久性。
Redis的一些关键原则和特性包括:
-
数据结构:Redis支持各种数据结构,如字符串、散列、列表、集合、排序集合等。每个数据结构都有自己的一组命令和操作,允许高效地操作和检索数据。
-
内存存储:Redis主要将数据存储在内存中,从而实现快速访问和低延迟操作。但是,它也提供了将数据持久化到磁盘的选项,从而允许数据的持久性。
3.复制和高可用性:Redis支持复制,允许为一个Redis实例创建多个副本。这提供了高可用性和容错通过允许读操作是分布在多个节点。
-
发布/订阅消息:Redis包含一个发布/订阅消息系统,客户端可以订阅频道并接收发布在这些频道上的消息。它支持实时通信和事件驱动架构。
-
Lua脚本:Redis支持Lua脚本,允许用户编写自定义脚本并在服务器上执行它们。这提供了在服务器端执行复杂操作的灵活性和能力。
-
分布式缓存:复述,通常是用作缓存由于其高性能和支持过期的钥匙。它可以与应用程序集成,缓存频繁访问的数据,减少主数据存储上的负载。
总的来说,Redis被设计为快速、可扩展和灵活,这使得它适用于广泛的用例,包括缓存、实时分析、消息传递等。