c面试笔记

c面试笔记,第1张

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

三大特征

栈中存的是基本数据类型的值,和引用类型在堆内存中的地址值 ==和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算法节省资源

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/738096.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-28
下一篇 2022-04-28

发表评论

登录后才能评论

评论列表(0条)

保存