Java基础

List和Set的区别

List

  1. 允许元素重复
  2. 允许元素为null
  3. 有序的,读取的顺序即为元素插入的顺序
  4. 常用的实现类有ArrayList、LinkedList

Set

  1. 不允许元素重复
  2. 只有一个元素可以为null
  3. 无序的(实际也是有顺序的,但是不是按照插入的顺序排列)
  4. 常用的实现类有HashSet、LinkedHashSet、TreeSet

HashSet是如何保证不重复的

  1. 如果hash码不同,则不是同一个元素,存
  2. 如果hash码相同,如果equals值不同,则存;否则不存

HashMap是线程安全的吗?为什么

  • 不是线程安全的
  • 可能在两个方面引起线程不安全
    • put时:若线程A、B同时将相同的key存入HashMap中,当A的线程查询到插入的位置时,线程B获得cpu并将数据插入HashMap,此时线程A重新获得cpu将数据从B更新为A,此时数据的一致性出现问题,丢失了B的数据
    • resize时:若线程A、B同时执行resize(),当线程A未执行完数据迁移时线程B获得cpu并顺利执行完resize操作,此时线程A重新获得cpu,因为jdk1.7中时逆序插入,所以此时的next会指向原map中上一个数据,该段数据会形成环形链表。此时再调用HashMap的get()方法时,会陷入死循环

HashMap的扩容过程

HashMap的扩容过程在resize()方法中实现,jdk1.7和jdk1.8的实现过程存在一部分的不同,故分别讲述

1. jdk1.7

.
这里写图片描述
除了计算新Map的容量之外,核心的数据迁移在transfer方法中。
这里写图片描述

遍历整个table,逐个迁移其中的链表数据

int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;

不难看出,根据单个entry的key的hash值和新的容量,求出新的entry在新的map中的位置,并且将当前的元素迁移到新的链表的头部,完成数据迁移,因此在jdk7中元素在对应的链表中是逆序插入的

2. jdk1.8

这里写图片描述
jdk1.8的做法与1.7略微有些出入。
遍历table,遍历单个entry的链表,如果

e.hash & oldCap == 0

将当前元素插入到与原Map相同的table位置处,否则插入到j+oldCap位置处,见代码,为方便理解,做简化

if(e.hash & oldCap==1){
	newTab[j + oldCap] = e;
}

不难看出,此时元素在对应的链表中是顺序插入的
因此,其他博文中说的HashMap在resize()时同一个链表中的数据是逆序插入的是jdk1.8之前的说法

final、finally、finalize的区别

final

  • 修饰符号,可用于修饰变量、方法、类
  • 被final修饰的变量不可以在初始化后被修改,但可以在不同阶段被初始化
    ```
    private final int a = 3;
    private final int a;
    {
    a = 3;
    }
    private static final int a;
    static{
    a = 3;
    }
    private final int a;
    Constructor(){
    a = 3;
    }
     - 被final修饰的方法不可被重写,因此抽象方法不能被声明成final
     - 被final修饰的类不可被继承
    
    

finally

 - 用于try代码块
```
try{

}catch{

}finally{

}
```
```
try{

}finally{

}
```
 - 在finally代码块前若执行System.exit(code),则不会执行finally中的代码块
 ![这里写图片描述](https://img-blog.csdn.net/20180916162234697?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lvdWhhdmVhZ29vZGRheQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
	可以看出,程序先执行finally中的计算,再return结果值

finalize

 - 方法,在垃圾收集器将对象从内存中清理出去前会执行的方法
 - 一个对象只会执行一次,当该对象在垃圾回收器的预回收内存中时,在finalize方法中重新为该对象建立引用,可以将该对象重新变为强引用关系,但该操作不可进行第二次

强引用、软引用、弱引用、虚引用

  • 强引用:最普遍的引用,当对象存在强引用时,垃圾回收器一定不会回收该对象
  • 软引用:非必须引用,内存即将溢出时被垃圾回收器回收
  • 弱引用:第二次垃圾回收时回收
  • 虚引用:垃圾回收器回收时回收,无法通过引用获取到对象值

Java反射

反射机制:

在运行状态中,对于任意一个类,都可以知道他所有的方法和属性;对于任意一个对象,都可以调用他的所有方法和属性,这种动态获得信息和调用对象方法的功能叫做反射机制

什么是反射:

  • 间接操作对象,获取或者设置对象属性
  • 只要知道类的名称,就能够获得类的所有信息,从而调用类的所有方法和属性

反射的步骤

  • 获得类的上下文加载器:
    • ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  • 通过类全名获得类
    • Class clazz = classLoader.loadClass(path);
  • 通过类获得构造函数
    • Constructor cons = clazz.getConstructor(null);
  • 通过构造函数创建对象
    • Object obj = cons.newInstance();
  • 通过类获得方法
    • Method method = clazz.getMethod("methodName", paramsClass);
  • 调用方法
    • method.invoke(obj, params);

Arrays.sort()和Collections.sort()的实现原理

Arrays.sort()的实现原理

	Arrays的排序采用DualPivotQuicksort(双基准快速排序)算法

在这里插入图片描述
点进去可以看到,当数组的长度小于QUICKSORT_THRESHOLD(286)的时候,直接采用快速排序
在这里插入图片描述
当长度小于286的时候,会判断数组长度是否大于INSERTION_SORT_THRESHOLD(47),如果小于这个值,则采用插入排序,否则使用快速排序
在这里插入图片描述
当长度大于286的时候,会判断数组的连续性好不好;如果数组的连续性好,会采用归并排序,否则使用快速排序
在这里插入图片描述

总结:

  • 调用Arrays.sort()排序时,
  • 如果arr.length() < 47,会采用插入排序
  • 如果arr.length()>47 && arr.length()<286,则采用快速排序
  • 如果arr.length()>286 && 数组的连续性好,采用归并排序
  • 如果arr.length()>286 && 数组的连续性不好,采用快速排序

Collections.sort()的实现原理

Collections.sort()底层调用了Arrays.sort(T[] )

在这里插入图片描述
会发现,如果用户设置了使用归并排序的话,则使用归并排序;否则使用TimSort算法
在这里插入图片描述

LinkedHashMap的应用

概念

  • 是Map接口的哈希表和链表的实现,具有可预知的迭代性(顺序与put时一致)
  • 区别于HashMap,LinkedHashMap维护着一个运行于所有条目的双向链表,链表定义了迭代的顺序,该顺序可以插入、读取

实现

  • Jdk1.7和Jdk1.8的实现有所区别,这里先讲述jdk1.7的实现方式
  • Entry:除了保存当前对象的引用外,还保存了上一个引用before和下一个引用after,从而构成了双向链表
  • 初始化:调用了父类HashMap的构造函数,重写了init()方法
    • @Override
      void init() {
      header = new Entry<>(-1, null, null, null);
      header.before = header.after = header;
      }
  • 存储:重写了addEntry()和createEntry(),提供了双向链表的特性
  • 读取:重写了get()方法,在getEntry()后,判断当前的accessOrder为true的话,将最近访问的entry放到链表头,并从原来的位置删除
  • 排序模式:对于访问顺序,如果accessOrder为true,则使用插入排序;否则根据插入时候的迭代顺序

cloneable接口实现原理

什么是clone

对于给定一个对象o,获得另一个对象o’,o和o’类型相同,内容相同,则称o’是o的克隆或拷贝

什么时候使用克隆

当想要修改对象属性,又不想影响原来值的时候

Java对克隆的支持

  • Object类有clone方法,protected Object clone() throws CloneNotSupportedException
  • 提供Cloneable接口,表明该类可以被拷贝

浅克隆、深克隆

  • 浅克隆:Object克隆时对对象一无所知,仅进行域对域的复制
  • 深克隆:对对象的所有引用进行浅克隆

异常分类以及处理机制

异常分类

  • Throwable:所有异常的祖先,指定代码中可以用异常处理机制通过Java应用程序传输的任何问题的共性
    • Error: 程序无法处理的错误,表示运行应用程序中较为严重的问题,表示代码运行时JVM出现的问题,如虚拟机运行错误、内存溢出,Error发生时虚拟机将停止工作
    • Exception:
      • 运行时异常:都是Runable及其子类异常,包括空指针异常、数组下标越界异常
      • 非运行时异常(编译异常):非Runable以外的异常,类型上都是Exception子类

异常处理机制

  • 抛出异常:当一个方法出现错误并引发异常时,方法创建异常对象并交付运行时系统,异常对象包含异常类型和发生异常时的程序信息,运行时程序负责检测到异常并执行程序
  • 捕获异常:当方法抛出异常时,运行时程序会为之寻找合适的异常处理器

throws抛出异常的规则

  • 如果是不可查异常,可以不使用throws抛出,可以通过编译,程序运行时遇到抛出
  • 必须声明方法可抛出的任何可查异常
  • 仅当抛出了异常,该方法的调用者才必须处理或者抛出异常
  • 调用方法必须遵循任何可查异常的处理和声明规则

wait和sleep的区别

wait

  • 属于Object
  • 使对象放弃持有锁,并进入等待的线程池
  • 只有对该对象调用notify()才可以唤醒该对象

sleep

  • 属于Thread
  • 使程序等待一段时间,让出cpu给其他的程序,但他的监控程序一直在运行,当指定时间到达会自动进入运行状态
  • 期间不会释放锁

数组在内存中如何分配

数组在内存中的初始化

静态初始化

初始化时显式指定每个数组元素的初始值,由系统决定长度

动态初始化

初始化时显式指定数组的长度,由系统给每个元素分配初始值

数组的内存分配

int[] arr = new int[3];
在堆内存中开辟int[3],在栈内存中创建arr,在arr地址上存储int[3]的堆内存地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值