唯品会一面,被问麻了.....

唯品会后端面经,被严刑烤打。

共24个问题,八股文占了一大半。。问的巨细无比,人麻了。

alt

面经专栏直通车

面经专栏下载

面经解析:

1. Java的数据类型

Java的数据类型包括基本数据类型和引用数据类型:

  • 基本数据类型:byte, short, int, long, float, double, char, boolean。
  • 引用数据类型:class, interface, array。

2. list和set实现类

  • List实现类:ArrayList, LinkedList, Vector。
  • Set实现类:HashSet, TreeSet, LinkedHashSet。

更多面经直通车:24面经大全

3. ArrayList和LinkedList的区别

ArrayListLinkedList都是Java集合框架中的实现类,它们分别基于数组和链表的数据结构。以下是它们之间的一些主要区别:

  1. 底层数据结构:

    • ArrayList使用动态数组实现。它的内部是一个数组,当数组容量不足时,会自动进行扩容。
    • LinkedList使用双向链表实现。每个元素都包含一个指向前一个元素和一个指向后一个元素的引用。
  2. 随机访问性能:

    • ArrayList支持快速的随机访问,因为它是基于数组的,可以通过索引直接访问元素。
    • LinkedList在随机访问时性能较差,因为必须从链表的头部或尾部开始遍历,直到找到目标元素。
  3. 插入和删除操作性能:

    • ArrayList在中间插入或删除元素时性能较差,因为需要移动数组中的元素。
    • LinkedList在插入和删除元素时性能较好,因为只需要改变相邻元素的引用。
  4. 空间复杂度:

    • ArrayList相对较省空间,因为它只需要存储元素值和数组容量。
    • LinkedList相对较耗费空间,因为每个元素都需要额外的两个引用字段。
  5. 迭代器性能:

    • ArrayList上的迭代器性能较好,因为它可以通过索引直接访问元素。
    • LinkedList上的迭代器性能较差,因为必须沿着链表一个一个地移动。
  6. 适用场景:

    • 如果需要频繁进行随机访问,使用ArrayList更为合适。
    • 如果需要频繁进行插入和删除操作,特别是在集合的中间位置,使用LinkedList更为合适。

选择使用哪个取决于具体的使用场景和操作需求。如果不确定,通常来说,ArrayList是一个更通用的选择,因为它在大多数常见的操作上都表现得很好。

4. HashSet加入元素的过程

HashSet 是基于哈希表实现的无序集合,它使用哈希算法来存储和检索元素。下面是向 HashSet 中加入元素的过程:

  1. 计算哈希码(Hash Code):

    • 当你向 HashSet 中添加一个元素时,首先会调用该元素的 hashCode() 方法,得到元素的哈希码。
    • 如果元素为 null,则它的哈希码为 0。
  2. 映射到桶位置(Bucket Position):

    • 哈希码经过一系列的变换和运算,被映射到哈希表中的一个桶位置(bucket position)。
    • 桶位置是一个数组索引,表示存储元素的位置。
  3. 处理哈希冲突:

    • 哈希表可能存在冲突,即不同元素映射到相同的桶位置。为了解决冲突,HashSet 使用链表或红黑树(在JDK 8之后)来存储相同桶位置上的元素。
    • 如果桶位置上已经有一个元素,新元素会被添加到链表或红黑树的末尾。
  4. 检查元素唯一性:

    • 在添加元素的过程中,HashSet 会通过调用元素的 equals() 方法来检查元素的唯一性。
    • 如果已经存在相同的元素(根据 equals() 判断),新元素不会被加入。

HashSet 的添加过程通过哈希码和哈希表的桶来实现,确保元素的快速存储和检索。因为哈希表的桶位置是通过哈希码计算得到的,所以元素的存储位置在理想情况下是均匀分布的。这有助于在大多数情况下实现 O(1) 时间复杂度的添加、删除和查找操作。

5. HashMap线程安全吗?为什么不安全?

HashMap 在多线程环境下不是线程安全的。这是因为 HashMap 的实现是基于哈希表的,而哈希表的操作涉及到多个步骤,包括计算哈希码、定位桶位置、插入或检索元素等。在多线程环境下,多个线程同时对 HashMap 进行修改操作可能导致数据不一致或者丢失。

以下是一些可能导致线程不安全的情况:

  1. 竞态条件(Race Condition): 多个线程同时尝试插入或删除元素时,可能导致竞态条件。两个线程可能同时检测到某个位置为空,然后都尝试插入元素,导致其中一个线程的操作被覆盖。

  2. 扩容操作:HashMap 需要扩容时,会创建一个新的数组并将旧的元素重新分配到新数组中。在这个过程中,如果有其他线程同时对 HashMap 进行修改,可能会导致元素在扩容过程中丢失或者被重复添加。

为了在多线程环境下保证线程安全,可以使用 ConcurrentHashMap 类,它提供了一些并发安全的操作。ConcurrentHashMap 使用分段锁的机制,将哈希表分成多个段,每个段上都有一个独立的锁,从而降低了锁的粒度,提高了并发性能。这样,不同的线程可以同时修改不同的段,避免了整个数据结构的锁竞争。

总的来说,如果需要在多线程环境中使用哈希表,推荐使用 ConcurrentHashMap 而不是 HashMap,以确保线程安全性。

6. 如何做到让HashMap线程安全?

在Java中,HashMap本身不是线程安全的,但可以通过以下几种方式来实现线程安全的HashMap

  1. 使用Collections.synchronizedMap方法:

    Map<K, V> synchronizedMap = Collections.synchronizedMap(new HashMap<K, V>());
    

    这将返回一个线程安全的Map,它在每个方法上都使用同步机制来确保线程安全。但请注意,虽然这确保了每个方法的原子性,但在多个操作之间,仍然可能需要额外的同步。

  2. 使用ConcurrentHashMap ConcurrentHashMap是Java提供的线程安全的Map实现。它使用分段锁机制,每个段相当于一个小的HashMap,不同的段之间互不影响,这样可以提高并发性能。

    Map<K, V> concurrentMap = new ConcurrentHashMap<K, V>();
    
  3. 使用Collections.synchronizedMap包装HashMap的迭代器: 如果你使用Collections.synchronizedMap来创建线程安全的HashMap,当你迭代Map时,仍然需要手动同步。你可以通过在迭代器上使用synchronized块来实现:

    Map<K, V> synchronizedMap = Collections.synchronizedMap(new HashMap<K, V>());
    Set<K> keySet = synchronizedMap.keySet();
    synchronized (keySet) {
        Iterator<K> iterator = keySet.iterator();
        while (iterator.hasNext()) {
            K key = iterator.next();
            // 在此处执行操作
        }
    }
    

如果需要线程安全的HashMap,推荐使用ConcurrentHashMap,因为它在并发场景下性能更好。根据具体的需求,选择适合的方法来保证线程安全。

7. ConcurrentHashMap怎么保证线程安全的?

ConcurrentHashMap是Java集合框架中的线程安全的Map实现。它采用了一些策略来确保在多线程环境中的安全性:

  1. 分段锁(Segmentation): ConcurrentHashMap将整个数据结构分割成多个独立的段(segments),每个段独立地管理一部分数据。每个段都类似于一个小的HashMap,有自己的锁。这样,不同段的数据可以在不同的锁上进行操作,提高了并发度。当一个线程在一个段上进行操作时,其他线程可以同时在其他段上进行操作,减小了竞争范围。

  2. 精细化的锁策略:ConcurrentHashMap中,只有在读写冲突的时候才会使用锁,而且只锁定与冲突相关的段,而不是整个Map。这种细粒度的锁策略减小了锁的争用,提高了并发性能。

  3. 读操作的无锁支持: ConcurrentHashMap对于读操作提供了无锁支持,允许多个线程同时进行读取操作,不会阻塞。只有在写操作发生时才需要加锁,确保写操作的原子性和可见性。

  4. CAS(Compare and Swap)操作: ConcurrentHashMap使用CAS操作来确保对数据的原子更新。CAS是一种无锁算法,它比传统的锁机制更轻量级。通过CAS,ConcurrentHashMap可以在不加锁的情况下完成一些简单的操作。

  5. 适应性自动调整: ConcurrentHashMap在运行时会根据负载因子、并发度等参数进行自动调整。这使得它在不同的负载和并发情况下都能够保持高效。

ConcurrentHashMap通过使用分段锁、细粒度的锁策略、无锁的读操作和CAS操作等技术,以及适应性自动调整,来保证在多线程环境中的高并发性能和线程安全。这些特性使得ConcurrentHashMap成为处理高并发情况下Map操作的理想选择。

8. 手撕生产者消费者模型

// 生产者
class Producer implements Runnable {
    private BlockingQueue<Integer> queue;

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }
    
    public void run() {
        try {
            while (true) {
                int value = produce(); // 生产数据
                queue.put(value); // 将数据放入队列
                Thread.sleep(1000); // 模拟生产过程
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    private int produce() {
        // 生产过程
        return 1;
    }
}

// 消费者
class Consumer implements Runnable {
    private BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }
    
    public void run() {
        try {
            while (true) {
                int value = queue.take(); // 从队列中取出数据
                consume(value); // 消费数据
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    private void consume(int value) {
        // 消费过程
    }
}

9. 手撕两个线程抢票代码,有没有其他方式保证线程安全?

// 使用synchronized关键字保证线程安全
class TicketSystem {
    private int tickets = 100;

    public synchronized void sellTicket() {
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --tickets);
        }
    }
}

// 或使用ReentrantLock
class TicketSystem {
    private int tickets = 100;
    private ReentrantLock lock = new ReentrantLock();

    public void sellTicket() {
        lock.lock();
        try {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --tickets);
            }
        } finally {
            lock.unlock();
        }
    }
}

10. Volatile关键字的作用

volatile是Java关键字之一,它主要用于保证多线程环境下变量的可见性和禁止指令重排序。volatile关键字的主要作用包括:

  1. 可见性(Visibility): 当一个变量被声明为volatile时,意味着这个变量可能会被多个线程同时访问,且不同线程之间的修改操作是可见的。具体来说,如果一个线程修改了一个volatile变量的值,这个修改对其他线程是可见的,其他线程会立即看到这个变量的最新值。

  2. 禁止指令重排序(Ordering): volatile关键字还有禁止指令重排序的作用。在不使用volatile的情况下,编译器和处理器可能会对指令进行重排序,这在多线程环境下可能导致意外的行为。通过将变量声明为volatile,可以防止编译器和处理器对其进行重排序,确保按照代码的顺序执行。

使用volatile的经典场景包括:

  • 标志位: 在多线程环境中,一个线程设置一个volatile标志位,另一个线程检查这个标志位,以便在某个条件满足时通知其他线程停止执行或执行某个操作。

  • 单例模式中的双检锁: 在双检锁机制中,为了避免指令重排序,需要将单例对象声明为volatile

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

volatile不能保证复合操作的原子性。如果一个操作涉及到多个变量的读写,而且这些操作必须在一个原子步骤内完成,那么volatile就无法满足需求,此时可能需要使用其他的同步机制,例如使用java.util.concurrent包中的原子类。

11. Atomic包用过吗?

java.util.concurrent.atomic 包提供了一组用于在多线程环境中进行原子操作的类。这些类通过使用硬件级别的原子性操作或者利用 sun.misc.Unsafe 提供的 CAS(Compare-And-Swap)操作来确保对变量的操作是原子的。这些类大多数都是基于原始数据类型的,例如 intlong,还有一些是引用类型。

以下是 java.util.concurrent.atomic 包中一些主要的类以及它们的用途:

  1. AtomicInteger: 用于对整数进行原子操作,支持原子的自增(incrementAndGet())、自减(decrementAndGet())等操作。

  2. AtomicLong: 用于对长整型进行原子操作,同样支持原子的自增、自减等操作。

  3. AtomicBoolean: 用于对布尔类型进行原子操作,支持原子的设置和获取操作。

  4. AtomicReference: 用于对引用类型进行原子操作,支持原子的获取和设置引用对象。

  5. AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray: 用于对数组中的元素进行原子操作,提供了一些原子性的数组操作。

  6. AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater: 用于对类的字段进行原子更新,允许在并发环境中对对象的字段进行原子性操作。

这些原子类提供了一种比使用 synchronized 关键字更轻量级的线程安全机制,特别适用于一些简单的计数器、状态标志等场景。在需要进行原子操作而又不需要全局的锁的情况下,这些类可以提供更好的性能。

虽然这些类提供了原子性的操作,但并不是所有的操作都可以用原子方式完成,因此在使用时仍然需要注意保证原子性的操作是否符合预期。

12. 索引是什么?为什么能提高查询效率?

  • 索引是数据库中用于加速查询的一种数据结构,通过存储一定规则的索引信息,可以快速定位到符合条件的记录。
  • 索引提高查询效率的原因是它减少了需要扫描的数据量,使得数据库能够更快地定位到符合条件的数据。

alt

#牛客在线求职答疑中心##晒一晒我的offer#
校招面经大全 文章被收录于专栏

收录各个网友分享的各个公司的面经,并给出答案。

全部评论
八股盛宴
46 回复 分享
发布于 01-01 21:31 湖北
这不是我的面经吗?
23 回复 分享
发布于 01-07 16:12 广东
唯品会都这样吧,拿过唯品会offer,两轮技术面疯狂拷打和手撕源码,二面几个面试官轮流问两个多小时,结果薪资待遇低得一批,直接拒了。
7 回复 分享
发布于 01-09 12:37 广东
这有多细不都是八股吗
7 回复 分享
发布于 01-20 09:09 北京
这种反而是最简单的
4 回复 分享
发布于 01-18 11:52 广东
最新面经品鉴,欢迎关注我
3 回复 分享
发布于 2023-12-22 18:10 北京
实习能不能转正
1 回复 分享
发布于 2023-12-25 10:05 陕西
这个面试很好啊,题目
1 回复 分享
发布于 01-17 08:45 广东
天呐,大二上结束才能回答五分之二还有救吗
1 回复 分享
发布于 01-20 10:04 江苏
过了一面吗
点赞 回复 分享
发布于 01-09 12:27 江西
需要的友友可以看看我首页,米哈游有大量正式和实习内推岗位,首页有内推方式,也可以咨询我
点赞 回复 分享
发布于 01-09 18:24 上海
怎么不问微服务的
点赞 回复 分享
发布于 02-01 14:14 陕西
这些不是问的很基础吗
点赞 回复 分享
发布于 02-20 16:28 上海

相关推荐

评论
227
1162
分享
牛客网
牛客企业服务