你好呀,我是歪歪。

前几天在网上冲浪的时候看到一个消息,关于智能密码锁的。

就是这种玩意:

当时我看到的那个消息说,开密码锁的时候,你输入的数字串只要包含你真正的密码就能开锁。

比如,假设你的密码是:250818

那你在按密码的时候输入“123250818456”也能开锁。

怎么可能是这样的开锁逻辑呢,密码都没匹配上,门就开了,这不扯呢吗?

所以,我当时以为拍视频的人在一本正经的搞抽象呢,

直到有天晚上回家,在电梯里我突然又想起了这个段子。

于是想着验证一下。

嘿,你猜怎么着?

我开锁的时候在正确的密码前后故意多输入了几个数字,然后再按“#”,门开了。

还真不是段子。

当时我大概是这样的:

这玩意有点意思啊。

一般来说我都是用指纹解锁,但是有时候晚上出去跑步,回来之后手上都是汗,指纹识别老是失败。

这种情况下,我就会选择输入密码。

而我之前输入密码偶尔按快了,会出现按错一位的情况。

这个时候我就会轻轻的叹一口气,表示无奈,然后先输入一个“#”,让电子锁喊一声“密码错误”,再重新输入。

那天我验证了“在按#之前只要包含正确密码输入,门就能打开的这个逻辑”之后,显得我之前的一些操作像是个傻子。

同时我也兴奋的把 Max 同学叫来,给她分享了我的伟大发现。

她说:这不会是 BUG 吧?

我作为程序员,就听不得 BUG 这个东西。

于是我又浅浅的研究了一下,发现这玩意,还真是 feature,不是 BUG。

“这不是 BUG,这是 feature”,没想到这句话还真会出现在一些非狡辩的场景下。

甚至这个 feature 几乎是密码锁的标配,而这个功能还有个专门的名称叫:虚位密码。

我在购物网站上随便找一个密码锁,都有相关的介绍:

看介绍,这个功能的使用场景主要就是当有人在你旁边,你又不方便让他回避的时候,你就可以在真实的密码前面输入一些干扰项,输入的长一点,也不怕别有用心的人偷窥了。

这个功能怎么说呢?

我个人认为是聊胜于无,因为我没有这个场景。

但是如果你告诉我,在输密码的时候,自己纯纯手滑,输错了,不用按“#”,让密码锁喊一声“密码错误”,而是可以直接重新输入一遍。

那我觉得这个功能是真好用。

因为这个场景是我真有。

问题就来了

我在了解到这个现象之后,自然而然的就带入了程序员思维。

所以,那么问题就来了。

假设,现在这是一个面试的场景,面试官要求你写一个逻辑来实现上面“虚位算法”的逻辑:

//判断sourceStr中是否有targetStr
public static boolean checkStr(String sourceStr,String targetStr) {}

你会怎么搞?

首先我们来分析一下。

假设我的密码是:250818。

要判断我输入的一串数字中是否也有 250818 这个序列存在,首先可以确定的是,我们要拿到这两个输入串,然后按照字符,逐个对比。

也就是要把 sourceStr、targetStr 转化为 char[],然后在 for 循环中逐一对比每个字符是否能对上。

而且因为有两个数组,所以这个 for 循环还得是双重 for 循环。

外层循环的是什么?

因为我们是要在 sourceStr,也就是用户输入的密码里面找正确的密码,所以外层循环的肯定得是 sourceStr。

拿着输入密码的第一个字符和 targetStr,也就是正确密码的字符串对应的整个数组进行逐一比较,如果匹配上了,再拿输入密码的后续字符和正确密码进行对比,循环往复,直到对比成功,或者整个输入串对比完成。

这就对应着第二层循环的逻辑。

大体思路还是非常清晰的,但是我们还要解决一个问题:外层 for 循环的次数是多少次?

假设下面这个 for 循环就是在循环 sourceStr,也就是我们要知道这里的 max 值应该是多少?

for (int i = 0; i <= max; i++) {}

这里我们做个假设:

sourceStr=123250818456
targetStr=2501818

那么我们外层的循环,从 sourceStr 的第一个字符“1”开始,最多循环到哪里的时候就能知道密码是否能匹配上了?

是不是循环到123250【8】18456,这个【8】的时候?

因为算上这个【8】后面就只剩下 6 位长度了,如果这个 【8】 都还没匹配上,那它后面的长度已经小于 6 位长度,再去匹配已经没有意义了。

而这个“6 位长度”怎么来的?

是不是就是 targetStr 的长度?

所以 max 的值就是 sourceStr.length-targetStr.length。

按照上面的思路,完整的代码就是长这样的:

public static boolean checkStr(String sourceStr,String targetStr) {
    char[] source = sourceStr.toCharArray();
    char[] target = targetStr.toCharArray();
    int sourceCount = sourceStr.length();
    int targetCount = targetStr.length();
    char first = target[0]; // 目标串首字符
    int max = sourceCount - targetCount; // 最大可匹配起始位置

    // 2. 外层循环:遍历源字符串
    for (int i = 0; i <= max; i++) {
        // 2.1 快速跳过不匹配位置
        if (source[i] != first) {
            while (++i <= max && source[i] != first); // 持续跳过直到找到首字符匹配
        }

        // 2.2 首字符匹配后,校验后续字符
        if (i <= max) {
            int j = i + 1; // 源字符串下一个位置
            int end = j + targetCount - 1; // 目标串结束位置
            int k = 1; // 目标串下一个位置,在下面的for循环中进行递增
            // 内层循环:逐字符比对
            for (; j < end && source[j] == target[k]; j++, k++);

            // 3. 校验是否完全匹配
            if (j == end) {
                return true; // 返回匹配起始索引
            }
        }
    }
    return false; // 未找到
}

看到这里可能有小伙伴心理早就开始嘀咕了:整这么复杂干啥玩意?

我一行代码就能秒了这题啊:

sourceStr.contains(targetStr);

好,不错,很有精神!

那我问你:contains 的底层逻辑是怎么样的?

啥,你说你不知道?

那你现在知道了,因为前面的实现逻辑,就是 Java 中 String 类 contains 方法的源码:

contains 方法最终会调用到这个 indexOf 方法:

看了前面的逻辑,你再看这个 indexOf 方法你就会觉得:有点眼熟。

所以这个问题的关键,就是你要抓住关键的问题。

如果是在面试,你就答上面这个按照字符逐个对比的,然后补一句:这个思路和 contains 方法是一样的。

如果是实际写代码,你一句话都不用说,contains 一把梭直接收工就完事。

这玩意就像面试的问你:手撕个 LFU 算法(最近最少使用算法) 来看看。

你回答的时候不能说“可以利用 LinkedHashMap 来实现”,对不对?

面试中,你得从 Node 开始撕,拿出双向链表+哈希表的方案来。

至于实际写代码嘛...

对不起,我一个写业务 的 Javaer,用不上这么高级的东西。

还有一个问题

其实在写文章的时候,我突然还想到了一个问题。

一个非常致命的问题。

如果密码锁的“虚位密码”这个逻辑真的成立,说明了什么?

说明密码锁的密码是明文存储的啊。

正常来说,密码肯定是要加密存储的,那不管你用什么加密方式。

250818 和 123250818456 加密出来的密文肯定是完全不一样,天差地别的。

加密后,sourceStr.contains(targetStr) 的逻辑就完全不成立了啊。

所以,从这个现象来看,支持“虚位密码”的密码锁的密码可能是明文存储的。

看到“明文存储”这几个字,是不是感觉很可怕?

如果来一次信息泄露,那不就变成“我家大门常打开”了吗?

关于这个点,我是这样想的。

密码锁的密码并不会存储在商家的服务器里面,而且存储在密码锁的本地。

其实一般来说,明文存在本地也是有风险的,但是在密码锁的这个场景下,其实也是能接受的。

你想想,别人为了拿到你的明文密码,是不是得把锁拆下来搞搞逆向工程啥的。

那锁都拆下来了,门不是轻轻一推就开了吗,还要啥密码?

One More Thing

我第一次得知密码锁的这个 feature ,并在自己家的密码锁上验证过后,确实大为震惊。

震惊的点在于,我日常生活中每天都在用的东西居然还有隐藏功能。

这让我莫名其妙的想到了另外一个点。

这个点是关于微信的。

微信,你点击这个“拍摄”,有时候拍出来的照片质感很差:

因为调用的不是手机的原相机。

但是,如果你是安卓手机,那长按“相册”按钮大概 3 到 5 秒,就会唤醒手机的原相机。

当我第一次用上面的方式唤醒手机的原相机的时候,内心活动大概也是这样的:

如果你不知道的话,你可以试一试。

家里有密码锁的注意了,这真不是 BUG,是 feature。的更多相关文章

  1. git &github 快速入门

    本节内容 github介绍 安装 仓库创建& 提交代码 代码回滚 工作区和暂存区 撤销修改 删除操作 远程仓库 分支管理 多人协作 github使用 忽略特殊文件.gitignore 1.gi ...

  2. git的使用[转]

    本节内容 github介绍 安装 仓库创建& 提交代码 代码回滚 工作区和暂存区 撤销修改 删除操作 远程仓库 分支管理 多人协作 github使用 忽略特殊文件.gitignore 为什么要 ...

  3. Git 教程(三):仓库与分支

    远程仓库 到目前为止,我们已经掌握了如何在Git仓库里对一个文件进行时光穿梭,你再也不用担心文件备份或者丢失的问题了. 可是有用过集中式版本控制系统SVN的童鞋会站出来说,这些功能在SVN里早就有了, ...

  4. 嫁给程序员的好处,你get到了吗?

    首先,我们要知道,什么是程序员?程序员是做什么的? "程序员(英文Programmer)是从事程序开发.维护的专业人员.一般将程序员分为程序设计人员和程序编码人员,但两者的界限并不非常清楚, ...

  5. 廖雪峰Git教程2

    转自:https://www.liaoxuefeng.com/wiki/896043488029600 [远程仓库] 到目前为止,我们已经掌握了如何在Git仓库里对一个文件进行时光穿梭,你再也不用担心 ...

  6. github祥解

    github介绍 安装 仓库创建& 提交代码 代码回滚 工作区和暂存区 撤销修改 删除操作 远程仓库 分支管理 多人协作 github使用 忽略特殊文件.gitignore 为什么要用版本控制 ...

  7. 第二次团队作业 -- 预则立&&他山之石

    我说的都队 031402304 陈燊 031402342 许玲玲 031402337 胡心颖 031402203 陈齐民 031402209 黄伟炜 031402233 郑扬涛 一.团队任务计划 周数 ...

  8. git使用介绍

    Git简单介绍 参考网址: git使用简介 这个教程推荐使用:git教程 git和svn的差异 git和svn的最大差异在于git是分布式的管理方式而svn是集中式的管理方式.如果不习惯用代码管理工具 ...

  9. gerrit git使用

    有关git的參考资料 pro git中文版, 最好的git书籍 http://git-scm.com/book/zh 图解git http://marklodato.github.com/visual ...

  10. 关于 Git使用的全面总结 —— 致敬Git之父Linux

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 17.0px ".PingFang SC"; color: #454545 } p.p2 ...

随机推荐

  1. 面试题:Spring BeanFactory和FactoryBean的区别

      BeanFactory:以Factory结尾,表明它是一个工厂类(接口),它是Spring IOC容器的核心接口,负责实例化和管理bean的一个工厂,为具体的IoC容器的实现提供规范.BeanFa ...

  2. 使用spring-boot-starter-mail发送邮件,HTML,附件

    前言 这里使用的是spring-boot-starter-mail, 当然了,你也可以使用com.sun.mail(javax.mail),Hutool对这个进行了封装, 但是我的项目是springb ...

  3. Linux系统安全配置相关

    一.说明 最近公司安全部门针对我们的系统从系统组件.系统配置.系统应用容器三个层面对系统进行了整体的扫描,针对系统配置这块,有许多安全配置项,这里在这里记录一下,有需要的直接按照介绍的配置进行相应的修 ...

  4. 数栈UI5.0设计实战|B端表单这样设计,不仅美观还提效

    表单是B端产品中最常见的组件之一,主要⽤于数据收集.校验和提交.比如登陆流程的账号密码填写,注册流程的邮箱.用户名等信息填写,都是表单应用的常见案例,在数栈产品中也是出现频率⾮常⾼的组件. 尽管表单应 ...

  5. 性能、安全和稳定,袋鼠云数据服务平台 DataAPI 为企业 API 保驾护航

    通过API 对外提供数据服务是大部分企业中比较常见的数据应用方式,对于 API 平台管理者.开发者和调用者来说,API 的调用性能.安全性和稳定性是在平台选型时最需要考虑的三个因素. 袋鼠云API开发 ...

  6. Spring IoC容器与依赖注入深度解析

    在 Spring 生态系统中, 控制反转(IoC) 与 依赖注入(DI) 是实现组件解耦的核心机制.本文从容器架构.依赖注入实现.生命周期管理及面试高频问题四个维度,结合 Spring 源码与工程实践 ...

  7. [abc306h/ex] Balance Scale

    Ex - Balance Scale 考虑只有>和<的情况,相当于给每条边定向,当且仅当成环时不合法,那么方案数就是\(DAG\)的方案数 对于=,就是将两个点合并 然后对于一般的求\(n ...

  8. Java学习篇(三)—— 集合框架

    集合框架是什么? 对容器的学习建议结合leecode,了解每一个容器的增删改查操作. 数据结构里学习了几种数据结构类型:数组.链表.栈.队列.树.哈希表.堆.在C++中,C++ STL提供了数组vec ...

  9. ATE测试工程师是做什么的?

    这是IC男奋斗史的第28篇原创 本文1437字,预计阅读4分钟. 前两天我们一个做封装的同事问我说,目前ATE测试工程师在就业市场上很火,很多公司都在急招,猎头也让他帮忙有偿推荐候选人.ATE测试工程 ...

  10. 特殊恢复:oradebug推进Linux平台SCN的值

    我们的文章会在微信公众号IT民工的龙马人生和博客网站( www.htz.pw )同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢! 由于博客中有大量代码,通过页面浏览效 ...