离谱,北邮211本科不符合华为OD要求

大家好,我是厂长。

刷到一条脉脉上的陈年老贴,差点没绷住,一位华为 OD 的主管看到一位求职者的学历不错,就主动发起了邀请,然后两方就互相破防了。

8d26457220700b535bc19d767582f9ac.png

我简单交代一下背景。求职者本科是北邮,硕士是清华,清华就不用提了,Top1,北邮也很厉害啊,211 院校,计算机强校。两方的对话可以说是嘲讽拉满:

  • “清华满足当 OD 的要求吗”,意思是你 OD 也有脸要我清华的简历?

  • “你本科也就 211 的而已呀”,意思是你 211 本科嚣张啥,都不满足我们的要求。

真应了那句话,强扭的瓜不甜,在双方看来是门不当户不对啊,作为旁观者的我看着都难受(🤔)。

伟大的罗素先生曾说过,“参差不齐乃幸福本源”,这个世界要允许厉害的人和平凡的人一起共事才行。厉害的就去大厂、研究所、高科技;普通的就去民营、小厂、外包打打工。总之,就是要自洽。

前几天我分享了一篇华为 OD 的面经,有小伙伴留言说这样的面经能不能多来点?那能看得上华为 OD 的小伙伴可以去看一下冲 OD 的药方哈。

如果想要更大的舞台,那不妨看看今天这份华为的面经,应该是不少小伙伴的梦中情司,华孝子、爱华信华等华的小伙伴可以提前准备起来喽。

华为面经

牛顿曾说过,“如果我比别人看得更远,那是因为我站在巨人的肩膀上”。因此,大家如果想在求职的过程中有一个好的结果,就一定要多看看前辈们的面经。

这次我们以《Java 面试指南》中同学 4 的华为一面为例,来看看如果你在面试中遇到这些面试题的话,该如何回答?

5d24af61b64b45901e2fbe9c083a4f5e.png

先来看技术一面的题目大纲(围绕 Java 后端四大件展开),相比较大厂的造火箭🚀,华为确实友好很多:

  • 说一下进程的通信机制

  • 说下工厂模式,场景

  • 说下单例模式,有几种

  • 说下java容器,hashmap,底层实现,效率

  • 说下redis 键值对和hashmap的区别

  • TCP的可靠传输的实现,UDP可靠传输的实现,二者的差异

  • redis的事务,说一下

内容较长,撰写硬核面经不容易,建议大家先收藏起来,面试的时候大概率会碰到,二哥会尽量用通俗易懂+手绘图的方式,让你能背会的同时,还能理解和掌握,总之:让天下没有难背的八股 😂

01、进程间的通信机制

推荐阅读:编程十万问:进程间通信的方式有哪些?

进程间通信(IPC,Inter-Process Communication)的方式有管道、信号、消息队列、共享内存、信号量和套接字。

df9b12dd764cf84187c040187a92aad1.png
编程十万问:进程间通信
管道:

管道可以理解成不同进程之间的传话筒,一方发声,一方接收,声音的介质可以是空气或者电缆。

进程间的管道就是内核中的一串缓存,从管道的一端写入数据,另一端读取。数据只能单向流动,遵循先进先出(FIFO)的原则。

173403f0166d6d688170ff4934015312.png
编程十万问:管道

①、匿名管道:允许具有亲缘关系的进程(如父子进程)进行通信。

49ed6e51db63d4f55c2f2ac6654c09aa.png
三分恶面渣逆袭:“奉先我儿”

②、命名管道:允许无亲缘关系的进程通信,通过在文件系统中创建一个特殊类型的文件来实现。

缺点:管道的效率低,不适合进程间频繁地交换数据。

信号 :

信号可以理解成以前的 BB 机,用于通知接收进程某件事情发生了,是一种较为简单的通信方式,主要用于处理异步事件。

比如kill -9 1050就表示给 PID 为 1050 的进程发送SIGKIL信号。

这里顺带普及一下 Linux 中常用的信号:

  • SIGHUP:当我们退出终端(Terminal)时,由该终端启动的所有进程都会接收到这个信号,默认动作为终止进程。

  • SIGINT:程序终止(interrupt)信号。按 Ctrl+C 时发出,大家应该在操作终端时有过这种操作。

  • SIGQUIT:和 SIGINT 类似,按 Ctrl+\ 键将发出该信号。它会产生核心转储文件,将内存映像和程序运行时的状态记录下来。

  • SIGKILL:强制杀死进程,本信号不能被阻塞和忽略。

  • SIGTERM:与 SIGKILL 不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。

消息队列:

消息队列是保存在内核中的消息链表,按照消息的类型进行消息传递,具有较高的可靠性和稳定性。

5231168ab9693f5ba6873abd3609b967.png
编程十万问:消息队列

缺点:消息体有一个最大长度的限制,不适合比较大的数据传输;存在用户态与内核态之间的数据拷贝开销。

58d4b917e1dca00f9abd54d3f70c2d47.png
编程十万问:消息队列
共享内存:

允许两个或多个进程共享一个给定的内存区,一个进程写⼊的东西,其他进程⻢上就能看到。

共享内存是最快的进程间通信方式,它是针对其他进程间通信方式运行效率低而专门设计的。

cddd7954efbd131c2af57281fc74ddd7.png
三分恶面渣逆袭:共享内存

缺点:当多进程竞争同一个共享资源时,会造成数据错乱的问题。

信号量:

信号量可以理解成红绿灯,红灯停(信号量为零),绿灯行(信号量非零)。它本质上是一个计数器,用来控制对共享资源的访问数量。

45a756f0aece8bb718cdf77d9d36a1db.png
三分恶面渣逆袭:信号量

它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。Java 中的 java.util.concurrent.Semaphore 类就实现了类似的功能。

控制信号量的⽅式有两种原⼦操作:

  • ⼀个是 P 操作(wait,减操作),当进程希望获取资源时,它会执行P操作。如果信号量的值大于0,表示有资源可用,信号量的值减1,进程继续执行。如果信号量的值为0,表示没有可用资源,进程进入等待状态,直到信号量的值变为大于0。

  • 另⼀个是 V 操作(signal,加操作),当进程释放资源时,它会执行V操作,信号量的值加1。如果有其他进程因为等待该资源而被阻塞,这时会唤醒其中一个进程。

a9703ba7486be3eaa9fd726a96d04724.png
编程十万问:信号量
套接字Socket:

这个和 Java 中的 Socket 很相似,提供网络通信的端点,可以让不同机器上运行的进程之间进行双向通信。

920ff7383a1e4e9fe4e5de5d312237ee.png

02、说下工厂模式和场景

推荐阅读:refactoringguru.cn:工厂模式

工厂模式(Factory Pattern)属于创建型设计模式,主要用于创建对象,而不暴露创建对象的逻辑给客户端。

其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。

举例来说,卡车 Truck 和轮船 Ship 都必须实现运输工具 Transport 接口,该接口声明了一个名为 deliver 的方法。

卡车都实现了 deliver 方法,但是卡车的 deliver 是在陆地上运输,而轮船的 deliver 是在海上运输。

49cf7c20c29e1cbf9dbb316b738a41b0.png
refactoringguru.cn:工厂模式

调用工厂方法的代码(客户端代码)无需了解不同子类之间的差别,只管调用接口的 deliver 方法即可。

工厂模式的主要类型

①、简单工厂模式(Simple Factory):它引入了创建者的概念,将实例化的代码从应用程序的业务逻辑中分离出来。简单工厂模式包括一个工厂类,它提供一个方法用于创建对象。

class SimpleFactory {
    public static Transport createTransport(String type) {
        if ("truck".equalsIgnoreCase(type)) {
            return new Truck();
        } else if ("ship".equalsIgnoreCase(type)) {
            return new Ship();
        }
        return null;
    }

    public static void main(String[] args) {
        Transport truck = SimpleFactory.createTransport("truck");
        truck.deliver();

        Transport ship = SimpleFactory.createTransport("ship");
        ship.deliver();
    }
}

②、工厂方法模式(Factory Method):定义一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类的实例化推迟到子类进行。

interface Transport {
    void deliver();
}

class Truck implements Transport {
    @Override
    public void deliver() {
        System.out.println("在陆地上运输");
    }
}

class Ship implements Transport {
    @Override
    public void deliver() {
        System.out.println("在海上运输");
    }
}

interface TransportFactory {
    Transport createTransport();
}

class TruckFactory implements TransportFactory {
    @Override
    public Transport createTransport() {
        return new Truck();
    }
}

class ShipFactory implements TransportFactory {
    @Override
    public Transport createTransport() {
        return new Ship();
    }
}

public class FactoryMethodPatternDemo {
    public static void main(String[] args) {
        TransportFactory truckFactory = new TruckFactory();
        Transport truck = truckFactory.createTransport();
        truck.deliver();

        TransportFactory shipFactory = new ShipFactory();
        Transport ship = shipFactory.createTransport();
        ship.deliver();
    }
}

应用场景

  1. 数据库访问层(DAL)组件:工厂方法模式适用于数据库访问层,其中需要根据不同的数据库(如MySQL、PostgreSQL、Oracle)创建不同的数据库连接。工厂方法可以隐藏这些实例化逻辑,只提供一个统一的接口来获取数据库连接。

  2. 日志记录:当应用程序需要实现多种日志记录方式(如向文件记录、数据库记录或远程服务记录)时,可以使用工厂模式来设计一个灵活的日志系统,根据配置或环境动态决定具体使用哪种日志记录方式。

03、说下单例模式

推荐阅读:refactoringguru.cn:单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式主要用于控制对某些共享资源的访问,例如配置管理器、连接池、线程池、日志对象等。

f955199a7c8b69ef001bd38e082465fb.png
refactoringguru.cn:单例模式

实现单例模式的关键点:

  1. 私有构造方法:确保外部代码不能通过构造器创建类的实例。

  2. 私有静态实例变量:持有类的唯一实例。

  3. 公有静态方法:提供全局访问点以获取实例,如果实例不存在,则在内部创建。

常见的单例模式实现:

01、饿汉式

饿汉式单例(Eager Initialization)在类加载时就急切地创建实例,不管你后续用不用得到,这也是饿汉式的来源,简单但不支持延迟加载实例。

public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
02、懒汉式

懒汉式单例(Lazy Initialization)在实际使用时才创建实例,“确实懒”(😂)。这种实现方式需要考虑线程安全问题,因此一般会带上 synchronized 关键字。

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
03、双重检查锁定

双重检查锁定(Double-Checked Locking)结合了懒汉式的延迟加载和线程安全,同时又减少了同步的开销,主要是用 synchronized 同步代码块来替代同步方法。

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;
    }
}

当 instance 创建后,再次调用 getInstance 方法时,不会进入同步代码块,从而提高了性能。

在 instance 前加上 volatile 关键字,可以防止指令重排,因为 instance = new Singleton() 并不是一个原子操作,可能会被重排序,导致其他线程获取到未初始化完成的实例。

04、静态内部类

利用 Java 的静态内部类(Static Nested Class)和类加载机制来实现线程安全的延迟初始化。

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

当第一次加载 Singleton 类时并不会初始化 SingletonHolder,只有在第一次调用 getInstance 方法时才会导致 SingletonHolder 被加载,从而实例化 instance。

05、枚举

使用枚举(Enum)实现单例是最简单的方式,也能防止反射攻击和序列化问题。

public enum Singleton {
    INSTANCE;
    // 可以添加实例方法
}

04、说下 Java 容器和 HashMap

Java 容器可以分为两条大的支线:

①、Collection,主要由 List、Set、Queue 组成:

  • List 代表有序、可重复的集合,典型代表就是封装了动态数组的 ArrayList 和封装了链表的 LinkedList;

  • Set 代表无序、不可重复的集合,典型代表就是 HashSet 和 TreeSet;

  • Queue 代表队列,典型代表就是双端队列 ArrayDeque,以及优先级队列 PriorityQueue。

②、Map,代表键值对的集合,典型代表就是 HashMap。

d01bc8fb8d8de2f6c30de2d5e03c1a6d.png
二哥的 Java 进阶之路:Java集合主要关系

推荐阅读:二哥的 Java 进阶之路:详解 HashMap

JDK 1.7 中,HashMap 的数据结构是数组+链表,不过应该已经没人在用了,所以我们主要说一下 JDK 8 中 HashMap 的数据结构。

JDK 8 中 HashMap 的数据结构是数组+链表+红黑树

数据结构示意图如下:

89bbc6208902520400c1d2f1833fa712.png
三分恶面渣逆袭:JDK 8 HashMap 数据结构示意图

也就是说,HashMap 的底层数据结构最主要的还是数组,当发生哈希冲突的时候就用链表来解决;不过,如果链表过长时,查询效率会比较低,于是当链表的长度超过 8 时,链表就会转换为红黑树。

HashMap 之所以叫“哈希表”,是因为它利用了哈希函数来计算键的哈希值,然后根据哈希值来决定元素在数组中的位置,这样就可以实现高效的增删改查效率。

数组的查询效率是 O(1),如果哈希冲突,就会用链表来解决,链表的查询效率是 O(n),于是当链表的长度超过 8 时,链表就会转换为红黑树,红黑树的查询效率是 O(logn)。

当向 HashMap 中添加一个键值对时,会使用哈希函数计算键的哈希码,确定其在数组中的位置。如果该位置已有元素(发生哈希冲突),则新元素将被添加到链表的末尾或红黑树中。如果键已经存在,其对应的值将被新值覆盖。

当从 HashMap 中获取元素时,也会使用哈希函数计算键的位置,然后根据位置在数组、链表或者红黑树中查找元素。

随着元素的不断添加,HashMap 的容量(也就是数组大小)可能不足,于是就需要进行扩容,阈值是capacity * loadFactor,capacity 为容量,loadFactor 为负载因子,默认为 0.75。扩容后的数组大小是原来的 2 倍,然后把原来的元素重新计算哈希值,放到新的数组中。

总的来说,HashMap 是一种通过哈希表实现的键值对集合,它通过将键哈希化成数组索引,并在冲突时使用链表或红黑树来存储元素,从而实现快速的查找、插入和删除操作。

05、说下 Redis 和 HashMap 的区别

7858eec752de8bb142ba0d1154fb33f0.png
三分恶面渣逆袭:Redis图标

Redis 是 Remote Dictionary Service 三个单词中加粗字母的组合,是一种基于键值对(key-value)的 NoSQL 数据库。

但比一般的键值对,比如 HashMap 强大的多,Redis 中的 value 支持 string(字符串)、hash(哈希)、 list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、 HyperLogLog(基数估算)、GEO(地理信息定位)等多种数据结构。

而且因为 Redis 的所有数据都存放在内存当中,所以它的读写性能非常出色。

不仅如此,Redis 还可以将内存数据持久化到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据并不会“丢失”。

除此之外,Redis 还提供了键过期、发布订阅、事务、流水线、Lua 脚本等附加功能,是互联网技术领域中使用最广泛的缓存中间件。

06、TCP 和 UDP 的差异

TCP 和 UDP 最根本的区别:TCP 是面向连接的,而 UDP 是无连接的

cf5090d2c47dd6fd20b0859f78c7d0e0.png
三分恶面渣逆袭:TCP 和 UDP 区别

可以这么形容:TCP 是打电话,UDP 是大喇叭(😂)。

16423e1f10c5fe866ff66c92b4eb2847.png
三分恶面渣逆袭:TCP 和 UDP 比喻

在数据传输开始之前,TCP 需要先建立连接,数据传输完成后,再断开连接。这个过程通常被称为“三次握手”。

UDP 是无连接的,发送数据之前不需要建立连接,发送完毕也无需断开连接,数据以数据报形式发送。

在此基础上,我们可以得出:TCP 是可靠的,它通过确认机制、重发机制等来保证数据的可靠传输。而 UDP 是不可靠的,数据包可能会丢失、重复、乱序。

说说 TCP 和 UDP 的应用场景?

  • TCP: 适用于那些对数据准确性要求高于数据传输速度的场合。例如:网页浏览、电子邮件、文件传输(FTP)、远程控制、数据库链接。

  • UDP: 适用于对速度要求高、可以容忍一定数据丢失的场合。例如:QQ 聊天、在线视频、网络语音电话、广播通信。容忍一定的数据丢失。

07、说下 Redis 事务

Redis 支持简单的事务,可以将多个命令打包,然后一次性的,按照顺序执行。

主要通过 multi、exec、discard、watch 等命令来实现:

  • multi:标记一个事务块的开始

  • exec:执行所有事务块内的命令

  • discard:取消事务,放弃执行事务块内的所有命令

  • watch:监视一个或多个 key,如果在事务执行之前这个 key 被其他命令所改动,那么事务将被打断

add10ff4ba2221e760feb0d723829ba3.png

这里简单说一下 Redis 事务的原理:

8375f6dffb83ec312b36fefe1cc492d2.png
三分恶面渣逆袭:Redis事务
  • 使用 MULTI 命令开始一个事务。从这个命令执行之后开始,所有的后续命令都不会立即执行,而是被放入一个队列中。在这个阶段,Redis 只是记录下了这些命令。

  • 使用 EXEC 命令触发事务的执行。一旦执行了 EXEC,之前 MULTI 后队列中的所有命令会被原子地(atomic)执行。这里的“原子”意味着这些命令要么全部执行,要么(在出现错误时)全部不执行。

  • 如果在执行 EXEC 之前决定不执行事务,可以使用 DISCARD 命令来取消事务。这会清空事务队列并退出事务状态。

  • WATCH 命令用于实现乐观锁。WATCH 命令可以监视一个或多个键,如果在执行事务的过程中(即在执行 MULTI 之后,执行 EXEC 之前),被监视的键被其他命令改变了,那么当执行 EXEC 时,事务将被取消,并且返回一个错误。

Redis 事务的注意点有哪些?

Redis 事务是不支持回滚的,不像 MySQL 的事务一样,要么都执行要么都不执行;一旦 EXEC 命令被调用,所有命令都会被执行,即使有些命令可能执行失败。失败的命令不会影响到其他命令的执行。

Redis 事务为什么不支持回滚?

引入事务回滚机制会大大增加 Redis 的复杂性,因为需要跟踪事务中每个命令的状态,并在发生错误时逆向执行命令以恢复原始状态。

Redis 是一个基于内存的数据存储系统,其设计重点是实现高性能。事务回滚需要额外的资源和时间来管理和执行,这与 Redis 的设计目标相违背。因此,Redis 选择不支持事务回滚。

换句话说,就是我 Redis 不想支持事务,也没有这个必要

—  —

最近厂长整理了一份程序员学习大礼包,包含数据结构与算法的最核心知识点,有兴趣的小伙伴可以扫码添加微信,备注“礼包”。

1af0566ac3010947dd385591d92ead7a.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值