3.我看你简历上写了很多开发技能,所以给我讲一下哈希表的实现过程。
在jdk1.7中,是数组+链表的数据结构实现。在jdk1.8中,是数组+链表或者红黑树的数据结构实现。
哈希表的实现过程主要由两个方法组成——put和get。 put方法主要用于存储对象。存储对象时,会首先调用参数key的()方法来获取哈希值。 ,然后用这个哈希值对数组的长度取模得到一个下标,这个下标表示当前键值对需要存放在数组中的哪个哈希桶中,然后遍历当前哈希桶的链表,查找链接列表:
1、是否为空,如果为空则直接插入;
2. 如果已经包含该密钥,则覆盖它;
3、如果是红黑树,则直接插入键值对;
4、如果当前数组长度>=64且链表长度>=8,则先将链表转换为红黑树,然后插入。
5、其他情况,如果不包含key,也不需要转换成红黑树,则插入链表尾部。在jdk1.7中,插入了链表头,但是链表存在循环问题,所以jdk1 .8做了优化。
>>>
每当我们向哈希表中插入一个键值对时,我们需要检查当前的负载因子是否超过默认的负载因子。当前负载因子是通过将数组中的元素数量除以数组的长度来获得的。如果超过,则需要重新哈希。重新散列需要遍历数组中每个散列桶下的链表中的每个节点,然后重新散列成新的散列表。
>>>
那么get方法主要是传入一个key,获取对应的。首先它也是调用key的()方法得到一个hash值,然后调制数组的长度得到一个下标来找到对应的hash。 ,然后遍历当前哈希桶下的链表,找到对应的key,并返回。
>>>
当然,在哈希表源码中,我们不是使用哈希值%数组长度来查找对应的哈希桶,而是使用
(数组长度 - 1) & hash,找到对应的哈希桶,因为JDK规定哈希表的数组长度必须是2的幂,而当数组的长度是2的幂时,这个哈希两种方法找到的桶是相同的,并且位运算效率更高。
例如:当哈希值(hash)为22,数组长度(n)为8时,通过hash % n找到下标为6的哈希桶,通过hash & (n - 1)。 6个哈希桶。
>>>
最后是容量和扩容机制:
当我们写了一行map=new()这样的代码时,此时它的容量为0,当我们第一次放入该元素时,它的大小就扩展为16,如果我们手动指定大小为19,那么它的大小就变成了16。实际容量其实是32,因为JDK规定数组的长度必须是2的某次方,2^4就是16,如果不够19,需要向上取整,即2^5 = 32。
扩展问题(根据实际情况回答):
3.1 对两个键调用()得到的结果是相同的。调用()得到的结果一定是一样的吗?
答:不一定一样。
因为查找的是当前键值对存放在哪个哈希桶中,比较的是同一个哈希桶下的链表中的节点是否相同。
3.2 对两个键调用()得到的结果是相同的。调用()得到的结果一定是一样的吗?
答:一定是一样的。
因为()是在对应的哈希桶下查找链表中的节点,如果()相同,则一定是在同一个哈希桶下。
4.我们来说一下什么是线程安全以及如何解决
所有线程安全问题的根源就是操作系统对这个进程的随机调度和抢占式执行。随机调度下,多线程程序执行时有无数种安排。在这些安排中,有些安排的逻辑是正确的,但有些安排可能会导致程序bug。对于多线程并发来说,导致程序bug的代码称为线程不安全代码。这是一个线程安全问题。为什么不同的安排可能会导致线程安全问题:
最好的证明例子:以共享屏幕为例,例如多个线程对同一个变量进行+1操作时,过程类似于计算1+1=2。
两个线程针对同一个变量+1,结果是2。这种安排在逻辑上是正确的。
在错误的安排下,线程1和线程2的执行流程为:
线程1执行LOAD:
线程2执行LOAD、ADD、SAVE:
线程1执行ADD、SAVE:
上面的排列结果是 1 + 1 = 1。这种排列是一个 bug。因此,多线程程序的不同安排可能会导致线程安全问题。
>>>
线程安全问题主要有五个因素:
1、第一个也是最重要的一点是操作系统刚才的随机调度和抢占执行造成的。
2、第二个原因是因为多个线程同时修改同一个变量,也就是刚才举的例子。
3、第三个原因是有些修改操作不是原子的。例如赋值“=”操作符对应一条机器指令,递增或递减对应三个机器指令,分别是(上例中的LOAD、ADD、SAVE),在多线程环境下,如果原子性不保证,当一个线程正在操作一个变量时,其他线程中途介入打断当前线程的操作,结果可能会不正确。
4、第四个原因是内存可见性带来的线程安全问题。内存可见性问题主要是一个线程修改、一个线程读取的场景。当线程1反复读取内存中的数据,然后判断CPU寄存器中的值检查数据是否符合预期时,线程2中途突然修改了CPU寄存器中的值,然后写回内存。编译器看到的是,线程1不断地读取内存,然后进行判断,每次读取内存时都是同一个变量,而且这个变量似乎没有发现任何变化,于是“编译器优化”了做了,将连续读取内存和判断操作优化为读取一次内存然后一直判断,如下场景(面试时可以画例子)
该场景对应的代码示例如下:
public class Main { static class Counter { public int flag = 0; } public static void main(String[] args) { Counter counter = new Counter(); Thread t1 = new Thread(() -> { while(counter.flag == 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t1 结束"); }); t1.start(); Thread t2 = new Thread(() -> { //让用户输入一个数字,赋值给 flag Scanner scanner = new Scanner(System.in); System.out.println("请输入一个整数: "); counter.flag = scanner.nextInt(); }); t2.start(); } }
上面代码中,线程1不断读取内存中的flag变量,然后判断flag是否为0。线程2中途修改了flag的值,使其不为0,但是因为线程1的循环体为空,重复读取同一个变量,因此循环速度太快,从而触发编译器优化,导致线程1不断读取内存,然后判断flag == 0是否为true。优化是读一次内存,然后不断判断flag==0为true。导致线程1没有及时读取flag的最新值,导致多线程安全问题。
5、第五个原因是指令重排序导致的线程安全问题。这也是编译器优化造成的。它通过调整代码的执行顺序来加快程序的执行速度。为什么调整代码的执行顺序可以加快程序速度?执行速度是多少? (例子)
例如,如果妈妈给我一张购物清单,让我去超市购买以下物品,如果我按照购物清单的顺序走,路线就会很僵化。
如果我适当调整顺序,路径将会缩短:
上面的例子对应的是程序。有时,虽然不能提高程序的运行速度,但有时却可以提高程序的执行速度。例如我们写下面这行代码:
测试 t = new Test();这行代码的最下面一行对应了三个步骤:
1.创建内存空间。
2、在该内存空间构造一个对象。
3. 将内存引用分配给t。
在上面的步骤中,编译器优化会将2,3的顺序调整为3,2。在单线程的情况下,这种顺序的调整不仅达到了提高程序执行速度的效果,而且不会造成线程安全问题。在多线程情况下,当其他线程尝试将t读取为非空时,此时t可能是一个无效对象。这是指令重新排序导致的问题。
如何解决
1.使用关键字解决多线程原子性问题。
2、使用关键字禁止指令重排序,也解决了内存可见性问题,但不保证原子性。
3、使用wait和一起使用,在里面使用。
5、既然选择参加考试,为什么需要学习那么多开发知识?
首先,我个人对开发比较感兴趣,所以在学校期间我学到了很多专业的开发知识。 (目的是为了凸显我对学习的热爱)
其次,测试不仅包括白盒测试和黑盒测试,还需要扎实的开发能力来提高个人项目测试的质量。作为测试人员,如果我们有扎实的开发能力,当我们为开发者提供检测到bug的时候,开发者会对我们提出的bug有更高的信心。而我们测试人员也需要开发性能工具来提高测试效率。 (目的是为了凸显我们对软件测试工作的理解)
6.您认为测试人员应该具备哪些素质?
1、具备快速学习能力,有良好的沟通能力,有一定的开发能力。
2.具有优秀的测试用例设计能力。
3、掌握自动化技术。
4、对软件测试有浓厚的兴趣。
5、有责任心和抗压能力。
7. 您设计过测试用例吗?测试用例的要素是什么?
测试用例包括测试环境、测试步骤、测试数据、预期结果等要素。
例子:
例如,在B站的输入框中输入一个空格,然后按回车查看结果。测试用例可以这样设计:
标题:在输入框中输入一个空格。
测试环境:系统, -版本111.0.5563.65(正式版)(64位)
测试步骤:
1)打开浏览器,输入B站网址,跳转到B站。
2)在输入框中输入关键字,按回车键显示结果。
测试数据:空格
预期结果:显示所有数据
跟进>>
1. 现在您已经设计了一个测试用例,现在您将看到一个登录页面。你能为这个登录页面设计一个测试用例吗?
2、既然设计了测试用例,那么可以设计一下微信发红包的测试用例吗?
8. 如何设计测试用例,它们是什么?
1. 等价类 2. 边界值
3. 判断表 4. 场景设计方法
5.正交排列法 6.错误猜测法
上述方法属于黑盒测试,也是纯功能测试。
后续问题:白盒测试和黑盒测试的区别>>
黑盒测试:纯功能测试,不关心程序是如何实现的。也称为系统测试。
白盒测试:更关心程序的内部实现。
后续问题:灰盒测试能否替代黑盒测试和白盒测试>>
灰盒测试不像白盒测试那么详细,也不像黑盒测试那么全面,所以灰盒测试不能取代黑白盒测试。
9. 您曾经为自己的项目设计过测试用例吗?
是的,在为项目设计测试用例时,我只使用思维导图为我的Web项目设计UI自动化测试用例。不过我也在自己的项目上进行了自动化,并使用了框架组合来实现。当 时,我将代码分成模块。主要有两个包。一个包下的类是工具类,主要用于创建驱动对象。这避免了在自动化测试期间每次都必须创建驱动程序。目的。另一个包主要是测试用例,每个页面一个测试类,然后通过测试套件添加所有测试类。这就是我的自动化项目的实施过程。
后续>>(不问的话也可以适当指出几点,说说项目的特点是什么)
您的自动化项目有哪些亮点?
1)使用中提供的注解,避免生成过多的对象,造成资源和时间的浪费,提高自动化执行的效率。
2)驱动程序对象只创建一次,避免了为每个用例重复创建驱动程序对象造成的时间和资源的浪费。
3)用例使用参数化:保持用例简洁,提高代码的可读性。
4)测试套件的使用减少了测试人员的工作量。通过该套件,您可以指定需要运行哪些测试类,也可以一次性执行所有要运行的测试用例。
5)等待的使用提高了自动化的效率,提高了自动化的稳定性。例如,当页面跳转时,或者页面还没有来得及渲染,获取到页面元素时,就会出现 not find的异常。 ,而且这可能不是bug,所以需要添加隐式等待,必要时可以使用强制等待。
6)使用截图方便问题追踪和问题解决。
10.你知道如何定位元素吗?
有八种定位元素的方法:
姓名
小时
11.你知道隐式等待和显式等待的区别吗?
首先简单描述一下隐式等待和显式等待的特点:
隐式等待
1、隐式等待是智能等待的一种。它可以在指定时间内连续搜索元素。如果找到则继续执行后续代码。如果超时后仍未找到该元素,则会报错。也就是说,如果等待10s,第一个三秒就找到了,接下来的七秒就没有等待了。
2、隐式等待适用于整个生命周期,隐式等待对所有方法都生效。
显示等待
1、显示等待也是智能等待。如果在指定时间内提前找到该元素,则稍后继续执行。
2. 显示等待对单个元素或一组元素有效。
3.显示等待可以自定义等待条件。
两者的区别
1. 隐式等待作用于浏览器驱动程序的整个生命周期,而显式等待作用于单个元素或一组元素。
2. 隐式等待仅适用于元素搜索方法,而显式等待允许您自定义等待条件。
2024年,加油!