首页 文章 精选 留言 我的

精选列表

搜索[面试],共5096篇文章
优秀的个人博客,低调大师

面试官问一个数据表字段怎么表示多种业务含义?我愣了五分钟

欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习 1 需求背景 在系统中用户一共有三种角色:普通用户,管理员,超级管理员,现在需要设计一张用户角色表记录这类信息。我们不难设计出如下方案: 我们使用1表示是,0表示否,那么观察上表不难得出,用户一有用超级管理员角色,用户二具有管理员角色,用户三具有普通用户角色,用户四同时具有三种角色。 如果此时新增加一种角色呢?那么新增一个字段即可: 2 发现问题 按照上述做法进行表设计功能上是没有问题的,优点是容易理解结构清晰,但是我们想一想有没有什么问题?笔者遇到过如下问题: 在复杂业务环境一份数据可能会使用在不同的场景,例如上述数据存储在MySQL数据库,这一份数据还会被用在如下场景: 检索数据需要同步一份到ES 业务方使用此表通过Flink计算业务指标 业务方订阅此表Binlog消息进行业务处理 如果表结构发生变化,数据源之间就要重新进行对接,业务方也要进行代码修改,这样开发成本比较非常高。有没有办法避免此类问题? 3 解决方案 我们可以通过位图法,这样同一个字段可以表示多个含义。首先设计如下数据表,userFlag字段先不填。 我们使用位图法每一个bit表示一种角色 我们使用位图法表示如下数据表 用户一位图如下十进制数值是4 用户二位图如下十进制数值是2 用户三位图如下十进制数值是1 用户四位图如下十进制数值是7 这时我们可以补齐数据表 4 位图法详解 本章节我们分析位图法方案一些关键节点。 4.1 枚举定义 定义枚举时不要直接定义为1、2、4这类数字,而是采用位移方式定义,这样使用者可以明白设计者意图。 /** * 用户角色枚举 * * @author JAVA前线 * */ public enum UserRoleEnum { // 1 -> 00000001 NORMAL(1, "普通用户"), // 2 -> 00000010 MANAGER(1 << 1, "管理员"), // 4 -> 00000100 SUPER(1 << 2, "超级管理员") ; private int code; private String description; private UserRoleEnum(Integer code, String description) { this.code = code; this.description = description; } public String getDescription() { return description; } public int getCode() { return this.code; } } 4.2 维护角色 假设用户已经具有普通用户角色,我们需要为其增加管理员角色,这就是新增角色,与之对应还有删除角色和查询角色,这些操作需要用到为位运算,说明详见代码注释。 /** * 用户角色枚举 * * @author JAVA前线 * */ public enum UserRoleEnum { // 1 -> 00000001 NORMAL(1, "普通用户"), // 2 -> 00000010 MANAGER(1 << 1, "管理员"), // 4 -> 00000100 SUPER(1 << 2, "超级管理员") ; // 新增角色 -> 位或操作 // oldRole -> 00000001 -> 普通用户 // addRole -> 00000010 -> 新增管理员 // newRole -> 00000011 -> 普通用户和管理员 public static Integer addRole(Integer oldRole, Integer addRole) { return oldRole | addRole; } // 删除角色 -> 位异或操作 // oldRole -> 00000011 -> 普通用户和管理员 // delRole -> 00000010 -> 删除管理员 // newRole -> 00000001 -> 普通用户 public static Integer removeRole(Integer oldRole, Integer delRole) { return oldRole ^ delRole; } // 是否有某种角色 -> 位与操作 // allRole -> 00000011 -> 普通用户和管理员 // qryRole -> 00000001 -> 是否有管理员角色 // resRole -> 00000001 -> 有普通用户角色 public static boolean hasRole(Integer role, Integer queryRole) { return queryRole == (role & queryRole); } private int code; private String description; private UserRoleEnum(Integer code, String description) { this.code = code; this.description = description; } public String getDescription() { return description; } public int getCode() { return this.code; } public static void main(String[] args) { System.out.println(addRole(1, 2)); System.out.println(removeRole(3, 1)); System.out.println(hasRole(3, 1)); } } 4.3 数据查询 假设在运营后台查询界面中,需要查询具有普通用户角色的用户数据,我们可以使用SQL语句如下 select * from user_role where (user_flag & 1) = user_flag; select * from user_role where (user_flag & b'0001') = user_flag; 使用MyBatis语句如下 <select id="selectByUserRole" resultMap="BaseResultMap" parameterType="java.util.Map"> select * from user_role where user_flag & #{userFlag} = #{userFlag} </select> <select id="selectByUserIdAndRole" resultMap="BaseResultMap" parameterType="java.util.Map"> select * from user_role where id = #{userId} and user_flag & #{userFlag} = #{userFlag} </select> 5 文章总结 本文我们从一个简单案例开始,分析了直接新增字段优缺点,使用新增字段方法,笔者遇到最多的问题就是在复杂业务场景中,如果新增字段数据需要新增对接工作量,增加了开发成本。 我们介绍了位图法,这样一个字段就可以表示多个含义,减少了字段冗余,节省了对接开发成本。当然位图法也有缺点,其增加了代码理解成本,数据库字段含义不直观,需要进行转义,大家可以根据需求场景选择使用。 欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,同时欢迎大家加我个人微信「java_front」一起交流学习

优秀的个人博客,低调大师

【LeetCode-面试算法经典-Java实现】【111-Minimum Depth of Binary Tree(二叉树的最小深度)】

原题 Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. 题目大意 给定一棵两叉树求树的最小深度。 解题思路 遍历法,对整个树进行遍历,找出最小的深度。 代码实现 树结果定义 public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { val = x; } } 1 2 3 4 5 6 算法实现类 public class Solution { private int min = Integer.MAX_VALUE; // 记录树的最小深度 private int cur = 0; // i当前处理的树的尝试 public int minDepth(TreeNode root) { depth(root); return min; } /** * 计算树的深度 * * @param node 当前结点 */ private void depth(TreeNode node) { if (node == null) { min = cur; return; } cur++; // 当前处理的层次加1 // 如果是叶节点,并且路径比记录的最小还小 if (node.left == null && node.right == null && cur < min) { min = cur; // 更新最小值 } // 处理左子树 if (node.left != null) { depth(node.left); } // 处理右子树 if (node.right != null) { depth(node.right); } cur--; // 还原 } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 /** * 计算树的深度 * * @param node 当前结点 */ private void depth (TreeNode node) { if (node == null ) { min = cur; return ; } cur++; // 当前处理的层次加1 // 如果是叶节点,并且路径比记录的最小还小 if (node.left == null && node.right == null && cur < min) { min = cur; // 更新最小值 } // 处理左子树 if (node.left != null ) { depth(node.left); } // 处理右子树 if (node.right != null ) { depth(node.right); } cur--; // 还原 }}

优秀的个人博客,低调大师

面试官:你简历上有熟悉设计模式,那你给我说一下单例模式实现及线程安全吧

云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 前言 单例应用的太广泛,大家应该都用过,本文主要是想聊聊线程安全的单例以及反序列化破坏单例的情况。 1、概念 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。 关键点: 私有化构造函数 通过一个静态方法或枚举返回单例类对象 确保单例类的对象有且只有一个,尤其是多线程环境下 确保单例类对象在反序列化时不会重新构建对象 2、实现 2.1、线程安全的单例 2.1.2、饿汉模式 饿汉模式:不管有没有调用getInstance方法,只要类加载了,我就给你new出来(a) public class A { private static final A a = new A(); public static A getInstance() { return a; } private A() {} } 以下两点保证了以上代码的线程安全: 调用一个类的静态方法的时候会触发类的加载(如果类没加载过) 类只会加载(被加载到虚拟机内存的过程,包括5个阶段)一次 static变量在类初始化的时候(类加载过程的最后一个阶段)会去赋值静态变量 2.1.2、懒汉模式 懒汉模式:延迟加载,用到再去new public class B { private static volatile B b; public static synchronized B getInstance() { if (b == null) { b = new B(); } return b; } private B() { } } 要保证线程安全,最简单的方式是加同步锁。synchroized保证了多个线程串行的去调用getInstance(),既然是串行,那就不会存在什么线程安全问题了。但是这实现,每次读都要加锁,其实我们想要做的只是让他写(new)的时候加锁。 2.1.3、Double Check Lock (DCL) public class B { private static volatile B b; public static synchronized B getInstance0() { if (b == null) { synchronized (B.class) { b = new B(); } } return b; } public static B getInstance() { if (b == null) { synchronized (B.class) { if (b == null) { b = new B(); } } } return b; } private B() { } } 为了解决懒汉模式的效率问题,我们改造成getInstance0(): 但还有个问题 X、Y 两个线程同时进入if (b == null), X先进同步代码块,new了一个B,返回。Y等到X释放锁之后,它也进了同步代码块,也会new一个B。 getInstance0()解决了效率问题,但它不是线程安全的。我们有进行了一次改造: getInstance(): getInstance在同步块里面,又做了一次if (b == null)的判断,确保了Y线程不会再new B,保证了线程安全。 getInstance() 也正是所谓的双重检查锁定(double checked locking)。 这里还有一个关键点:private static volatile B b;b是用volatile修饰的。 这个主要是因为new 并不是原子的。 B b = new B(); 可以简单的分解成一下步骤: 分配对象内存 初始化对象 设置引用指向分配的内存地址 2,3 直接可能发生指令重排序,就是说对象还未初始化完成,就让b指向了一块内存地址,这时候b就不是null了。 2.1.4、静态内部类单例模式 public class C { private C() {} public static C getInstance() { return CHolder.c; } private static class CHolder { private static final C c = new C(); } } 静态内部类的线程安全也是由jvm保证的,在调用Cholder.c的时候,去加载CHolder类,new 了一个c。 总的来说,这个方式比DCL还是高点的,因为DCL加了volatile,效率上还是略微有些些影响。 上面介绍的3种线程安全的单例,在有种极端的情况,单例模式有可能被破坏:反序列化 Java序列化就是指把Java对象转换为字节序列的过程Java反序列化就是指把字节序列恢复为Java对象的过程。 反序列化的时候,会重新构造一个对象,破坏单例模式。我们看下代码验证下: public class C1 implements Serializable { private C1() { System.out.println("构造方法"); } public static C1 getInstance() { return CHolder.c; } private static class CHolder { private static final C1 c = new C1(); } // 注意这块被注释的代码 // private Object readResolve(){ // System.out.println("read resolve"); // return CHolder.c; // } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { C1 c = C1.getInstance(); System.out.println(c.toString()); try { ObjectOutputStream o = new ObjectOutputStream( new FileOutputStream("d:/tmp/c.out")); o.writeObject(c); o.close(); } catch(Exception e) { e.printStackTrace(); } C1 c1 = null, c2 = null; try { ObjectInputStream in =new ObjectInputStream( new FileInputStream("d:/tmp/c.out")); c1 = (C1)in.readObject(); in.close(); } catch(Exception e) { e.printStackTrace(); } try { ObjectInputStream in =new ObjectInputStream( new FileInputStream("d:/tmp/c.out")); c2 = (C1)in.readObject(); in.close(); } catch(Exception e) { e.printStackTrace(); } System.out.println("c1.equals(c2) : " + c1.equals(c2)); System.out.println("c1 == c2 : " + (c1 == c2)); System.out.println(c1); System.out.println(c2); } } 结果: 构造方法 me.hhy.designpattern.singletonpattern.C1@1540e19d c1.equals(c2) : false c1 == c2 : false me.hhy.designpattern.singletonpattern.C1@135fbaa4 me.hhy.designpattern.singletonpattern.C1@45ee12a7 放开注释的代码 构造方法 me.hhy.designpattern.singletonpattern.C1@1540e19d read resolve read resolve c1.equals(c2) : true c1 == c2 : true me.hhy.designpattern.singletonpattern.C1@1540e19d me.hhy.designpattern.singletonpattern.C1@1540e19d 正如我们看到的那样,加上readResolve就解决了反序列化单例被破坏的问题。 当然,如果没实现Serializable接口,也就不会有这个被破坏的问题… 还是看场景。 关于readResolve的介绍,感兴趣的同学们可以看java.io.ObjectInputStream#readUnshared方法上的注释(博主看了,看得不是很明白,一知半解,就不误人子弟了) 而我们下面要介绍的枚举单例,并不会有这个问题。 2.1.5、枚举单例 public enum DEnum { INSTANCE; private D d; DEnum() { d = new D(); } public D getInstance() { return d; } } public class D {} 线程安全的保证: 枚举只能拥有私有的构造器 枚举类实际上是一个继承Enum的一个final类 上面的INSTANCE实际是被static final 修饰的 序列化不破坏单例的保证: 在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 2.2 线程不安全的单例 2.2.1、懒汉模式 不过多介绍了,这个其实在线程安全的单例部分,我们介绍的比较详细了。 public class B { private static volatile B b; public static B getInstance() { if (b == null) { b = new B(); } return b; } private B() { } } 3. 总结 单例的应用实在是太多了,也没必要再去找源码种的经典使用(因为基本上大家用过)。 枚举单例构造方法还是public,并不是防止外部直接去new它。个人认为如果一个类要开放给外部使用,用内部类的形式实现单例是最合适的。 【云栖号在线课堂】每天都有产品技术专家分享!课程地址:https://yqh.aliyun.com/live 立即加入社群,与专家面对面,及时了解课程最新动态!【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK 原文发布时间:2020-08-04本文作者:程序员伟杰本文来自:“掘金”,了解相关信息可以关注“掘金”

优秀的个人博客,低调大师

[雪峰磁针石博客]Python经典面试题: 用3种方法实现堆栈和队列并示例实际应用场景

介绍 数据结构在计算机中组织存储,以便我们可以有效地访问和更改数据。 堆栈和队列是计算机科学中定义的最早的数据结构。 堆栈 遵循后进先出 (Last-in-First-Out LIFO)原则。 push - 在堆栈顶部添加元素: pop - 删除堆栈顶部的元素: 队列 遵循先入先出(FIFO:First-in-First-Out)原则。 enqueue - 在队列的开头添加元素: dequeue - 删除队列开头的元素: 使用列表实现堆栈和队列 Python的内置List数据结构k堆栈和队列操作的方法。 堆栈 letters = [] # Let's push some letters into our list letters.append('c') letters.append('a') letters.append('t') letters.append('g') # Now let's pop letters, we should get 'g' last_item = letters.pop() print(last_item) # If we pop again we'll get 't' last_item = letters.pop() print(last_item) # 'c' and 'a' remain print(letters) # ['c', 'a'] 执行结果 g t ['c', 'a'] 队列 fruits = [] # Let's enqueue some fruits into our list fruits.append('banana') fruits.append('grapes') fruits.append('mango') fruits.append('orange') # Now let's dequeue our fruits, we should get 'banana' first_item = fruits.pop(0) print(first_item) # If we dequeue again we'll get 'grapes' first_item = fruits.pop(0) print(first_item) # 'mango' and 'orange' remain print(fruits) # ['c', 'a'] 执行结果 banana grapes ['mango', 'orange'] 使用Deque库的堆栈和队列 deque是Double Ended Queue的缩写 - 可以获取存储的第一个或最后一个元素的通用队列,下面我们使用Deque库的堆栈和队列: from collections import deque # you can initialize a deque with a list numbers = deque() # Use append like before to add elements numbers.append(99) numbers.append(15) numbers.append(82) numbers.append(50) numbers.append(47) # You can pop like a stack last_item = numbers.pop() print(last_item) # 47 print(numbers) # deque([99, 15, 82, 50]) # You can dequeue like a queue first_item = numbers.popleft() print(first_item) # 99 print(numbers) # deque([15, 82, 50]) 执行结果 47 deque([99, 15, 82, 50]) 99 deque([15, 82, 50]) 参考资料 本文最新版本地址 本文涉及的python测试开发库 谢谢点赞! 本文相关海量书籍下载 python工具书籍下载-持续更新 python GUI工具书籍下载-持续更新 更严格的实现 创建撤消功能 - 允许用户回溯他们的操作,直到会话开始。堆栈是这种情况的理想选择。 我们可以通过将其推送到堆栈来记录用户所采取的每个操作。 当用户想要撤消操作时,他们将从堆栈中弹出它。 游戏中,每次按下按钮,都会触发输入事件。 测试人员注意到,如果按钮按下得太快,游戏只处理第一个按钮,特殊动作将无效!可以使用队列修复它。 我们可以将所有输入事件排入队列。 #!/usr/bin/python3 # -*- coding: utf-8 -*- # 项目实战讨论QQ群630011153 144081101 # python测试开发库汇总: https://github.com/china-testing/python-api-tesing/ # 本文最佳板式地址: https://www.jianshu.com/p/c990427ca608 # A simple class stack that only allows pop and push operations class Stack: def __init__(self): self.stack = [] def pop(self): if len(self.stack) < 1: return None return self.stack.pop() def push(self, item): self.stack.append(item) def size(self): return len(self.stack) # And a queue that only has enqueue and dequeue operations class Queue: def __init__(self): self.queue = [] def enqueue(self, item): self.queue.append(item) def dequeue(self): if len(self.queue) < 1: return None return self.queue.pop(0) def size(self): return len(self.queue) document_actions = Stack() # The first enters the title of the document document_actions.push('action: enter; text_id: 1; text: This is my favourite document') # Next they center the text document_actions.push('action: format; text_id: 1; alignment: center') # As with most writers, the user is unhappy with the first draft and undoes the center alignment document_actions.pop() # The title is better on the left with bold font document_actions.push('action: format; text_id: 1; style: bold') input_queue = Queue() # The player wants to get the upper hand so pressing the right combination of buttons quickly input_queue.enqueue('DOWN') input_queue.enqueue('RIGHT') input_queue.enqueue('B') # Now we can process each item in the queue by dequeueing them key_pressed = input_queue.dequeue() # 'DOWN' # We'll probably change our player position key_pressed = input_queue.dequeue() # 'RIGHT' # We'll change the player's position again and keep track of a potential special move to perform key_pressed = input_queue.dequeue() # 'B' # This can do the act, but the game's logic will know to do the special move

优秀的个人博客,低调大师

程序员吐槽自己阿里p7面试微软被拒,网友:你就是高级一点的码农

我想一提起阿里巴巴,我们就互相到马云这位大佬。然而阿里巴巴也是我国巨头企业霸主之一,在国际上也十分具有知名度。众所周知阿里员工的待遇和福利是非常优渥的,因此也吸引了很多年轻人的目光。但是阿里和国际知名企业如谷歌、微软等相较于技术来说还是有着一定的差距。 就有一位阿里工作的程序员想去微软养老。他说自己是P7级了,工作累想去微软养老,想要一个principal(微软的内部级别,类似于国内大厂的项目经理)。结果被拒了... 就楼主的吐槽而言,一位亚马逊工作的员工说出了自己的看法,在美国,他相信阿里巴巴对微软来说仍然有很大的差距,尤其是在科研方面,阿里的P7是不超过一个更高层次的码农而已,怎么能对标微软的principal了。事实就是如此,国内的阿里、百度却是离微软谷歌等国际性的大企业还存在差距。 基本上大部分网友认为楼主被拒才是正常的,阿里P7的程序员对标微软principal这一水平,阿里巴巴的很多业务P7也不是很强,在阿里你绩效好就可以拿很多钱,但这并不等同于你可以为其他公司获得相同的薪水。 不知道这楼主的吐槽你们怎么看,观众之中不乏人才,说下你们的看法吧! 想要学习Java高架构、分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频免费获取 架构群;468947140

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册