相关知识点

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012070360/article/details/81663650

1:tcp/ip协议

2:Socket原理

3:多进程相关的

4:View事件分发源码

5:HashMap源码,原理,增删的情况后端数据结构如何位移,如何变得线程安全,每种方式的优缺点

HashMap内部通过维护一个Entry<K, V>数组(变量为table),来实现其基本功能,而Entry<K, V>是HashMap的内部类,其主要作用便是存储键值对,其数据结构大致如下图所示。

从Entry的数据结构可以看出,多个Entry是可以形成一个单向链表的,HashMap中维护的Entry<K, V>数组(之后简称为Entry数组,或table,容易区分)其实就是存储的一系列Entry<K, V>链表的表头。那么HashMap中存储数据table数组的数据结构,大致可以如下图所示(假设只有部分数据)。

注:Entry数组的默认长度为16,负载因子为0.75。

将上图中的每一行,称为桶(bucket),那么table的索引便是bucketIndex。而HashMap中的插入、获取、删除等操作最主要的便是对table和桶(bucket)的操作。下面将主要通过插入操作,看其数据结构的变化

在整个插入操作中,有一个很重要的操作,便是对table数组扩容,扩容的算法相对简单,但是在多线程下它却容易引发一个线程安全的问题。

注:扩容需要会把原先table中的值移动到新的数组中,再赋值给table变量,一个合适的初始大小和负载因子能够提高效率。

4. 线程不安全

在多线程环境下,假设有容器map,其存储的情况如下图所示(淡蓝色为已有数据)。

此时的map已经达到了扩容阈值12(16 * 0.75 = 12),而此时线程A与线程B同时对map容器进行插入操作,那么都需要扩容。此时可能出现的情况如下:线程A与线程B都进行了扩容,此时便有两个新的table,那么再赋值给原先的table变量时,便会出现其中一个newTable会被覆盖,假如线程B扩容的newTable覆盖了线程A扩容的newTable,并且是在A已经执行了插入操作之后,那么就会出现线程A的插入失效问题,也即是如下图中的两个table只能有一个会最后存在,而其中一个插入的值会被舍弃的问题。

这便是HashMap的线程不安全性,当然这只是其中的一点。而要消除这种隐患,则可以加锁或使用HashTable和ConcurrentHashMap这样的线程安全类,但是HashTable不被建议使用,推荐使用ConcurrentHashMap容器。

从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。

在ConcurrentHashMap中,就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中:

ConcurrentHashMap中默认是把segments初始化为长度为16的数组。

根据ConcurrentHashMap.segmentFor的算法,3、4对应的Segment都是segments[1],7对应的Segment是segments[12]。

(1)Thread1和Thread2先后进入Segment.put方法时,Thread1会首先获取到锁,可以进入,而Thread2则会阻塞在锁上:

(2)切换到Thread3,也走到Segment.put方法,因为7所存储的Segment和3、4不同,因此,不会阻塞在lock()

以上就是ConcurrentHashMap的工作机制,通过把整个Map分为N个Segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。

6:android动画原理

7:虚拟机相关

8:Object类你知道的方法

clone()

clode()方法又是一个被声明为native的方法,因此,我们知道了clone()方法并不是Java的原生方法,具体的实现是有C/C++完成的。clone英文翻译为”克隆",其目的是创建并返回此对象的一个副本。形象点理解,这有一辆科鲁兹,你看着不错,想要个一模一样的。你调用此方法即可像变魔术一样变出一辆一模一样的科鲁兹出来。配置一样,长相一样。但从此刻起,原来的那辆科鲁兹如果进行了新的装饰,与你克隆出来的这辆科鲁兹没有任何关系了。你克隆出来的对象变不变完全在于你对克隆出来的科鲁兹有没有进行过什么操作了。Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。

getClass();

getClass()也是一个native方法,返回的是此Object对象的类对象/运行时类对象Class<?>。效果与Object.class相同。

wait():

调用此方法所在的当前线程等待,直到在其他线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。

notify()/notifyAll():

唤醒在此对象监视器上等待的单个线程/所有线程。

finalize()

protected void finalize() throws Throwable {} 对象的"遗言"方法 当gc回收一个对象的时候,主动会调用这个对象的finalize方法 面试题:final和finalize之间的区别? final表示最终的 修饰符,可以修饰类、方法、变量 finalize是Object类里面的一个方法,当gc回收一个对象的时候会主动调用的一个方法

toString()

public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } 用来制定打印一个对象,显示的内容 当打印一个对象时,实际上打印的是该对象的toString方法,Object类本身打印对象,显示 类型@XX

equals()

public boolean equals(Object obj) { return (this == obj); } Object类本身比较的是两个对象的地址 程序员可以按照自己的意愿将内存里面不同的两个对象视为相等对象 -> 逻辑相等也就是说,制定一个类型比较的规则,当什么条件成立的时候,可以将两个不同的对象视为相等对象

hashCode()

public native int hashCode(); 制定一个对象的散列特征码 

9:你重写过hashcode和equals么,要注意什么,为什么要重写hashcode和equals

阅读HashMap的源码,我们可以看到,HashMap中实现了一个Entry[]数组,数组的每个item是一个单项链表的结构,当我们put(key, value)的时候,HashMap首先会newItem.key.hashCode()作为该newItem在Entry[]中存储的下标,要是对应的下标的位置上没有任何item,则直接存储上去,要是已经有oldItem存储在了上面,那就会判断oldItem.key.equals(newItem.key),那么要是我们把上面的Person作为key进行存储的时候,重写了equals()方法,但没重写hashCode()方法,当我们去put()的时候,首先会通过hashCode() 计算下标,由于没有重写hashCode(),那么实质是完整的Object的hashCode(),会受到Object多个属性的影响,本来equals的两个Person对象,反而得到了两个不同的下标。

同样的,HashMap在get(key)的过程中,也是首先调用hashCode()计算item的下标,然后在对应下标的地方找,要是为null,就返回null,要是 != null,会去调用equals()方法,比较key是否一致,只有当key一致的时候,才会返回value,要是我们没有重写hashCode()方法,本来有的item,反而会找不到,返回null结果。

10:讲一下稳定的排序和不稳定的排序

https://blog.csdn.net/ithomer/article/details/5636226

排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同

11:讲一下快速排序的思想

12:说一下jvm内存模型吧,有哪些区,分别干什么的

Java 内存模型中将 JVM 分为堆和栈:

堆为同一个 JVM 中所有线程共享,存放运行时创建的对象和数组数据;

栈为每个线程独有,栈中存放了当前方法的调用信息以及基本数据类型和引用类型的数据。

Java 中的堆

堆在虚拟机启动时创建,堆占用的内存由垃圾回收器管理,不需要我们手动回收。

JVM 没有规定死必须使用哪一种内存回收机制,不同的虚拟机实现可以使用不同的回收算法。

堆中包含在 Java 程序中创建的所有对象,无论是哪一个线程创建的。

一个对象的成员变量随着这个对象自身存放在堆上。不管这个成员变量是基本类型还是引用类型。

Java 中的栈

栈在线程创建时创建,它和 C 语言中的栈相似,在一个方法中,你创建的局部变量和部分结果都会保存在栈中,并在方法调用和返回中起作用。

当前栈只对当前线程可见。即使两个线程执行同样的代码,这两个线程仍然会在自己的线程栈中创建一份本地副本。

因此,每个线程拥有每个本地变量的独有版本。

栈中保存方法调用栈、基本类型的数据、以及对象的引用。

计算机中的内存、寄存器、缓存

这部分摘自:http://ifeve.com/java-memory-model-6/

一个现代计算机通常由两个或者多个 CPU,每个 CPU 都包含一系列的寄存器,CPU 在寄存器上执行操作的速度远大于在主存上执行的速度。

每个 CPU 可能还有一个 CPU 缓存层。CPU 访问缓存层的速度快于访问主存的速度,但通常比访问内部寄存器的速度还要慢一点。

        通常情况下,当一个 CPU 需要读取主存时,它会将主存的部分读到 CPU 缓存中。它甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。

        当 CPU 需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。

多线程可能出现的问题

通过上述介绍,我们可以知道,如果多个线程共享一个对象,每个线程在自己的栈中会有对象的副本。

如果线程 A 对对象中的某个变量进行修改后还没来得及写回主存,线程 B 也对该变量进行了修改,那最后刷新回主内存后的值一定和期望的值不一致。

就好比拭心和小翔同时开发同一模块代码,拭心下笔如有神不一会儿搞定了注册登录并且提交,小翔没有从服务器拉代码就蒙头狂写,最后一 pull 代码,就会发现自己写的好多都跟服务器上的冲突了!

竞态条件与临界区

当多个线程操作同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。

在临界区中使用适当的同步就可以避免竞态条件,比如 synchronized, 显式锁和原子操作类等。

内存可见性

拭心写的代码小翔无法立即看到,这就是所谓的“内存可见性”问题。

为了让线程 A 对变量做的修改线程 B 立即可以看到,我们可以使用 volatile 修饰变量或者对修改操作

13:说一下gc算法,垃圾回收,分代回收说下

https://blog.csdn.net/paul_wei2008/article/details/55259579

14:TCP连接中的三次握手和四次挥手,四次挥手的最后一次ack的作用是什么,为什么要time wait,为什么是2msl

为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态
因为虽然双方都同意关闭连接了,而且握手的4个报文也都发送完毕,按理可以直接回到CLOSED 状态(就好比从SYN_SENT 状态到ESTABLISH 状态那样),但是我们必须假想网络是不可靠的,你无法保证你(客户端)最后发送的ACK报文一定会被对方收到,就是说对方处于LAST_ACK 状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT 状态的作用就是用来重发可能丢失的ACK报文。

15:递归求出所有的路径

16:设计模式讲一下熟悉的

17:会不会滥用设计模式

18:多线程的条件变量,为什么要在while体里

19:手撕算法:反转单链表

递归法:

public class ReverseLink {

    public static void main(String[] args) {

        Node node = readyNote();

        reverseLinkedList(node);

    }

    public static Node readyNote() {

        Node node1 = new Node();

        node1.data = 1;

        Node node2 = new Node();

        node2.data = 2;

        Node node3 = new Node();

        node3.data = 3;

        Node node4 = new Node();

        node4.data = 4;

        Node node5 = new Node();

        node5.data = 5;

        Node node6 = new Node();

        node6.data = 6;

        node1.next = node2;

        node2.next = node3;

        node3.next = node4;

        node4.next = node5;

        node5.next = node6;

        return node1;

    }


    static Node reverseLinkedList(Node node) {

        if (node == null || node.next == null) {

            return node;

        } else {

            Node headNode = reverseLinkedList(node.next);

            node.next.next = node;

            node.next = null;

            return headNode;

        }

    }

}

public class Node {

    Integer data;

    Node next;

}

20:实现以下类似微博子结构的数据结构,输入一系列父子关系,输出一个类似微博评论的父子结构图

21:首先java多线程

22:手写java的socket变成,服务端和客户端

23:手撕算法:爬楼梯,写出状态转移方程

24:时针分针什么时候重合

25:java中的多态

26:JVM参数你知道的说一下

Xmx4608M dalvik.vm.heapsize

-XX:ParallelGCThreads=<N> 整数。并行GC的线程个数

27:手撕一个单例模式

猜你喜欢

转载自blog.csdn.net/u012070360/article/details/81663650