c面试笔记

面向对象

面向过程更注重事情的每一步

而面向对象更关注事情有哪些参与者(对象)、以及各自需要做什么举例如下

在这里插入图片描述

三大特征

在这里插入图片描述

在这里插入图片描述

栈中存的是基本数据类型的值,和引用类型在堆内存中的地址值

==和equals

堆内存中有常量池如String s = “abc”;在常量池中分配,堆内存中还有对象-----
----(Object)的数据

在这里插入图片描述

比如String重写equals

在这里插入图片描述

在这里插入图片描述

concurrenthashmap

为什么用

HashTable 是线程安全的。HashTable 容器使用 synchronized 来保证线程安全,但在线程竞争激烈的情况下 HashTable 的效率非常低下.

数据结构和原理

使用ConcurrentHashMap ,分段锁的思想,将 HashMap 进行切割,把 HashMap 中的哈希数组切分成小数组,每个小数组有 n 个 HashEntry 组成,其中小数组继承自ReentrantLock(可重入锁),这个小数组名叫Segment(JDK1.7

在这里插入图片描述

在这里插入图片描述

JDK 采用自旋锁+锁膨胀的机制进行加锁,也就是自旋获取锁,当自旋次数超过64时,会发生膨胀,直接陷入阻塞状态,等待唤醒。并且在整个put操作期间都持有锁。

JDK1.8中取消了Segment 分段锁,采用 CAS + synchronized 来保证并发安全,ConcurrentHashMap 中 synchronized 只锁定当前链表或红黑二叉树的首节点,只要节点 hash 不冲突,就不会产生并发

只需要一次定位,如果对应下标处没有结点,说明没有发生哈希冲突,此时直接通过CAS进行插入,若成功,直接返回。若失败,则使用synchronized进行加锁插入。

锁的粒度从段锁缩小为结点锁,并且不是每次都要加锁了,cas尝试失败了再加锁

put()方法中 初始化数组大小时,1.8不用加锁,因为用了sizeCtl这个变量,为-1时表示 table正在初始化,-(n+1)时表示正在扩容,正数表示扩容时的阈值

在这里插入图片描述


synchronized是jvm层面的锁,自动释放锁

reentrantlock是api层面的锁,需要手动unlock()释放锁

reentrantlock

等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。

1.7、1.8区别

1.71.8
数据结构ReentrantLock、Segment、HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构Synchronized、CAS、Node、红黑树,Node的val和next都用volatile修饰,保证可见性,查找替换赋值都用CAS
扩容segment每个元素内部进行扩容,不影响其他的segment元素如果某个线程put时发现concurrenthashmap正在扩容时,那么改线程会一起进行扩容,如果没有扩容则吧key value添加到map中,然后判断是否超过了阈值判断是否扩容
支持多个线程同时扩容,扩容之前也会生成一个新的数组,每个线程负责单独的部分转移
元素查询两次hash,第一次定位到segment,第二次定位到元素所在链表的头部查找替换赋值都用CAS(性能较高)
加锁put对于当前的segment加锁,get无需加锁,volatile保证可见性,Node用volatile修饰锁链表的head结点,不影响其他元素的读写,粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容,读操作无锁,volatile保证可见性,val和next都用volatile修饰,数组也用volatile修饰

final关键字

final含义

可以修饰类、方法、成员变量、局部变量

修饰类

表示类不可被继承

修饰方法

表示方法不能被重写,但是可以重载

修饰变量

表示一旦被赋值不能更改为其它值

修饰成员变量

1.如果是普通成员变量只能在普通代码块内初始化,构造方法中初始化或声明时初始化值

2.如果是类变量(静态成员变量),可以在静态代码块中初始化,或声明时初始化

修饰局部变量

必须由程序员显式的初始化,在使用之前进行初始化(只能初始化一次)

修饰基本数据类型

数值一旦被初始化不能修改

修饰引用数据类型

在初始化后,其引用的地址不能变(不能指向另一个对象)。但是引用的值可以改变


为什么局部的内部类和匿名的内部类只能访问局部final变量

编译之后会生成生成两个class文件,Test.class、Test1.class
在这里插入图片描述

在这里插入图片描述

局部变量

在这里插入图片描述

成员变量

在这里插入图片描述

Reentrantlock

Reentrantlock(可重入锁)

可重入锁,可以在锁的内部再加锁(加几次锁,释放几次锁)

可以实现公平锁,也可以实现非公平锁

公平锁和非公平锁都使用AQS

公平锁非公平锁
加锁时,检测AQS是否有线程排序,如果有则,当前线程到AQS排队加锁时,先判断时候加到锁,没有竞争到锁则到AQS排队
有线程排队则唤醒排在AQS前面的线程没有竞争到锁会唤醒排在AQS前面的线程

AQS

AQS就是并发包下的一个基类

AbstractQueuedSynchronizer类中,有几个属性和一个双向队列 (CLH队列)

内部类

static final class Nodes {
//排他锁的标识
static final Node EXLUSIVE = null;
//如果带有这个标识,证明是失效了
static final int CANCELLED = 1;
//具有这个标识,说明后继结点需要被唤醒
static final int SIGNAL = -1;
//Node对象存储标识的地方
volatitle int waitStatus;
//指向上一个结点
volatitle Node prev;
//指向下一个结点
volatitle Node next;
//当前Node绑定的线程
volatitle Thread thread;
//返回前驱加点,如果前驱加点为null,抛出NPE
final Node predecessor() throws NullPointerException {
    Node p = prev;
    if(p == null){
      throw new NullPointerException();
    }else{
      return p;
    }
  }
}

在这里插入图片描述

双亲委派机制

jvm理解

java源程序->.class(字节码文件对象)->jvm运行

什么是OOM

什么是栈溢出StackOverFlowError?怎么分析?

内存快照如何抓去,怎么分析Dump文件?

谈谈JVM中,类加载器你的认识

在这里插入图片描述

在这里插入图片描述

1.jvm的位置

jvm是位于操作系统之上的

在这里插入图片描述

2.jvm的体系结构

在这里插入图片描述

3.类加载器的作用

加载Class文件

类加载器
  1. 启动类(根)加载器
  2. 扩展类加载器
  3. 应用程序加载器
  4. 自定义类加载器

每个加载器都有属于自己的加载范围如根加载器(java_home下的rt.jar)扩展类加载器 (java_home下jre的ext文件夹)

双亲委派机制:当一个加载器收到类加载请求会传递给他的父亲加载器加载,因此最终都会从根加载器加载,然后加载器会检查是否能加载该类,否则抛出异常通知子加载器加载 -------》默认情况下,一个限定名的类只会被一个类加载器加载并解析使用,这样在程序中,它是唯一的,不会产生歧义

ArrayList和LinkedList

数据结构

ArrayList是用一个长度数组实现

LinkedList使用链表实现

使用场景

因为ArrayList用数组实现,用下标可以直接访问元素,所以查询效率很快,适合查询频繁的情况,但因为其空间连续,删除和插入元素会让部分元素移动位置,进而消耗资源,所以不适合插入、删除频繁的情况

LinkedList用双向链表实现,删除和插入元素直接修改结点的指向就行,所以插入、删除效率很高,适合插入、删除频繁的情况,但是查找元素会遍历整个链表,消耗资源所以不适合查询频繁的情况

空间

LinkList需要更多的内存空间,因为它除了要存储数据之外,还需要存储该节点的前后节点信息,而ArrayList索引处就是存的数据

ArrayList扩容

初始化容量为10,第一次添加元素的时候才会初始化容量为,默认为10

每次扩容时原来的1.5倍,会将老数组中的元素重新拷贝一份到新的数组中

区别

ArrayListLinkedList
数据结构长度可变的数组 (扩容影响效率)双向链表(存指针,更占空间)
连续空间(随机访问快速)可以不连续
实现接口实现List实现List、Dequeue(可实现双端队列)
使用场景随机访问频繁插入,删除频繁
使用尾插法并指定初始容量可以极大提升性能、甚至超过LinkedList需要创建大量Node结点,损耗空间

ThreadLocal

方法

static final ThreadLocal<T> localVar = new ThreadLocal<T>();
//设置线程中本地变量的值
localVar.set()
//获取线程中本地变量的值
localVar.get()
//清除本地内存中的本地变量
localVar.remove();

作用

使用Threadlocal绑定的变量,会将其缓存再线程内部,线程访问改变量时访问的是缓存的内容

每个线程对象内部都有一个ThreadLocalMap key为ThreadLocal对象,value为缓存的值

当我们在创建一个变量后,每个线程对其进行访问的时候访问的都是线程自己的变量

线程隔离

内存泄露问题

使用线程池时,线程不会被销毁,内部缓存的对象不会被垃圾回收,如果不断创建这样的对象,会造成内存空间被一步一步侵蚀

解决

1.在使用完毕后应该用remove方法清楚本地内存中的本地变量

2.将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

场景

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。

2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。

Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务
过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离
性。Spring框架里面就是用的ThreadLocal来实现这种隔离

对象创建的过程

对象创建的过程

.java----javac---->.class-----ClassLoader

1.加载类到内存

2.半初始化

3.init

由类加载器把类加载进内存,

初始化过程,主要是对目标类里面的静态变量,成员变量,静态代码块进行初始化,当目标类被初始化后就可以从常量池里找到类元信息了

之后jvm会把目标对象里的普通成员变量初始化为0值

(int初始化为0,string初始化为null)

执行目标对象内部生成的init方法,进一步初始化成员变量的值

执行构造块,最后调用对象的构造方法

对象在内存中的存储

markword 8

classpointer 4

instance data

padding

Object o = new Object();占多少字节?

16字节,因为makword占8字节,classpointer占4字节,没有成员变量所以没有实例数据,然后对齐4字节

hashmap

哈希算法

根据任意长度计算出固定长度的值

好的哈希算法

1.高效计算出目标值,长文本也能快速计算出hash值

2.根据hash值很难推出原文

3.尽可能散列

4.原始信息一点不同,hash值都有很大区别

数据结构

1.8是数组+链表+红黑树

每个数据单元是一个node结构

node结构有key、value、next、hash字段

默认初始散列表数组长度为16

散列表(数组)是懒加载在第一次put时才会创建数组
默认负载因子0.75 用来计算扩容的阈值 默认情况下扩容阈值是12(16*0.75)

链表转红黑树

链表长度大于8,数组长度大于等于64时进行树化

怎么计算hash值

计算key的hash值,高16和低16异或运算

在这里插入图片描述

put过程四种情况
  1. slot为null,则直接把key和value包装为node放入slot
  2. slot不为null,则判断key是否完全相等,相等的话就调用replace方法替换,没有链化的话,不等就尾插法追加,如果已经链话则,继续比较下一个结点,如果key完全相等则替换,最后如果到最后一个结点不想等则追加,然后判断时候打到阈值,判断时候进行树化

扩容

到达阈值后进行左移运算

不*2因为位运算性能高




hashmap1.7和1.8

共同

先根据key计算出hash值与数组长度-1相与,计算出在数组中的下标

区别
1.71.8
数据结构数组、链表数组、链表、红黑树
结点封装为Entry封装为Node
put过程根据key计算出下标,则判断数组位置是否为空,为空则直接放入元素,不为空则认定为一个链表,遍历链表如果遇到key相同的就直接更新 value,如果没有相同,之后判断是否需要扩容需要扩容则扩容到原来的两倍,之后采用头插法插入根据key计算出下标,则判断数组位置是否为空,为空则直接放入元素,不为空则判断当前结点为链表还是红黑树(为链表则封装为链表结点,为红黑树则封装为一个红黑树结点),遍历链表或红黑树如果遇到key相同的就直接更新 value,如果没有相同key则采用尾插法插入元素,最后判断时候进行扩容,或者树化
扩容树化当链表结点数>8,数组长度大于等于64时,将当前链表转化为红黑树(当结点数<=6时退回链表),扩容当hashmap中元素个数>=阈值(数组长度*0.75)时进行扩容,长度左移
插入元素头插法,插入前判断是否扩容尾插法,插入后判断时候扩容
hash算法较为复杂简化,因为红黑树提高了插入查询效率,可以简化hash算法节省资源
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值