5.20日
java的集合体系结构
它提供了一组接口、类和算法,用于存储和管理对象集合。Java的集合框架包括多个基本接口,如Collection、List、Set、Map等。Collection是集合层次结构的根接口,代表一组对象;List是有序集合,可以包含重复元素;Set是不包含重复元素的集合;Map是键值对的集合。这些接口提供了各种操作,如搜索、排序、插入、操作和删除,使数据操作变得简单快捷。
hashmap的实现原理
HashMap是Java中的一个重要数据结构,它基于哈希表实现。HashMap通过将键映射到值来存储数据,具有快速的查找和插入性能。在HashMap中,键是唯一的,而值可以重复。HashMap的实现原理是通过计算键的哈希码,然后将其映射到内部数组的索引位置,以实现快速的查找和插入操作。在处理哈希冲突时,HashMap使用链表或红黑树来解决。
抽象类和接口的区别?
- 抽象类使用abstract定义 接口使用interface定义
- 接口里面的方法默认使用是用public abstract修饰的,接口里面的属性都是静态常量,默认使用public static final修饰的
- 抽象类包含抽象方法和非抽象方法,非抽象方法需要有方法体
- java8之前接口中的方法都是抽象方法 ,java8之后 接口可以写默认方法,默认方法可以写方法体写代码逻辑了
抽象类是一种类,可以包含抽象方法和具体方法,不能被实例化,需要子类继承并实现其抽象方法。接口是一种纯抽象的类型,只包含常量和抽象方法的定义,不能包含具体方法的实现。一个类可以实现多个接口,但只能继承一个抽象类。因此,抽象类提供了一种代码重用和继承的机制,而接口则提供了一种更灵活的实现多态和组合的方式。
为什么java不支持多继承
Java不支持多继承是为了避免多继承可能引发的复杂性和歧义。多继承会导致菱形继承等问题,增加代码的复杂性和难以维护性。Java通过接口的方式实现了类与类之间的多重继承,使得代码结构更清晰、灵活,并且避免了多继承可能带来的问题。
java的异常体系
Java异常分为可检查异常(Checked Exception)和不可检查异常(Unchecked Exception)。可检查异常需要在代码中显式处理,如IOException;不可检查异常通常是运行时异常,如NullPointerException。异常处理通过try-catch-finally块来捕获和处理异常,保证程序的稳定性和可靠性。
5.21日
为什么list在foreach中不能删除数据,如何想删除数据怎么办
因为foreach循环实际上是通过迭代器来遍历集合的,而在foreach循环中无法直接访问迭代器的remove方法。如果在foreach循环中尝试直接删除元素,会导致集合结构发生变化,从而抛出ConcurrentModificationException(并发修改)异常。为了安全地从集合中删除元素,应该使用Iterator迭代器来进行操作。通过使用Iterator,可以在遍历集合的同时安全地删除元素,而不会引发异常。
什么是微服务架构? 什么是集群
微服务架构是一种软件架构设计风格,其中软件系统被拆分为一组小型、独立的服务单元,每个服务单元都运行在自己的进程中,并通过轻量级通信机制(通常是HTTP API)进行交互。每个微服务都专注于执行特定的业务功能,可以独立部署、扩展和维护。微服务架构的优势包括提高系统的灵活性、可伸缩性和可维护性,同时降低了系统的耦合度和复杂性。
集群是指将多台计算机(通常是服务器)连接在一起,以共同完成某项任务或提供某种服务。在集群中,这些计算机通过网络通信协作,共享负载和资源,以提高系统的性能、可靠性和可用性。
5.23日
HashMap是如何解决hash冲突的
采用链式寻址法解决,之所以会产生hash冲突,因为hashmap底层是数据组+加链表组成的,当往hashmap中存放数据的时候,因为是key,value形式的,他会根据key的hashcode方法获取hashcode值,因为要落到数组结构中,那么就使用了位与运算,进行计算落到具体的数组中,如果两个key他们经过计算获取到的数组位置是一样的,那么就生产的hash冲突的问题。
**链地址法(开放寻址法的一种):**HashMap在每个哈希桶(bucket)中存储一个链表(在 Java 8 及以后版本中,当链表长度超过一定阈值时会转换为红黑树以优化性能)。当两个不同的键具有相同的哈希值时,它们会被存储在同一个哈希桶的链表中。因此,查找一个键时,首先根据哈希值找到对应的哈希桶,然后遍历该桶中的链表来找到具体的键。
**重新哈希:**虽然HashMap本身不直接实现重新哈希来解决冲突,但在某些情况下,我们可以通过实现高质量的hashCode()方法来减少冲突。一个好的hashCode()方法应该尽量分散地生成哈希值,以减少哈希冲突的可能性。
**扩容:**当哈希表中的元素数量超过某个阈值(默认为容量的 0.75)时,HashMap会自动扩容(即创建一个新的、更大的哈希表,并将原哈希表中的所有元素重新哈希并放入新表)。扩容可以减少哈希冲突的可能性,因为新的哈希表有更多的桶来存储元素
**使用红黑树(Java 8 及以后版本):**当链表长度超过某个阈值(默认为 8)时,Java 8 中的HashMap会将链表转换为红黑树。红黑树是一种自平衡的二叉搜索树,它可以在 O(log n) 的时间复杂度内完成查找、插入和删除操作,从而提高了HashMap在哈希冲突较多时的性能
HashMap 的 put 流程
HashMap的put流程是 Java 集合框架中非常重要的一个操作,它用于在HashMap中插入或更新键值对。以下是HashMap的put方法的大致流程(基于 Java 8 及之后的版本):
1.计算哈希值 2.确定桶的位置 3.处理哈希冲突 4.扩容 5.返回旧值
计算哈希值:
当调用put(K key, V value)方法时,首先会调用key对象的hashCode()方法来计算哈希值。
确定桶的位置:
将计算得到的哈希值通过某种算法(通常是哈希值对数组长度取模)转换为数组索引,确定键值对应该存储在哪个桶(bucket)中。
处理哈希冲突:
-
如果桶中已经有元素(链表或红黑树),说明发生了哈希冲突。此时会进行以下操作:
-
如果桶中是一个链表,遍历链表,使用equals()方法查找是否存在相同的键。
-
如果找到了相同的键,则使用新的值替换旧的值(更新操作)。
-
如果没有找到相同的键,则将新的键值对添加到链表的末尾。
-
如果链表长度超过了某个阈值(TREEIFY_THRESHOLD,默认为 8),并且数组大小大于或等于MIN_TREEIFY_CAPACITY(默认为 64),则将链表转换为红黑树。
扩容:
如果HashMap中的元素数量超过了数组长度和加载因子(load factor)的乘积(默认加载因子为 0.75),则触发扩容操作。创建一个新的数组,其长度是原数组长度的两倍。将原数组中的所有元素重新哈希并放入新数组中。
返回旧值(可选):
如果键值对是新的(即没有发生键冲突),则返回null;如果替换了旧值,则返回被替换的旧值。
5.24日
HashMap的扩容机制
扩容必须满足两个条件:
HashMap的扩容机制是HashMap在动态调整其内部数组大小以应对元素数量增长的重要机制
1、 存放新值的时候当前已有元素的个数必须大于等于阈值
2、 存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)
- 扩容触发条件:HashMap的扩容主要基于两个条件。首先,当HashMap中的元素数量(size)超过了阈值(threshold)时,会触发扩容。这个阈值是HashMap的容量(capacity)与加载因子(load factor)的乘积。在Java的HashMap实现中,默认的加载因子是0.75,这意味着当HashMap中的元素数量达到了其容量的75%时,就会触发扩容。(默认长度16)
- 扩容过程:当HashMap需要扩容时,会创建一个新的数组,其容量是原数组的两倍。然后,HashMap会重新计算每个元素的哈希值,并根据新的容量确定每个元素在新数组中的位置。这个过程被称为“rehashing”。
HashMap 是线程安全的吗?多线程下会有什么问题?
HashMap 在 Java 中是非线程安全的。在多线程环境下使用 HashMap 可能会导致以下问题:
- 数据不一致性:由于 HashMap 的 put、get、remove 等操作没有内置的同步机制,多个线程可以同时修改 HashMap 的结构或内容,这可能导致数据的不一致性,如数据丢失、重复插入或覆盖。
- 死循环问题:在 Java 7 中,尤其当存在哈希碰撞并且多个线程同时插入时,可能会导致链表形成环状结构,进而引发死循环。虽然这个问题在 Java 8 中由于引入了红黑树作为散列冲突严重时的解决方案而得到缓解,死循环的问题基本被避免,但 HashMap 本身仍然是非线程安全的。
- 扩容时的问题:当 HashMap 需要扩容时,如果没有适当的同步控制,多个线程同时进行扩容操作可能会导致数据错乱,比如元素被错误地放置到新的桶中,或某些元素没有被正确地迁移。
- 可见性问题:线程修改 HashMap 的操作可能对其他线程不可见,这是由于 Java 内存模型中关于数据同步的规定,一个线程修改的数据可能不会立即对其他线程可见。
解决办法
可以通过外部同步锁解决多线程同步
也可以通过concurrentHashMap进行代替
5.25日
你知道concurrentHashmap吗,他的原理是什么
Java 中的一个并发哈希表实现,用于在多线程环境下安全地存储和操作键值对
它的原理主要基于两个方面:分段锁和使用 CAS (Compare and Swap) 操作。
- 分段锁:
ConcurrentHashMap
将整个数据结构分割成多个段(Segment),每个段相当于一个小的哈希表,每个段都有自己的锁。这样可以减小锁的粒度,不同的段可以被不同的线程同时访问,提高并发性能。当一个线程对某个段进行操作时,只有这个段会被锁定,其他段的操作不会被阻塞。 - CAS 操作:在
ConcurrentHashMap
的实现中,对于不同的操作,比如插入、删除、更新等,都是通过 CAS 操作来实现的。CAS 是一种乐观锁技术,它允许多个线程同时尝试更新同一个变量的值,但只有一个线程能够成功,其他线程需要重试。这种方式在并发环境下能够提供较高的性能。
为什么在JDK8中HashMap要转成红黑树
在JDK8中,HashMap在某些情况下会将链表转换为红黑树,这是为了解决在链表过长时导致的性能问题。
原因是什么
提高性能
当链表长度超过一定阈值(默认为8)时,HashMap会将链表转换为红黑树,这样可以将查找、插入等操作的时间复杂度从O(n)降低到O(log n),提高了HashMap在大数据量情况下的性能。
在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度
解决了什么问题
红黑树的引入解决了HashMap在处理大量数据时的性能问题,使得HashMap在更广泛的场景下都能保持较好的性能表现。
为什么不使用二叉树
如果使用二叉树有序化的数据 就有可能导致变成另一个列表
5.27日
为什么会有跨域,解决跨域的原理是什么
1.什么是跨域
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
• 同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域
2.为什么会有跨域
为了网络安全 防止不安全的请求 ,浏览器设置了一个同源策略,域名,端口,协议全部相同,就叫做同源。当页面在执行一个脚本时,会检查访问的资源是否同源,如果不是,就会报错。在实际开发中,经常会有跨域加载资源的需求,避免不了跨域请求,所以就出现了跨域
3.跨域的解决方案
1、 后端加注解
2、nginx反向代理
- 利用nginx把跨域反向代理为不跨域,支持各种请求方式
3、设置浏览器属性
- 方法:在浏览器中设置参数
--args --disable-web-security
(注意有空格)。
4、前端代码
5、CORS(跨域资源共享)
- 一种由 W3C 提出的跨域解决方案。它允许服务器在响应头中设置一些特殊的字段(如
Access-Control-Allow-Origin
),来告诉浏览器哪些源可以访问该资源。浏览器在接收到响应后,会检查这些字段的值,并根据这些值来决定是否允许跨域请求。
6、JSONP (JSON with Padding)
- 原理:利用
<script>
标签不受同源策略限制的特性,通过<script>
标签的src
属性发送带有callback
参数的GET请求,服务端返回数据时,将JSON数据作为参数传入该函数中,以此实现跨域加载数据。
5.28日
@RestController和@Controller的区别
@Controller标识当前类是SpringMVC Controller处理器,而@RestController则只负责数据返回
@Controller
-
用途:
@Controller 注解主要用于定义一个控制器类,该类负责处理用户的HTTP请求,并返回相应的视图。 -
返回类型:
在 @Controller 中,处理方法通常返回一个字符串,该字符串代表要渲染的视图的名称。框架会根据这个视图名称查找并渲染相应的视图。 -
视图解析:
@Controller 类的处理方法返回的字符串通常被解释为视图名称,然后由视图解析器解析为实际的视图。
@RestController
-
用途:
@RestController 注解是 @Controller 的一个特化版本,专门用于构建 RESTful {/ ˈrestfl / }风格的Web服务。它组合了 @Controller 和 @ResponseBody 注解的功能。 -
返回类型:
在 @RestController 中,处理方法的返回值会直接作为HTTP响应的主体内容,而不会被解释为视图名称。通常,@RestController 返回的是JSON或XML格式的数据。 -
视图解析:
由于 @RestController 不涉及视图解析,因此它不关心视图名称或视图解析器。
你们项目是如何配置全局异常处理的
在项目中配置全局异常处理是一种良好的实践,可以帮助开发人员更好地管理和处理应用程序中的异常情况。在Java中,特别是在Spring框架中,可以通过@ControllerAdvice注解来实现全局异常处理。全局异常处理的配置可以在整个应用程序中捕获未处理的异常,并提供统一的方式来处理这些异常,从而提高应用程序的健壮性和可维护性。
- 创建自定义异常
- 创建全局异常处理器
- 创建测试控制器
为什么要配置全局异常处理呢?全局异常处理的好处包括:
统一异常处理:通过配置全局异常处理,可以确保应用程序在遇到异常时有一致的处理方式,避免在各个地方重复编写异常处理逻辑,提高代码的复用性和可维护性。
提高用户体验:全局异常处理可以帮助应用程序更友好地向用户展示错误信息,提高用户体验。例如,可以统一返回特定格式的错误信息或页面,让用户更容易理解和处理异常情况。
便于日志记录和监控:通过全局异常处理,可以集中记录应用程序中的异常情况,便于日志记录和监控。这有助于开发人员及时发现和解决潜在的问题。
提高应用程序的稳定性:通过统一处理异常,可以避免未捕获的异常导致应用程序崩溃或出现不可预料的行为,从而提高应用程序的稳定性。
在Spring框架中,可以通过创建一个带有@ControllerAdvice注解的类来实现全局异常处理。在这个类中,可以定义异常处理方法,针对不同类型的异常进行处理,以及返回适当的响应给客户端。这样可以确保应用程序在发生异常时有统一的处理逻辑。
@Resource和@Autowired
spring中,@Resource和@Autowired都是做bean的注入时使用。使用过程中,有时候@Resource 和 @Autowired可以替换使用;有时,则不可以。
共同点
- @Resource和@Autowired都可以作为注入属性的修饰,在接口仅有单一实现类时,两个注解的修饰效果相同,可以互相替换,不影响使用。
不同点
- @Resource是Java自己的注解,@Resource有两个属性是比较重要的,分是name和type;Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName自动注入策略。
- @Autowired是spring的注解,是spring2.5版本引入的,Autowired只根据type进行注入,不会去匹配name。如果涉及到type无法辨别注入对象时,那需要依赖@Qualifier或@Primary注解一起来修饰。
什么是spring的ioc
Spring的IoC(控制反转)是Spring框架的一个关键概念,它指的是将对象的创建和依赖关系的管理交给Spring容器来完成,而不是在代码中直接创建和管理对象。IoC的核心思想是将对象之间的依赖关系从程序中剥离出来,通过外部配置来管理,从而实现了松耦合(Loose Coupling)。
在Spring中,IoC容器负责实例化、配置和组装对象,使得对象之间的依赖关系可以通过配置文件进行定义和管理。通过IoC容器,我们只需要在配置文件中声明对象之间的依赖关系,而不需要在代码中直接实例化对象或者通过构造函数传递依赖,从而使得系统更加灵活、可扩展和易于维护。
Spring提供了两种主要的IoC容器:BeanFactory和ApplicationContext。BeanFactory是Spring框架最基础的IoC容器,提供了基本的IoC功能,而ApplicationContext是BeanFactory的扩展,提供了更多的企业级特性,如国际化支持、事件发 布、资源加载等功能。
总的来说,Spring的IoC容器实现了控制反转,将对象的创建和依赖关系的管理交给了容器,从而降低了组件之间的耦合度,提高了代码的灵活性和可维护性。
5.30日
ArrayList 和 LinkedList 有什么区别,分别适用于那种开发场景
ArrayList 和 LinkedList 在Java中是两种不同的数据结构实现,它们适用于不同的开发场景。
ArrayList 是基于动态调整大小的数组实现的 适用于频繁随机访问元素,它支持通过索引快速访问元素 ,内存占用方面比较紧凑 适合大量元素的存储
而 LinkedList 是基于双向链表实现的,适用于需要频繁删除、插入元素的场景,但是likedList不支持随机访问 是按照顺序遍历链表来访问元素的。
根据具体的需求和操作类型,选择合适的数据结构可以提高程序的效率和性能。一般来说,如果需要频繁随机访问元素,使用 ArrayList 更合适;如果需要频繁插入、删除元素,使用 LinkedList 更合适。
ArrayList 的扩容机制
ArrayList 的扩容机制是在元素数量超过当前容量时,会创建一个新的更大容量的数组,并将原数组中的元素复制到新数组中。
ArrayList 的底层是用动态数组来实现的。我们初始化一个ArrayList 集合还没有添加元素时,其实它是个空数组,只有当我们添加第一个元素时,内部会调用扩容方法并返回最小容量10,也就是说ArrayList 初始化容量为10。 当前数组长度小于最小容量的长度时(前期容量是10,当添加第11个元素时就就扩容),便开始可以扩容了,ArrayList 扩容的真正计算是在一个grow()里面,新数组大小是旧数组的1.5倍,如果扩容后的新数组大小还是小于最小容量,那新数组的大小就是最小容量的大小,后面会调用一个Arrays.copyof方法,这个方法是真正实现扩容的步骤。
是一个数组结构的存储容器 默认数组长度是10 也可以在构建arrayList的时候 指定初始长度,当我们不断在arrayList里面填加数据的时候当添加的数据超过了10 那么arrayList就没有多有的容量去存储后面的数据 那arrayList就会自动扩容
原理是什么
原理是为了保证在添加元素时能够保持较好的性能,避免频繁地进行数组扩容操作。
当 ArrayList 的容量不足以容纳新元素时,会执行以下操作:
创建一个新的数组,通常是当前容量的 1.5 倍大小。
将原数组中的元素复制到新数组中。
更新 ArrayList 内部的引用指向新数组。
添加新元素到新数组中。
这种扩容机制保证了在大部分情况下,添加元素的时间复杂度为 O(1)。但是,在扩容时会涉及元素的复制操作,因此在添加大量元素时可能会产生一定的性能开销。
了解 ArrayList 的扩容机制有助于理解其内部工作原理,以及在实际开发中更好地优化数据结构的使用。
ArrayList 和 LinkedList 有什么区别
ArrayList 是基于动态调整大小的数组实现的,而 LinkedList 是基于双向链表实现的。
- 内部实现:
- ArrayList 内部使用动态数组来存储元素,因此在读取数据时速度较快。
- LinkedList 内部使用双向链表来存储元素,不需要进行内存位移操作,因此在数据操作时速度较快
- 操作性能:
- ArrayList 适合频繁读取数据,对于访问数据较为高效,。
- LinkedList 适合频繁操作数据,对于数据操作(插入、删除)较为高效
- 接口实现:
- ArrayList 只实现了 List 接口,因此只能作为列表使用。
- LinkedList 实现了 List 和 Deque 接口,可以同时作为列表和队列使用。
- 访问时间:
- ArrayList 提供 O(1) 的时间复杂度用于基于索引的访问,但在插入和删除时为 O(n)。
- LinkedList 提供 O(1) 的时间复杂度用于插入和删除,但在基于索引的访问时为 O(n)。
- 内存使用:
- ArrayList 的元素存储在连续的内存位置,内存利用更高效。
- LinkedList 需要额外的指针1来存储前后节点的地址,因此内存消耗更大。
- 拓展 :
- 在双向链表(doubly linked list)中,每个节点除了存储数据外,还需要存储指向前一个节点和后一个节点的指针(或引用)。这两个指针分别称为 prev_node 和 next_node。通过这两个指针,可以在链表中轻松地进行向前和向后遍历,实现双向的数据访问和操作。
- 由于每个节点都需要存储额外的指针来指向前后节点,这就导致了 LinkedList 内存消耗更大的情况。相比之下,像 ArrayList 这样的动态数组结构只需要存储元素本身,不需要额外的指针来连接节点,因此在内存消耗上通常会更加高效。
分别适用于那种开发场景
ArrayList 适用于需要频繁随机访问元素的场景,因为它支持通过索引快速访问元素,时间复杂度为 O(1)。另外,ArrayList 在内存占用方面比较紧凑,适合大量元素的存储。
LinkedList 适用于需要频繁插入、删除元素的场景,因为在链表中插入和删除元素的时间复杂度为 O(1)。但是,LinkedList 不支持随机访问,需要按顺序遍历链表来访问元素。
ArrayList 的扩容机制了解吗,
ArrayList 的扩容机制是在元素数量超过当前容量时,会创建一个新的更大容量的数组,并将原数组中的元素复制到新数组中。
ArrayList 扩容机制的原理
当 ArrayList 的容量不足以容纳新元素时,会执行以下操作:
- 创建一个新的数组,通常是当前容量的 1.5 倍大小。
- 将原数组中的元素复制到新数组中。
- 更新 ArrayList 内部的引用指向新数组。
- 添加新元素到新数组中。
- 默认长度10
这种扩容机制保证了在大部分情况下,添加元素的时间复杂度为 O(1)。但是,在扩容时会涉及元素的复制操作,因此在添加大量元素时可能会产生一定的性能开销。
指针实际上是指向内存中另一个位置的变量。在 Java 中,虽然没有直接的指针概念,但在实现 LinkedList 这种数据结构时,需要使用类似指针的概念来连接节点 ↩︎