当前位置: 首页 > 面试经验 >

蚂蚁网商银行测开,前两轮面经分享!(Java)

优质
小牛编辑
81浏览
2023-04-20

蚂蚁网商银行测开,前两轮面经分享!(Java)

代码题:给定一个数组,输出第k大的数

可以使用快速选择算法(Quickselect algorithm)来解决这个问题,这个算法类似于快速排序算法,不同之处在于它只需要对数组的一部分进行排序。

下面是快速选择算法的步骤:

从数组中随机选择一个元素作为基准值(pivot)。

将数组中小于基准值的元素移到数组的左边,大于基准值的元素移到数组的右边。

如果基准值的下标是k-1,则返回该元素。

如果基准值的下标大于k-1,则在基准值的左边继续查找第k大的元素。

如果基准值的下标小于k-1,则在基准值的右边继续查找第k大的元素。

import java.util.Random;
public class Solution {
 public int findKthLargest(int[] nums, int k) {return quickselect(nums, 0, nums.length - 1, k);}
private int partition(int[] nums, int left, int right) {
    Random random = new Random();
    int pivotIndex = random.nextInt(right - left + 1) + left;
    int pivot = nums[pivotIndex];
    swap(nums, pivotIndex, right);
    int storeIndex = left;
    for (int i = left; i < right; i++) {
        if (nums[i] > pivot) {
            swap(nums, i, storeIndex);
            storeIndex++;
        }
    }
    swap(nums, storeIndex, right);
    return storeIndex;
}

private int quickselect(int[] nums, int left, int right, int k) {
    if (left == right) {
        return nums[left];
    }
    int pivotIndex = partition(nums, left, right);
    if (pivotIndex == k - 1) {
        return nums[pivotIndex];
    } else if (pivotIndex > k - 1) {
        return quickselect(nums, left, pivotIndex - 1, k);
    } else {
        return quickselect(nums, pivotIndex + 1, right, k);
    }
}

private void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];

java集合的种类,HashMap的底层实现,装载因子是多少?为什么是这个值

Java中常用的集合有List、Set、Queue和Map。其中List接口的实现类包括ArrayList和LinkedList,Set接口的实现类包括HashSet和TreeSet,Queue接口的实现类包括LinkedList和PriorityQueue,Map接口的实现类包括HashMap和TreeMap等。

下面我们重点来讲解一下HashMap的底层实现和装载因子。

HashMap是基于哈希表实现的,它的底层数据结构是数组和链表(或红黑树),使用哈希函数将键映射为索引,以实现快速的插入、查找和删除操作。

具体地说,哈希函数会将键值对的键映射为一个整数,这个整数可以作为数组的索引。为了处理冲突,即不同的键值对映射到了同一个数组索引的情况,HashMap使用链表或红黑树来存储这些键值对。当链表长度超过阈值(8)时,链表会被转化为红黑树。

HashMap的装载因子(load factor)是指哈希表中键值对的数量与数组长度的比值,当哈希表中键值对的数量超过数组长度与装载因子的乘积时,HashMap会进行扩容操作,即重新创建一个更大的数组,并将所有键值对重新哈希到新数组中。这个操作的时间复杂度为 O(n),其中 n 是键值对的数量。

默认情况下,HashMap的装载因子为 0.75,这是一个比较合适的值,可以在时间和空间上取得一个平衡。如果我们想要更高的性能,可以将装载因子调小,但是这会增加空间的占用;反之,如果我们想要减小空间的占用,可以将装载因子调大,但是这会增加哈希冲突的概率,降低性能。

你平常使用线程的场景,你认为在你们这个场景下,用多线程和多进程有什么区别?

线程和进程底层的关系,线程能否利用cpu的多核。

进程和线程是操作系统中的概念,是为了管理和调度计算机资源的。进程是程序在操作系统中的一次执行过程,是资源分配和调度的基本单位;而线程是进程中的一个执行单元,也是操作系统调度的基本单位。一个进程可以包含多个线程,这些线程可以共享进程的资源,例如内存、文件等。线程是轻量级的,它们可以在同一个进程内并发执行,从而提高程序的性能。

在底层实现上,每个线程都是由操作系统内核进行管理和调度的。线程有自己的栈空间、程序计数器和寄存器等状态,但是它们共享进程的地址空间和其他资源。

当一个进程被创建时,它会有一个主线程。主线程可以创建其他线程,并且可以访问进程的资源。不同的线程可以在不同的CPU核心上执行,这样可以利用多核CPU提高程序的并发性能。

总的来说,线程和进程是操作系统中的两个基本概念,线程是进程中的执行单元,可以共享进程的资源。线程可以利用CPU的多核来并发执行,从而提高程序的性能。

项目相关问题,做的测试相关的工作简单介绍

事务的ACID特性,举例子说明这四个特性

事务的ACID特性是指原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),是保证数据库事务正确、可靠、安全的四个重要特性。

原子性(Atomicity):指事务是一个不可分割的操作序列,事务中的所有操作要么全部执行成功,要么全部回滚到事务开始之前的状态。例如,银行转账操作中的扣款和转账两个步骤,必须要么同时成功,要么同时失败回滚。

一致性(Consistency):指事务执行前后,数据的完整性约束没有被破坏。例如,一个银行账户的余额不能为负数,如果一笔转账操作导致余额为负数,那么这个操作就不符合一致性要求。

隔离性(Isolation):指事务的执行不能相互影响,每个事务都应该在逻辑上独立执行,就像它是唯一的操作一样。例如,两个用户同时对同一个银行账户进行转账,它们之间应该是互不干扰的。

持久性(Durability):指一旦事务提交,对数据所做的更改就是永久性的,即使系统出现故障也不会丢失数据。例如,一个订单的状态从“待付款”变成“已付款”,这个状态变化应该永久保存下来,即使数据库崩溃也不能丢失。

综上所述,ACID特性对于数据库事务的正确性和可靠性具有非常重要的作用。通过这四个特性,可以确保数据的正确性、可靠性和安全性,从而保证了业务系统的正常运行。

分布式事务知道吗?谈谈你的理解

分布式事务是指涉及到多个独立的系统或者多个数据库之间的事务操作。由于这些系统和数据库可能位于不同的服务器上,它们之间的通讯会有一定的延迟,并且每个系统或数据库有自己的事务管理机制,因此,需要特殊的技术来确保这些系统之间的事务操作的一致性和正确性。下面介绍一些常见的分布式事务解决方案:

两阶段提交(2PC):2PC是最常见的分布式事务解决方案。它的工作原理是:首先,事务协调者会向所有参与者发送一个“准备就绪”请求,并等待所有参与者的回应。如果所有参与者都准备好了,则事务协调者发送一个“提交”请求,否则发送一个“回滚”请求。

补偿事务(Compensating Transaction):补偿事务是另一种常见的分布式事务解决方案。它的工作原理是:在某个参与者失败时,所有其他参与者会执行一个特殊的“补偿”操作,以撤消之前的操作,从而使整个事务回滚。

3PC(Three-Phase Commit):3PC是2PC的改进版,它加入了一个“超时”阶段,以解决2PC中的“阻塞”问题。在3PC中,如果某个参与者在“准备就绪”阶段失败了,事务协调者会向所有其他参与者发送一个“中止”请求,这样所有参与者都会执行补偿操作。

TCC(Try-Confirm-Cancel):TCC是一种比较新的分布式事务解决方案,它的工作原理是:在分布式系统中,每个参与者都实现了一个TCC接口,它包含“尝试”、“确认”和“取消”三个方法。在事务执行时,先执行“尝试”方法,检查所有参与者是否准备好了,如果都准备好了,则执行“确认”方法,否则执行“取消”方法。

总之,在分布式系统中实现事务操作并不容易,需要特殊的技术和解决方案来确保其正确性和一致性。以上介绍的是一些比较常见的分布式事务解决方案,每个解决方案都有其优缺点,需要根据实际情况选择合适的方案。

如果让你现在去做当时觉得难的事情,你觉得你能怎么优化?

我会考虑以下优化策略:

1、制定一个明确的计划:制定一个清晰、具体、可执行的计划可以帮助我分解复杂的任务,把它分成几个小步骤,逐步完成目标。

2、寻找适当的资源:我会寻找一些适当的资源来帮助我完成任务,例如:阅读相关文献、参加相关培训、向专家咨询、和同事讨论、使用工具和技术等。

3、管理时间:我会把任务分解成可行的步骤,并分配适当的时间完成每个步骤。同时,我会留出一些时间用于调整和解决意外情况。

4、提高效率:我会尝试使用一些高效的方法来完成任务,例如使用工具自动化重复性的操作、并行处理多个任务等等。

5、不断学习:对于我来说,学习是提高效率和克服困难的关键。我会不断学习新知识和技能,以便更好地应对挑战。

总之,面对一个难题,我会采取一些优化策略,使自己能够更高效地完成任务,同时不断学习和成长。

然后就是反问环节:业务内容等

3.28二面:电话面试,一个小时左右

首先是自我介绍,然后是对项目的盘问

对自己的项目设计测试用例;问了一些前端的(没答出来)

http的状态码

HTTP(Hypertext Transfer Protocol)状态码是用于表示Web服务器响应HTTP请求的3位数字代码。这些状态码分为5个类别:

1xx(信息):接收的请求正在处理,需要进一步操作。

2xx(成功):请求已成功接收、理解和接受。

3xx(重定向):要完成请求,需要进一步操作。例如,浏览器需要重定向到另一个URL。

4xx(客户端错误):客户端请求包含语法错误或无法完成请求。例如,404 Not Found表示请求的URL在服务器上未找到。

5xx(服务器错误):服务器不能完成对请求的有效响应。例如,500 Internal Server Error表示服务器在尝试处理请求时发生内部错误。

以下是一些常见的HTTP状态码:

200 OK:请求成功

201 Created:新资源已成功创建

301 Moved Permanently:请求的URL已永久移动到新位置

400 Bad Request:客户端请求中有语法错误

401 Unauthorized:请求未经授权

403 Forbidden:服务器拒绝提供请求的资源

404 Not Found:服务器无法找到请求的资源

500 Internal Server Error:服务器内部错误

503 Service Unavailable:服务器无法处理请求,因为服务器过载或维护

get请求传递的参数放在http请求的header还是body

一般来说,GET请求传递的参数是通过将参数放在HTTP请求的URL后面以查询字符串(query string)的形式传递的,而不是放在请求头(header)或请求体(body)中。

例如,下面的URL包含了两个参数:

https://example.com/search?q=keyword&page=2其中,q和page是参数名,keyword和2是对应的参数值。这些参数会被编码并放置在URL的查询字符串中,即?后面的字符串。

GET请求的请求体中通常不包含数据,因为请求体主要用于在POST、PUT等请求方法中传递数据。在实际应用中,也可以将GET请求的参数放在请求头中,但不常用,而且HTTP标准并没有规定GET请求的参数必须放在请求头中的哪个字段中。因此,一般还是建议将GET请求的参数放在URL的查询字符串中。

mysql数据库的数据存储形式;为什么要用b+树?

在MySQL数据库中,数据以页(page)的形式进行存储,每个页默认大小为16KB。一个页可以存储多条记录,每条记录占用的空间不同,但总空间不得超过页的大小。

MySQL中使用了多种数据存储引擎,其中InnoDB引擎是最常用的一种。InnoDB引擎的数据存储采用了B+树的数据结构,用于实现数据的索引和快速查找。

B+树是一种多路搜索树,每个节点可以有多个子节点,可以存储多个数据项。B+树的特点是所有数据都存储在叶子节点中,非叶子节点只存储索引信息。同时,B+树的叶子节点按照从小到大的顺序连接起来,形成了一个有序的链表,可以快速实现范围查询和排序操作。

在MySQL中采用B+树作为数据存储结构,可以带来以下优点:

高效的查找:B+树的结构保证了查找效率较高,每次查找只需要进行O(log n)次比较操作。

有序性:B+树的叶子节点按照从小到大的顺序连接起来,可以实现范围查询和排序操作。

支持高并发:B+树的结构支持高并发,可以在多个线程之间共享数据。

适合大规模数据存储:B+树的结构适合存储大规模数据,可以有效地利用磁盘空间。

总之,B+树是一种高效、有序的数据存储结构,在MySQL中的应用可以提高数据的查询效率、支持高并发、适合大规模数据存储等。

索引设计题:数据表里面有abc三个单独的列,其中A&B、B&C以及A、B、C是最频繁的五个查询,你会怎么设计索引?

针对这个问题,可以考虑如下的索引设计方案:

针对AB、BC这两个组合条件的查询,可以创建一个联合索引(AB, BC)或(BC, AB)。这样可以保证对于AB、BC组合条件的查询可以快速地定位到对应的记录,避免全表扫描。

针对单独的A、B、C三个条件的查询,可以分别创建单列索引。这样可以快速地定位到符合条件的记录,避免全表扫描。

综合以上两个方案,可以创建以下几个索引:

(A,B) 创建联合索引,用于支持A and B和A索引查询

(B,C)创建联合索引,用于支持B and C和B索引查询

(C)创建单列索引,用于支持针对C条件的查询。

需要注意的是,在进行索引设计时,需要考虑到索引的创建成本和维护成本,过多的索引会增加数据库的负担,因此需要根据实际情况进行权衡和选择。另外,在实际应用中还需要考虑到数据的更新和删除操作对索引的影响,需要合理地选择索引类型和创建方式。

测试场景题:假如你是一个玩具公司的质检员,你会怎么在流程控制和用例设计方面去保障玩具的质量。

作为一名玩具公司的质检员,我的工作责任是确保玩具的制造过程中没有问题,并且最终交货的产品质量是高的,安全的。在流程控制和用例设计方面,我会采取以下措施来保证玩具的质量:

流程控制:我会定期检查和评估公司的生产和检测流程,检查是否有任何可能影响产品质量的短板和不足。如果发现有不足,我会建议公司采用最佳做法来解决问题,以确保生产流程的质量和连续性。

用例设计:我会为每种玩具制定一组详细的用例设计,以确保生产过程中每个步骤都得到检查和验证。这些测试用例将覆盖从提供原材料到制造,包装和发货的整个过程。用例将涵盖关键方面,如尺寸,重量,材料组成,产品外观等等,以确保每个产品都符合规范。

样品测试:还会在每个生产周期中从每个批次中选取样品,进行全面的测试和评估。这些测试将涵盖各种方面,如产品质量和安全性,产品的结构完整性和舒适性等等。进行测试的数据将帮助我们确定制造过程中的任何质量问题,并且能够追溯到可能存在的质量问题的原因。

总之,做好以上三点,我们可以有效地控制质量的风险,确保产品质量和安全性,并确保玩具可以取得优秀的销售效果。

生产者和消费者问题主要是解决怎么样的一个问题?

“生产者消费者问题”是指在多线程编程中,生产者和消费者之间的缓冲区的同步问题,其中生产者是向缓冲区生产数据的线程,消费者是从缓冲区消费数据的线程。主要的问题是生产者和消费者需要共同访问同一份资源,即共享的缓冲区,而生产者和消费者的执行速度和顺序未必相同,可能存在以下问题:

生产者可能在缓冲区已满时尝试添加数据,从而导致死锁。死锁是指多个进程或线程因竞争同一资源而陷入无限等待的状态。

消费者可能在缓冲区为空时尝试消费数据,也会导致死锁。解决问题的方法是使用同步机制进行控制,以防止生产者和消费者同时访问缓冲区,确保缓冲区在任何时候都处于正常状态。其中,最常用的同步机制是信号量和互斥锁。

具体来说,通过在生产者和消费者之间引入一个信号量或互斥锁,可以保证其访问共享资源(即缓冲区)的互斥性,保证多个线程安全地访问同一资源。

例如,当缓冲区满时,生产者进程可以等待信号量或互斥锁,直到有空间可用,并且有空间之后唤醒生产者线程进行生产。

同样,当缓冲区为空时,消费者进程可以等待信号量或互斥锁,直到有数据可用,并且有数据之后唤醒消费者线程进行消费。由于生产者消费者问题在多线程编程中经常出现,因此对于任何多线程应用程序,都需要讨论和解决此问题,以确保正确性和高效性。

引发死锁的条件

死锁是指多个线程或进程持有自己的锁并等待Hold其他的锁,导致无法继续运行的状态。必须同时满足以下四个条件才能引发死锁:

互斥条件:至少有一种资源是被独占的。当多个线程或进程需要使用这些资源时,只有一个线程或进程能够占用资源,而其他线程或进程必须等待该资源释放。

请求和保持条件:一个已经连了某个资源的进程申请请求新的资源,但是由于它持有的资源没有释放,所以申请新的资源被阻塞。同时,其他进程想要获得该资源,但由于它被该进程持有,所以也被阻塞。

不可剥夺条件:在某些情况下,进程已经获得的资源不能被强制性地剥夺。这意味着只有在进程完成它占用的所有资源后,系统才能释放资源。

循环等待条件:存在循环等待资源的情况。也就是说,一个进程等待另一个进程所持有的资源,而该进程又在等待其他进程所持有的资源,因此形成一个循环依赖,进程无法继续执行下去。需要注意的是,这些条件中任何一个条件都缺少,都不会发生死锁。

因此,为了避免死锁的发生,可以根据这些条件采取适当的措施,如定期释放资源或引入超时机制等等。

乐观锁和悲观锁的理解,举例子说明java里面的乐观锁和悲观锁?

在并发编程中,乐观锁和悲观锁是两种处理共享资源的策略。悲观锁悲观锁(Pessimistic Lock,简称P锁)是一种悲观的策略,它认为在共享资源被处理的整个过程中,都可能会被其他线程访问和修改,因此在每次访问共享资源前都会加上锁,保证自己的线程拥有独占的访问权。

Java中常见的悲观锁机制就是synchronized,它可以保证同一时刻只有一个线程持有锁,其他线程需要等待获取锁才能访问共享资源,这样就能保证共享资源的一致性。Java中的悲观锁举例:通过synchronized关键字对共享资源加锁,使得线程访问共享资源的时候,能够独占共享资源。例如下面的代码就采用了悲观锁的机制:

public class ExampleThread implements Runnable {
  
 private int count;
public void run() {
    synchronized(this) {
        for(int i=0;i<5;i++) {
            count++;
            System.out.println(Thread.currentThread().getName() + " count:" + count);
        }
    }
}
}

2. 乐观锁相比之下,乐观锁(Optimistic Lock,简称O锁)是一种乐观的策略,它认为在整个过程中,共享资源很少被其他线程占用,所以可以直接访问共享资源,而不用加锁。只有当真正更新时才会检查是否有冲突,如果检测到冲突,则返回给应用层处理。

Java中常见的乐观锁机制就是CAS(Compare and Swap),它可以在不加锁的情况下实现对共享变量的原子操作,当多个线程尝试去更新同一个共享变量时,只有其中一个线程可以更新成功,其他线程需要重试或者等待。这样可以避免锁竞争对性能的影响,提高并发的效率。Java中的乐观锁举例:通过CAS原理实现多个线程同时操作同一个资源的情况。例如下面的代码就采用了乐观锁的机制:

public class ExampleThread implements Runnable {private AtomicInteger count = new AtomicInteger(0);
public void run() {
    for (int i = 0; i < 5; i++) {
        int val = count.incrementAndGet();
        System.out.println(Thread.currentThread().getName() + " count " + val);
    }
}

}

以上是乐观锁和悲观锁的基本概念和Java中的示例。需要根据不同的场景和应用选择适合的锁机制,以获得更好的性能和保证共享资源的一致性。

怎么解决乐观锁CAS的ABA问题,原子类

CAS(Compare and Swap)是一种乐观锁的实现方式,它可以在无锁的情况下实现并发安全。但是,在多线程情况下,可能会出现“ABA”问题。

“ABA”问题是指在一个线程读取到某个数据之后,在执行CAS操作之前,该数据的值被其他线程修改了多次,并且最终修改回原值,这种情况下CAS操作会误认为变量没有被修改过,因此也会执行修改操作。

要解决“ABA”问题,可以使用版本号或时间戳等机制。当使用CAS进行数据比较时,除了比较变量的值以外,还需要比较变量的版本号。如果当前版本号和比较版本号不一致,说明变量已经被其他线程修改过,无法执行CAS操作。

例如,Java中的AtomicStampedReference和AtomicMarkableReference就是使用版本号机制来解决ABA问题的。

CAS是怎么样的一个底层实现?

CAS(Compare and Swap,比较并交换)是一种乐观锁的实现方式,用来解决多线程并发修改同一数据时的并发问题。

CAS操作包含三个操作数,即内存地址V、旧的预期值A和新值B。具体的工作原理如下:

1.当执行CAS操作时,先读取当前内存地址V的值。

  1. 如果当前的值与旧的预期值A不同,则操作失败,终止操作。
  2. 如果当前值与旧的预期值A相同,则将内存地址V的值更新为新值B。
  3. 如果操作期间发现内存地址V的值发生了变化,即被其他线程修改过,则重复执行第1步至第3步的操作,直至更新值成功。

在Java中,CAS操作可以使用Unsafe类进行底层实现。Unsafe类提供了直接操作内存数据的方法,包括获取内存地址、读取内存地址的值、CAS操作等。由于Unsafe类底层的实现是基于硬件操作的,所以具有极高的性能。

Java提供了一些原子类(例如AtomicInteger、AtomicLong、AtomicBoolean等),它们是基于CAS实现的。例如,当多个线程同时调用AtomicInteger实例的incrementAndGet()方法时,底层实现就是通过CAS来保证数据的并发安全。当然,由于CAS操作的实现必须要使用到底层硬件的支持,所以Java中使用CAS操作也是受限的,并不是所有的JVM都支持这一操作。如果JVM不支持Unsafe和CAS操作,就需要采用其他方式手动实现CAS操作,

例如使用synchronized来对部分代码块加锁等。总而言之,在并发编程中,CAS是一种非常重要的技术,它可以在一定程度上提高程序的并发性能。了解底层的CAS实现原理,有助于我们更好地理解Java并发的机制,更好地解决并发问题。

测试场景题:对天猫的优惠机制进行测试:小二会在优惠开始之前将优惠策略写入数据库,系统读入缓存中;到优惠生效过程中,你能考虑到的测试点,去保证整个流程运转正确。(两个角度,小二角度;用户角度)

以下示例仅供参考,从小二的角度考虑:

  1. 验证小二在写入数据库时,是否正确地写入优惠策略,以及优惠策略是否符合要求,包括但不限于折扣率、满减规则等等。
  2. 验证小二的填写策略的过程是否正常,包括但不限于填写策略的UI、填写策略的流程等。
  3. 验证优惠策略被写入数据库后,是否能够正确地读取出来,并且被正确地存储到缓存中,如果缓存失效了,能否正确地重新加载。
  4. 验证小二在策略生效前是否可以进行修改、删除等操作,并且修改后是否能够正确地封存、终止过期的策略。
  5. 在写入优惠策略时,需要考虑并发的情况,例如同时写入相同的策略,需要验证并发的情况下是否能够正常运作,并且写入的数量是否准确。

从用户的角度考虑:

  1. 验证用户是否可以正确地获取自己当前适用的优惠策略,在用户购买时是否已经将优惠减免的金额正确地反映。
  2. 验证用户是否可以在付款前取消优惠,并且是否可以正确地反映优惠被取消的状态,是否需要重新计算支付金额。
  3. 验证用户在购买过程中,如果优惠策略过期或被修改,是否可以正确地通知用户,并且提供恰当的解决方案。
  4. 验证用户在编写优惠策略时能够得到明确的说明和帮助,可以了解自己享受的优惠是否合规、是否合理,是否有其他更为优惠的选择。
  5. 当出现异常、错误等异常情况时,能否及时调整、处理,避免影响到用户的正常使用。

最后就是反问环节。

1、**********************************。

2、如果你对 测试感兴趣,**************************等你交流哦。

此外,我最近打算***************************不定期有送书,发红包活动,工作中遇到的技术问题,都可以在里面咨询**,还有工作内推的机会。有兴趣的小伙伴,欢迎**

#23届找工作求助阵地##我的实习求职记录##2022届毕业生现状##实习,投递多份简历没人回复怎么办##软件开发薪资爆料#
 类似资料: