面试准备

Spring

Spring

SpringBoot

SpringMVC

Mybatis

sql注入

Redis

java

jdk jre jvm

java1.8有什么特性

容器

ArrayList

  • 动态数组
  • 线程不安全(Vetor和CopyOnWriteArrayList是安全的)
  • 扩容机制
    • 扩容为原数组的1.5倍
      int newCapacity = oldCapacity + (oldCapacity >> 1))
      //这里oldCapacity 右移一位,其效果相当于oldCapacity /2。
      //因为位运算的速度远远快于整除运算。
      
    • 然后判断扩容后的容量(newCapacity)是否大于所需要的容量(minCapacity),若还是小于需要容量,那么就把需要容量当作数组的新容量。
    • 再检查新容量是否超出了ArrayList所定义的最大容量(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8)。如果超过了通过hugeCapacity函数来判断需要容量(minCapacity)是否大于最大容量(MAX_ARRAY_SIZE),如果minCapacity > MAX_ARRAY_SIZE,函数返回Integer.MAX_VALUE,如果minCapacity <=MAX_ARRAY_SIZE,函数返回MAX_ARRAY_SIZE。
    • 然后把原来数组的数据copy到这个新生成的数组中。
  • add函数(在结尾添加是O(1),在指定位置插入不是,需要把插入位置及之后的位置的元素都往后挪一格,通过System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)实现)
    先判断是否需要扩容(如果是指定index插入还会判定index是否在范围内), 然后把元素加入数组末尾。
    elementData[size++] = e
    
  • 查找(get函数)为O(1)复杂度
  • remove函数和在指定位置插入一样,需要把数组向前挪动一格,所以为O(n)

HashMap

  • 1.8之前底层为数组+链表=链表散列,数组为主体,存放键值对Entry列表,链表解决哈希冲突(拉链法),1.8开始当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(并且对应的table的长度最小为64时)。
    • 哈希冲突的解决办法:
      • 链表数组法(拉链法):每个键值对表长取模,如果结果相同,用链表的方式依次往后插入
      • 开放地址法 —— 线性探测:每个键值对表长取模,一旦发生冲突,就把位置往后加1,直到找到一个空的位置
      • 开放地址法 —— 平方探测:发生冲突以后添加的值为查找次数的平方
      • 开放地址法 —— 双哈希探测:这个有点复杂以后再看。
  • key——key.hashcode()——>hashCode——hash()[扰动函数]——>hash——(n-1)&hash[为数组长度]——>key对应当前元素位置
    • hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法,换句话说使用扰动函数之后可以减少碰撞。
    • 1.7和1.8的hash()实现不同,但是原理相同。
  • 一些常问的参数
    public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
     	// 序列号
    	private static final long serialVersionUID = 362498820763181265L;    
    	// 默认的初始容量是16
    	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;   
    	// 最大容量
    	static final int MAXIMUM_CAPACITY = 1 << 30; 
    	// 默认的填充因子
    	static final float DEFAULT_LOAD_FACTOR = 0.75f;
    	// 当桶(bucket)上的结点数大于这个值时会转成红黑树
    	static final int TREEIFY_THRESHOLD = 8; 
    	// 当桶(bucket)上的结点数小于这个值时树转链表
    	static final int UNTREEIFY_THRESHOLD = 6;
    	// 桶中结构转化为红黑树对应的table的最小大小
    	static final int MIN_TREEIFY_CAPACITY = 64;
    	// 存储元素的数组,总是2的幂次倍
    	transient Node<k,v>[] table; 
    	// 存放具体元素的集
    	transient Set<map.entry<k,v>> entrySet;
    	// 存放元素的个数,注意这个不等于数组的长度。
    	transient int size;
    	// 每次扩容和更改map结构的计数器
    	transient int modCount;   
    	// 临界值 当实际大小(threshold = capacity * loadFactor)超过临界值时,会进行扩容
    	int threshold;
    	// 加载因子,默认为0.75f
    	final float loadFactor;
    }
    
    • put方法(1.8)
      • 使用Node<K,V> tab储存数据(tab=table)
      • 首先判断table是否为空/长度是否为0
        • 为0,调用resize()扩容
        • 不为0,根据hash计算数组下标
          • 对应位置为null,直接插入
          • 对应位置有值(桶里已经存在元素)
            • 桶中第一个元素p(p=tab[(n-1)&hash])和插入元素的key是否相同
              • 相同,直接覆盖
              • 不同
                • p为树节点(p instanceof TreeNode),插入红黑树
                	e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                
                • p为链表节点,从头开始遍历链表。
                  • 判断是否达到链表末尾,如果到达在链表末尾,在末尾插入节点,然后判断节点个数是否达到阈值(是否需要转换成红黑树)break跳出循环;
                  • 如果没有到底链表末尾,继续循环,判断链表中的节点的key至与插入元素的key是否相同
                    * 如果存在相同的key,新值替换旧值,break跳出循环。
                    * 如果不存在相同的key,继续循环。
        • 最后判断当前size+1是否大于阈值,来决定是否扩容

为什么线程不安全
当有多个线程都对同意HashMap进行put操作,put操作扩容,会形成环。
怎么实现一个线程安全的HashMap

ConcurrentHashMap

多线程

进程与线程

  • 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
    在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
  • 线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
    Java的程序天生就是多线程,一个Java程序中除了有main主线程,还有处理reference的线程,调用对象finalize方法的线程等。

上下文切换

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换会这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。

死锁

  • 产生具备以下四个条件:
    • 互斥条件:该资源任意一个时刻只由一个线程占用。
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
  • 避免死锁:破坏死锁的四个条件
    • 破坏互斥条件
      这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
    • 破坏请求与保持条件
      一次性申请所有的资源。
    • 破坏不剥夺条件
      占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
    • 破坏循环等待条件
      靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

JVM

数据库

计算机网络

操作系统

高并发

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值