Java基础(1)
一、 java的集合
java主要有四种集合,Map,Set,List, Queue
1. ArrayList和LinkedList有什么区别
-
•
内部实现
-
•
ArrayList是动态数组实现,底层使用数组来存储元素。
-
•
LinkedList是链表实现,底层使用双向链表来存储元素。
-
-
•
访问效率
-
•
ArrayList支持随机访问,可以通过下标快速定位元素,时间复杂度为O(1)。
-
•
LinkedList不支持随机访问,需要从头或尾遍历链表,时间复杂度为O(n)。
-
-
•
插入和删除效率
-
•
ArrayList在插入和删除元素时,可能需要移动后续的元素或者进行扩容,时间复杂度为O(n)。
-
•
LinkedList在插入和删除元素时,只需要修改指针的指向,时间复杂度为O(1)。
-
-
•
内存占用
-
•
ArrayList的内存占用与数组的长度成正比,可能存在空间浪费。
-
•
LinkedList的内存占用与节点的个数成正比,每个节点除了存储数据外,还需要存储两个指针。
-
-
•
适用场景
-
•
ArrayList适合于频繁访问和少量插入删除的场景。
-
•
LinkedList适合于频繁插入删除和少量访问的场景。
-
2. 对List集合去重都有哪些方法
-
•
使用Set集合的特性,将List转换为Set,再转换回List,可以实现无序的去重。
-
•
优点:简单方便,代码量少,效率高。
-
•
缺点:不能保持原来的顺序,可能会丢失重复元素的信息。
-
-
•
使用两个for循环遍历List,比较元素是否相等,如果相等则删除重复元素,可以实现有序的去重。
-
•
优点:能保持原来的顺序,不需要额外的空间。
-
•
缺点:代码量多,效率低,时间复杂度为O(n^2)。
-
-
•
使用List的contains方法判断元素是否已经存在于另一个新的List中,如果不存在则添加,可以实现有序的去重。
-
•
优点:能保持原来的顺序,代码量少,易于理解。
-
•
缺点:效率低,时间复杂度为O(n^2),需要额外的空间。
-
-
•
使用Java 8的Stream API,调用distinct方法对List进行去重,可以实现有序的去重。
-
•
优点:简洁优雅,利用了函数式编程的特性,效率高。
-
•
缺点:需要Java 8或以上版本支持,可能不够灵活。
-
-
•
使用TreeSet集合,并实现Comparator接口,根据对象的某个属性进行去重和排序。
-
•
优点:能根据自定义的规则进行去重和排序,适用于复杂对象的去重。
-
•
缺点:需要实现Comparator接口,代码量多,可能会改变原来的顺序。
-
3. 数组和链表分别适用于什么场景,为什么
-
•
存储空间的要求
-
•
数组需要一块连续的内存空间来存储元素,而链表可以利用零散的内存空间,因此链表更适合于内存空间紧张或者不确定需要多少空间的情况。
-
•
数组在初始化时就确定了大小,不能动态扩展,而链表可以根据需要动态增加或减少节点,因此链表更适合于元素个数不确定或者频繁变化的情况。
-
-
•
访问速度的要求
-
•
数组支持随机访问,可以通过下标快速定位元素,时间复杂度为O(1),而链表不支持随机访问,需要从头或尾遍历链表,时间复杂度为O(n),因此数组更适合于需要频繁访问或者按照顺序访问的情况。
-
•
链表在访问时需要额外的空间来存储指针信息,而数组只需要存储元素本身,因此数组更适合于对空间敏感或者元素较大的情况。
-
-
•
插入和删除操作的要求
-
•
数组在插入和删除元素时,可能需要移动后续的元素或者进行扩容,时间复杂度为O(n),而链表在插入和删除元素时,只需要修改指针的指向,时间复杂度为O(1),因此链表更适合于需要频繁插入删除或者在任意位置插入删除的情况。
-
•
链表在插入和删除时需要额外的操作来维护指针信息,而数组只需要修改元素本身,因此数组更适合于对操作简单或者元素较小的情况。
-
4. ArrayList和linkedList底层数据结构是什么
ArrayList底层的数据结构是Object数组,数组的结构特点就是查询快增删慢,因此多用于一些查询多增删少的场景,
LinkedList 底层数据结构是双向链表,这种数据结构的特点就是增删快,查询慢,因为增删对于链表来说只是修改上下节点的引用,而查询则需要对链表进行遍历
ArrayList:数组实现,增删慢,查询快,线程不安全
Vector:数组实现,增删慢,查询快,线程安全
LinkedList:双向链表实现,增删快,查询慢,线程不安全
5. ArrayList的扩容机制
在new时初始化一个空数组,当第一次add时初始化容量为10,当数组满了元素需要继续添加时,会继续扩容,扩容容量为原容量+原容量的一半,扩容操作有复制数组的操作,可能会影响效率
6. set集合 如何保证元素不重复
先说结论:通过hashcode方法和equals方法来保证元素不重复,当一个元素添加进set集合中时,首先会调用hashcode方法算出该元素的hash值,并与hash表进行比较,若该hash值下不存在元素,说明这个元素没有重复,若这个hash值下有元素,则可能有重复元素,接下来会调用equals方法将该元素与该hash值下的所有元素进行匹配,若有相同的的判定为重复元素,若没有相同的,则认定为一个新元素,可以添加进set集合中
7. HashSet的原理是什么
HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75的HashMap。HashSet封装了一个HashMap对象来存储所有的集合元素,所有放入HashSet中的集合元素实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。
当我们向HashSet中添加元素时,HashSet会调用该元素的hashCode()方法来计算出该元素的哈希值,然后根据哈希值计算出该元素在数组中的位置。如果该位置没有其他元素,就直接将该元素存储在该位置。如果该位置已经有其他元素了,就调用equals()方法来比较两个元素是否相等,如果相等则不存储该元素,如果不相等则通过链式地址法的方式往后插入,直到找到链表最后的位置为止,然后将该元素存储在该位置。
8. TreeSet在排序时是如何比较元素的
参考链接:
java 中 TreeSet 集合中的比较器(Comparable 和 Comparator) - 知乎 (zhihu.com)
如果不指定自定义的比较器 Comparator,那么插入的对象必须实现 Comparable 接口,元素按照实现此接口的 compareTo() 方法去排序。
TreeSet底层使用红黑树实现
9. HashMap 的扩容机制
hashMap的底层是通过数组加链表加红黑树实现,数组存储hash值对应的元素,当有hash冲突时,会使用尾插法,将元素插入到hash对应元素的下一个节点,在jdk8中引入了红黑树,是为了能够快速查找节点,红黑树时间复杂度为O(logn),当链表长度大于等于8时,会将链表重构成红黑树
在new一个新的hashMap时,会根据传入的参数来初始化容量,如果没有传参那初始容量就是0,只有在第一次put的时候,才会进行扩容
默认第一次扩容的初始容量为16,负载因子为0.75,在put的元素达到容量乘以负载因子时,会触发扩容机制,每次扩容会直接扩2倍,例如初始化容量为16的hashMap元素达到12时,触发扩容,扩容后的容量为32,第二次扩容的触发阈值为32*0.75=24;
10. hashmap是如何快速定位到数据的
hashMap是基于散列的一种集合,每添加一个元素都会通过键的hashcode方法先计算出键的散列值,然后通过取余计算出对应数组的下标位置,在查询的时候也是通过计算一次散列值,然后取余算出数组下标,如果这个下标有多个元素形成了链表,那么就会调用equal方法遍历链表直到equals方法返回true,如果链表长度超过8,此时链表会转换成红黑树,再遍历红黑树,红黑树相对链表查询效率会更高一点
11. ConcurrentHashMap是如何保证线程安全的
主要是通过CAS和synchronized+volatile实现,concurrenthashmap对value和数组使用volatile修饰,使得修改对每个线程都是可见的,对于更新操作,先通过CAS判断是否有其他线程修改过,随后使用synchronized锁将元素添加进链表或红黑树中
12. 常用的map集合有哪些
HashMap最常用的map集合,相对高效,但是线程不安全,对于查询和插入的效率较高,适合一些需要快速存取的场景,如缓存,计数器等,适用于单线程下
linkedhashMap:常用于一些需要通过插入顺序来进行遍历的情况,非线程安全
treeMap,常用于一些对于键值对需要自然排序或者自定义排序的情况
13. hashmap是线程安全的吗
hashmap不是线程安全的,他不能保证在多线程的情况下的正确性和一致性
hashmap内部结构是一个数组,每个数组元素是一个链表,用来存储hash值相同的键值对,当多个线程同事对hashmap进行插入或删除操作时可能会对链表结构出现破坏,比如环形链表,丢失节点等
hashMap扩容是插入时达到阈值触发扩容,当多线程同时扩容可能会导致数据的覆盖或者丢失
hashmap的读取操作是不加锁的, 当一个线程在读取hashmap的某个键值对时,若此时有其他线程正在修改,那么读取的线程可能读取的是错误的或过期的数据
14. hashmap的内部数据结构是什么
hashMap 内部的数据结构就是数组+链表+红黑树,一个数组和若干链表或红黑树,数组的每个元素都是一个节点,包含键值对和next下个节点的位置
当一个键值对插入到hashmap里面时会先计算出键的hash值,当再判断是否有hash冲突,若已经存在有相同的hash值的元素,那么再调用元素本身的equal方法判断是否与冲突的元素相等,若相等则不插入,若不相等则将元素向链表后面插入,在jdk1.8之前,hashmap使用的是链表来解决冲突,但是当链表过长时,查询的效率会非常低下,所以在1.8引进了红黑树,当链表的长度大于等于8时将链表重构成红黑树,大大提升查询效率
15. hashmap在java8中引入红黑树有什么好处
可以增加查询的效率,在jdk1.8之前,使用的是单纯的链表,但是当链表越来越长时,对查询插入和删除的效率都会变得非常低,因为需要一个一个节点去遍历,时间复杂度为On,红黑二叉树是一种平衡搜索树,他的查询效率在最坏情况下更好,为Ologn
16. hashmap和hashtable的区别是什么
线程安全性:hashMap是非线程安全的,只适用于单线程环境,多线程环境下需要使用Collections.synchronizedMap方法来包装一个同步的map集合。hashtable是线程安全的,它的方法都使用了synchronized关键字来保证同步访问,但是这也导致了它的效率较低。 null键和null值:hashMap可以接受null作为键(key)或者值(value),并且可以有多个null值,但只能有一个null键。hashtable不允许null作为键(key)或者值(value),否则会抛出NullPointerException异常。 继承关系:hashMap继承自AbstractMap类,实现了Map、Cloneable、Serializable接口。hashtable继承自Dictionary类,实现了Map、Cloneable、Serializable接口。Dictionary类是一个已经被废弃的抽象类,不建议使用。 初始容量和扩容机制:hashMap的初始容量为16,每次扩容时容量翻倍,即乘以2。hashtable的初始容量为11,每次扩容时容量增加一倍再加1,即乘以2再加1。两者的加载因子默认都是0.75,当元素数量超过容量乘以加载因子时,就会触发扩容操作。 计算哈希值的方法:hashMap对键(key)的哈希值进行了二次哈希处理,以获得更好的散列分布。hashtable直接使用键(key)的哈希值进行取模运算,以确定元素在数组中的位置。
二、异常
1. java中异常的处理机制有哪些
Java 中有两种主要的处理异常的机制,分别是 try-catch-finally 和 throws
2. Error和exception的区别是什么
Error 和 Exception 是 Java 中两种不同的异常类,它们都继承自 Throwable 类,但是有以下区别:
Error 表示严重的错误,通常是由 JVM 或系统引起的,不应该由程序员处理。例如,内存溢出、栈溢出、虚拟机错误等。123 当 Error 发生时,程序会立即崩溃,Java 虚拟机会停止运行。2 Exception 表示程序可以处理的异常,可以被捕获或抛出。例如,空指针、数组越界、文件找不到等。当 Exception 发生时,程序可以通过 try-catch-finally 或 throws 语句来处理异常,使程序恢复运行或终止执行。 Exception 又分为两种:运行时异常(RuntimeException)和受检查的异常(Checked Exception)。
RuntimeException 是一种非受检异常,也就是说,它不需要在方法声明中使用 throws 子句来指定。如果一个方法可能抛出 RuntimeException 或其子类的异常,那么它可以选择性地使用 try-catch-finally 捕获并处理,也可以不使用。 运行时异常一般是由程序逻辑错误导致的,可以通过修改代码来避免。 Checked Exception 是一种受检异常,也就是说,它必须在方法声明中使用 throws 子句来指定。如果一个方法可能抛出 Checked Exception 或其子类的异常,那么它必须使用 try-catch-finally 捕获并处理,或者继续用 throws 声明抛出。1如果没有处理或声明受检异常,编译器会报错。受检异常一般是由外部错误导致的,例如文件找不到、网络连接失败等,这些错误并不是程序本身的错误,而是在应用环境中出现的外部错误。
3. throw和throws区别
throw:
表示方法内抛出某种异常对象 如果异常对象是非 RuntimeException 则需要在方法申明时加上该异常的抛出 即需要加上 throws 语句 或者 在方法体内 try catch 处理该异常,否则编译报错 执行到 throw 语句则后面的语句块不再执行
throws:
方法的定义上使用 throws 表示这个方法可能抛出某种异常 需要由方法的调用者进行异常处理
-
•
throw用于在方法内部主动抛出一个异常对象,通常是在遇到错误或异常情况时使用。例如,如果一个方法的参数为null,可以使用throw new NullPointerException()来抛出一个空指针异常。
-
•
throws用于在方法声明中指定该方法可能抛出的一个或多个异常类型,通常是在调用其他可能抛出异常的方法时使用。例如,如果一个方法要读取一个文件,可以使用throws IOException来声明该方法可能抛出一个输入输出异常。
-
•
throw会中断方法的执行流程,转入异常处理流程,而throws不会影响方法的执行流程,只是告诉调用者该方法可能会抛出异常。
-
•
throw可以抛出任何类型的异常,无论是受检查的还是非受检查的,而throws只能声明受检查的异常,即除了RuntimeException及其子类以外的Exception子类。
三、反射
1. java反射机制的作用是什么
java反射机制创建对象的方式有哪些
java是如何实现动态调用某个方法的