Just Do Java

Java 's Blog


  • 首页

  • 分类

  • 作者

  • 归档

  • 关于

不的不说,面试6家拿下三家的Offer确实有点压力

发表于 2021-03-18 | 分类于 抓包工具

阿粉的一个朋友,在26岁的年纪终于决定结束北漂的生涯,选择了回二线城市当个程序员继续发展,尽管这个朋友想的是二线城市可能技术不会那么领先,但是实时完完全全的打了他的脸。他被面试官的题目给整的彻底福气。

<–more–>

1.去二线城市为什么可以不看学历

这个朋友在最近在和阿粉的聊天,说到了自己想要回二线城市发展的原因,一方面是北京这种大城市,对学历要求比较高,另外一方面就是觉得自己年纪到了,也应该是时候回家去发展,结婚,生子,安稳的生活,不再北漂。

不得不说,北上广这大城市固然的好,有他们天然的优势,技术超前,工资待遇也不错,但是对一些程序员真的是不是特别的友好,比如专科的程序员,阿粉也能理解,毕竟科班出身,对很多知识从大学的时候就开始学习,当然比一些不是科班出身的半路出家的程序员要强一点,这是公认的。

毕竟HR在筛选人才的时候,是选择需要去用最短的时间找到最合适的人,不得不说,有些专科程序员很牛逼,这是公认的,但是在一大群人中去寻找这个适合的人,是不是时间上可能有点麻烦。这是没有任何针对的谈论,大家不要骂小编,这是实话实说。

给大家看几个图,这是在boss上在北京的招聘要求:

比比皆是,这是非常正常的一件事,但是为什么说二线城市可以不看学历了呢?

其实就是一个事,缺人,因为现在培训机构太多了,不管是某马,某鸟,某内,都是非常著名的培训机构,从里面出来的学员比比皆是,水平层次参差不齐,有大神,也有混子,这是无可厚非的,毕竟师傅领进门,修行靠个人,但是二线城市这种情况可能和一线城市相比,差距确实挺大的。给大家举个例子。

大家都知道,培训机构出来的学生,简历很多都是经过美化的,但是这种美化,实际上有些美化的确实很厉害,让很多HR都分辨不出来,但是面试的时候的水平,大家肯定也都能知道,给大家看一组数据以及阿粉朋友的真实情况,同样的三年的工作经验。在二线城市是什么样子的。

某培训机构发布的薪资截图,(二线城市):

这是从他们的网站上实际去扒拉下来的内容,因为这是他们内部发的,都是二线城市,而就在同样的二线城市,同样的三年的工作经验,

这个朋友很硬气,就是最低10K,低了不干,从周一开始,面试了6家,一直到周三,面试通过了3家,最低给的是9K的薪资,因为有工作经验,虽然面试的时候有一些问题感觉自己回答的并不是很好,但是一样是可以拿到10K的工资,这个朋友有自信感觉自己值这个价位。

而培训机构为什么会低一点,因为没有实际的工作经验,不敢要,对方压工资,他也觉得没有任何问题,同样的三年经验,这就是差距。

说完了这些,阿粉在接下来给大家说说另外一个事。

2.面试为什么说对自己的提升最快

阿粉一向都是相信,面试是对自己提升最快的时候,因为面试官会给你准备出各种各样的问题,有时候同一个问题,会有很多种不同的问法,而这些不同问法,需要你去实实在在的去学习,哪怕你觉得这家面试咋这么难,你出门的时候也可以掏出你的手机,在备忘录里面写下都问了你哪些问题,你觉得自己回答的不够好的。

比如说:

不的不说,这种时候,真的是能够让你快速成长,你既然在简历里面写了你了解分布式,那么你一定知道分布式会出现什么样的问题,并且你们是怎么解决的,就可能因为你处理过这块的内容,面试官就会对你的看法可能产生改变。

问到了你不会的内容,知识盲区,阿粉还是那句话,不会,可以就只是说那你了解这一点,说出自己的了解内容,不要去不懂装懂就行,没有人能把所有内容说的滚瓜烂熟的。

就比如阿粉之前遇到过一个面试官:问了一个很有意思的问题,你对Linux熟不熟?阿粉就回答,还可以,但是高级操作很少做,我们在开发的时候最多有时候就是需要去对服务器上的文件进行部署,也就是替换一些我们修改过的类,比如说拷贝啦,解压,压缩,还有就是看内存,找线程这块的,然后把对应的命令说出来,一般这种东西我们不是做职业运维的,所以这东西不是经常性的使用,都是自己做好笔记,然后有需要的时候,直接去笔记里面去找出来用就行。

面试官也很鸡贼,能把你记录的笔记给我看看么?

于是阿粉拿出了珍藏多年的“干货”,分享给了面试官,然后面试官看了一下,说,还不错,我看你还有记录处理bug的习惯呢?

是的,阿粉在开发的时候有时候遇到很多稀奇古怪的bug的时候,在处理结束之后,阿粉会把这个bug自己做个笔记,防止以后遇到了还得去Google,去问别人,有时候看一眼之后,想想自己之前遇到过么?找找笔记不也是很香么?

就像上图,只是一部分,阿粉的笔记有点多,不能分享给大家,只是给大家提供一个比较好的习惯。

阿粉也在给大家把最近面试的问题也给大家梳理一下,看大家有没有遇到类似的,可以提前准备。

3.实地面试,问到了哪些问题

  • 你们项目的Redis存取数据有没有遇到过问题?

  • Redis能处理百万以上的数据么?需要你做些什么东西呢?

  • 说一些你对乐观锁和悲观锁的看法

  • 你们是怎么处理高并发的

  • Redis数据同步是怎么处理的

  • 分布式事务是怎么处理的

这些问题阿粉也不给大家一一的去赘述了,因为阿粉相信大家一定能够掌握,也希望在这金三银四面试季,各位能够找到合适的工作。

阅读全文 »

Java 面试虐我千百遍,我带面试如初恋【觉得值得收藏】

发表于 2021-03-14 | 分类于 Java

Hello 大家好,传说中的金三银四就在当下,阿粉知道很多小伙伴都在蠢蠢欲动,明修栈道暗度陈仓,表面上还在公司踏踏实实的干着,心里早就有了自己的小算盘。作为一个 Java 工程师,涉及到的领域会很多,每次面试都会被问到头破血流,阿粉今天就给大家带来一份面试题目,具体是哪家公司的阿粉就不说了,因为不管哪家其实都差不多,重点是自己要完全掌握,才能更好的应对。

技术细节

第一个部分我们先来看技术细节,往往很多公司的第一面都是考察技术细节的,小伙伴们如果在面试的过程中经常一面没有过的话,那往往是基础不扎实,这个时候我们就需要静下心来,好好的补一下基础知识,看看面经刷刷题,或者看看我们 Java 极客技术的文章都是可以的。

  1. 三分钟自我介绍

    自我介绍这块是面试的一个开场白,这块没什么好说的,不过建议大家可以将自己想表述的内容以文字的形式写下来,然后熟练的背诵下来,这样不管是在电话面试,视频面试还是现场面试都能够流畅的有条理的表述出来。

  2. 最熟悉的领域是什么?

    这个问题给大家一个忠告!千万别说自己不会的领域,不然整个面试还没开始就已经结束了,千万别为了表现,说自己什么领域都擅长,这样在被面试官问了几个问题都答不上来的话,那基本上就判了死刑了,而且整个印象都不会好,以后再想面试都很难了。所以这里我们实事求是就好,熟悉什么就说什么,不熟悉的就不说。

  3. Redis 的常用数据类型有哪些?

    Redis 常用的五大数据类型,String,Hash,List,Set,Sorted Set。然后不要说完这几个类型就不说了,可以再扩展下,顺便可以说下每种数据类型在自己的项目里面是否有用到,具体使用在什么地方,为什么选用这种类型。面试的过程中虽然说是一问一答,但是也要适当的扩展一下,把自己知道的东西稍等多说一点,让面试官知道你是真正的了解。问你数据类型有哪些你就回答有这五个,这样的面试就比较枯燥,很难继续进展下去,给面试官的印象就不好。

  4. Redis 的持久化了解吗?有几种方式,区别是什么?

    关于 Redis 的持久化,我们公众号之前有篇文章专门写了,大家可以看下面试官:请说下 Redis 是如何保证在宕机后数据不丢失的

  5. Redis 的三种集群模式了解吗?

    关于 Redis 的集群模式不管自己有没有搭建过,都可以把自己知道的说出来,主从模式,哨兵模式,集群模式,以及三种模式的区别。

  6. Redis 的主从数据是如何同步的?

    Redis 的主从数据同步跟上面的集群模式这两点都可以单独成一篇文章了,后期阿粉单独分享。

  7. Redis 使用的场景?

    Redis 作为目前主流的缓存数据库,使用的场景有很多,用户 token 的存储,热点数据的存储,当成队列使用,分布式锁,计数器,布隆过滤器等等,结合自己项目中使用的场景可以跟面试官多聊一下。

  8. Redis 为何性能如此高?

    Redis 官文号称最高 QPS 可以达到十万,那么问什么 Redis 可以有这么高的并发呢?主要是 Redis 采用多路复用 IO 模型,在处理命令的时候是采用单线程去处理的,同时 Redis 的底层采用 C 语言编写,数据结构也相对简单,所以才能如此快速。

  9. MySQL 的存储引擎有哪些,InnoDB 和 MyISAM有什么区别?

    MySQL 常用的存储引擎主要有 InnoDB 和 MyISAM 以及 Memory,其中关于 InnoDB 和 MyISAM 的区别公众号前面有文章详细说过,感兴趣的小伙伴可以看一下面试必问的 MySQL 知识点,你还有哪些没准备好,赶紧收藏脑图!

  10. 什么是事务?事务的隔离级别有哪些?为什么需要事务的隔离级别?

事务的内容在上面的文章里面也有详细说到,可以进去看下。

  1. SQL 优化方面的内容?分库分表是怎么做的?

    关于 SQL 优化也是一个老生常谈的话题,在面试中也经常会被问到,关于 SQL 优化,有一篇文章很不错,推荐给大家,里面讲到了很多优化的点,认真记下来对我们很有帮助52条SQL语句性能优化策略,建议收藏

  2. MySQL 的索引采用的是什么存储结构?为什么采用这个?

    MySQL 的索引也是一个面试中逃避不了的一个问题,详细的内容可以参考公号前面的文章为什么Mysql的常用引擎都默认使用B+树作为索引?

  3. Java 中的线程池有哪些默认实现,几个核心参数是如何配合工作的?拒绝策略有哪些?

    Java 中的线程池有默认的实现,但是日常工作的时候我们一般都会手动创建线程池,具体的内容可以看文章面试官因为线程池,让我出门左拐!,聊聊面试中的 Java 线程池

  4. Java 中 volatile, synchronized 的作用是什么?

    这两个关键词在 Java 中的重要性已经不言而喻了,不用阿粉在多说什么了,那对于这两个关键词小伙伴都理解了吗?同样的,我们前面也有写过,诡异的并发之可见性

  5. 什么是类加载机制?

    啥也不说了,直接看文章 JVM 是如何加载 Java 类的?,相信看完你就懂了。

看完上面 15 个面试题,很容易发现一个问题,那就是真正面试的时候很多东西我们公众号之前都有写过,只要好好的把这些东西消化了,再加上面试的时候好好发挥,不说一定能拿到 offer,至少前几面都能很完美的过。看到这样是不是觉得这篇文章值得收藏呢?收藏起来,以后在面试前或者面试别人的时候都可以拿出来用一用。

项目部分

上面提到的是一些技术细节,其实还有很多很多东西,毕竟 Java 的领域涉及到很多地方,像这种面试题简直可以无休止的问下去。聊完了技术细节,后面一般都是针对项目开始询问,每个人的项目不一样,涉及的领域也不一样,不具有参考性,但是一些通用的问题还是要准备下的,比如

  1. 项目运用到了哪些技术,整个的框架是什么样子的?
  2. 你在这个项目中主要负责哪些内容,你是怎么设计的?
  3. 在做这个项目的时候有没有遇到什么技术难点,你是怎么解决的?
  4. 项目上线后的 QPS 是多少,用户量有多少,有没有出现过 Fullgc 或者线上 OOM 的情况,你是如何处理的?
  5. 模块与模块之前是怎样调用的?HTTP 还是 RPC?RPC 框架用的是什么?了解其实现原理吗?
  6. 做这个项目的目的是什么?你对项目所涉及的行业有了解吗?

上面这几个都是日常做项目的时候需要知道和思考的,技术上面涉及到的东西肯定要知道,在最后一个问题上面往往很多小伙伴会忽略,认为不就是一个项目吗?关行业什么事情?其实不是这样的,了解了一个项目的背景和价值可以更好的帮忙我们理解业务需求,从而做到更好的实现,而且对于一些想在某个方向长期发展的朋友,那就更有必要了。

架构部署方面

  1. 你们项目是分布式的吗?怎么部署的?
  2. 如何在不增加机器的情况下提升 QPS?
  3. 如何降低各个服务或者机房之间网络通信耗时?
  4. 如果机房突然出现故障如何保证数据不丢失?
  5. 当下游服务出现问题时如何做熔断限流保证自己的服务不受影响?

上面几个问题是阿粉遇到的几个架构部署相关的问题,关于这一方面阿粉也不是特殊熟悉,所以就不做回答了,相信关注阿粉的小伙伴都是厉害的,可以在我们的留言下面回答下,大家一起学习谈论,帮助他人同时也是提升自己。

阅读全文 »

常见的垃圾回收器你知道有哪些吗?

发表于 2021-03-07 | 分类于 Java

作为一个 Java 开发,在面试的过程中垃圾回收器是经常会被问到的一个问题,随着 Java 的发展,垃圾回收器也经历了很多的发展。大家熟知的垃圾回收器主要有下面几种。

  1. Serial 单线程新生代复制算法的垃圾回收器;
  2. SerialOld 垃圾回收器,是一种单线程老年代标记整理算法;
  3. ParNew 垃圾回收器,是 Serial 的多线程实现,采用复制算法实现;
  4. Parallel Scavenge 垃圾回收器,是一种高效的多线程复制算法;
  5. ParallelOld 垃圾回收器,是 Parallel Scavenge 的一种老年代的多线程标记整理算法;
  6. CMS 垃圾回收器,是一种多线程标记清除算法,后面会详细介绍;
  7. G1 垃圾回收器,是一种高吞吐量的垃圾回收器。

回收算法

在介绍垃圾回收器之前,我们先了解一下垃圾回收器背后的算法,每个垃圾回收器都是具体算法的实现,不同的垃圾回收器只是背后的算法不同而已,下面就先简单介绍下具体的算法。

标记清除

标记清除算法是一种先标记,后清除的算法,在第一次扫描的时候先标记出所有需要清理的内存,将所有需要回收的内存都标记过后,一次性清理掉。这种算法简单但是效率低,而且内存碎片化严重。内存一旦碎片化严重的话,就会浪费内存,无法分配较大的对象。

复制算法

复制算法的实现方式比较简洁明了,就是霸道的把内存分成两部分,在平时使用的时候只用其中的固定一份,在当需要进行 GC 的时候,把存活的对象复制到另一部分中,然后将已经使用的内存全部清理掉。这种算法可以解决碎片化的问题,但是缺点也很明显,就是浪费内存,有一半的内存都不能使用。

标记整理算法

既然标记清除和复制算法各有优缺点,那自然的我们就想到是否可以把这两种算法结合起来,于是就出现了标记整理算法。标记阶段是标记清除算法一样,先标记出需要回收的部分,不过清除阶段不是直接清除,而是把存活的对象往内存的一端进行移动,然后清除剩下的部分。

标记整理的算法虽然可以解决上面两个算法的一些问题,但是还是需要先进行标记,然后进行移动,整个效率还是偏低的。

分代回收算法

分代回收算法是目前使用较多的一种算法,这个不是一个新的算法,只是将内存进行的划分,不同区域的内存使用不同的算法。根据对象的存活时间将内存的划分为新生代和老年代,其中新生代包含 Eden 区和 S0,S1。在新生代中使用是复制算法,在进行对象内存分配的时候只会使用 Eden 和 S0 区,当发生 GC 的时候,会将存活的对象复制到 S1 区,然后循环往复进行复制。当某个对象在进行了 15 次GC 后依旧存活,那这个对象就会进入老年代。老年代因为每次回收的对象都会比较少,因此使用的是标记整理算法。

垃圾回收器

上面虽然提到了好几个垃圾回收器,但是目前主流的垃圾回收器只有 CMS 和 G1。下面就跟大家聊下这两个垃圾回收器。

CMS 垃圾回收器

CMS 全称 Concurrent Mark Sweep 并发标记清除垃圾回收器。CMS 是一种以获取最短停顿时间为目的的垃圾回收器。提到停顿时间,我们都知道任何垃圾回收器在进行工作的时候都会出现 STW,Stop the World 停止用户进程,这对业务来说只很难接受的,但是现在市面上所有的垃圾回收器都无法避免这个问题,只能最大化的去优化,从而降低停顿的时间。

CMS 虽然被称为是并发的垃圾回收器,但是也并不是完全并发的,从名字上我们可以看到是采用标记-清除算法来实现的,整个实现过程分为五个步骤:

  1. 初始标记:暂停所有线程,从来可达性分析来标记对象,这也是 CMS 垃圾回收器第一个 STW 的时候;
  2. 并发标记:并发标记的时候 GC 线程和用户线程是同时存在的,这个过程中会记录所有可达的对象,但是这个过程结束过后由于用户线程一直在运行所以还会产生新的引用更新,也就是需要下一步了;
  3. 并发预清理:这个阶段用户线程和 GC 线程同时运行,GC 线程会进行一下预清理的动作;
  4. 重新标记:重新标记这个阶段会暂停用户线程,将上一步并发标记过程中用户线程引起的更新进行修正,这个时间会比初始标记时间长,但是会比并发标记时间短一点;
  5. 并发清除:在所有需要清理的对象都被标记完过后就会执行最后一步清理的动作。清理的时候用户线程是可以继续运行的,GC 线程只清理标记的区域。

G1 垃圾回收器

G1 全称 Garbage-First 是一种面向服务器的垃圾回收器,通过将堆内存划分为多个 Region 来实现可预测的停顿时间模型。在 G1 当中,新生代和老年代已经不再是物理隔离,而都是被划分一个个 Region 区域。正是由于这种可预测的时间停顿模型让 G1 成为了一个高吞吐量的垃圾回收器。G1 能充分利用 CPU,多核环境下可以缩短 STW 的时间。

G1 垃圾回收器的整个实现过程分为四个步骤:

  1. 初始标记:通过可达性分析标记 GC Roots 的直接关联对象,这个阶段与 CMS 一样需要 STW;
  2. 并发标记:并发标记是通过 GC Roots 找到存活的对象,这个阶段 GC 线程是与用户线程同时运行的,并且这个阶段的时间比初始标记长;
  3. 最终标记:最终标记跟 CMS 的重新标记一样,也是为了修正并发标记过程中因用户线程继续运行而导致产生新的引用更新;同样的这里也需要 STW;
  4. 筛选回收:筛选回收这里会对每个 Region 的回收成本进行排序,根据用户期望的停顿时间来制定收回计划,这也就是可预测的停顿时间模型的体现之处,这个阶段 GC 线程是与用户线程同时运行的。

总结

虽然说 Java 开发不用程序员去手动创建和回收内存,但是了解和掌握垃圾回收器是每个 Java 程序员必须要掌握的,不仅仅是面试的过程中会被问到,对自己的职业发展也是很有帮助的。本文是阿粉自己学习和整理的,部分资料参考网络上,分享给大家,帮助大家一起成长。

阅读全文 »

手把手教你学会如何使用WireShark进行抓包

发表于 2021-03-03 | 分类于 抓包工具

前段时间,因为同事需要分析数据,所以使用了WireShark,但是呢,小伙子不太知道怎么抓取数据,于是就来询问了一下阿粉,阿粉就手把手的教给他,如何使用WireShark进行抓包分析,在这里也分享给大家。

阅读全文 »

开发者搜索,使用魔法打败魔法

发表于 2021-02-28 | 分类于 Java

大家好,我是阿粉~

搜索引擎应该各位程序员开发中离不开软件吧,日常开发不懂的地方,直接上搜索引擎,大半问题都可以得到解答。

我们时常会把将这种方式戏称为「面向搜索引擎编程」,讲真的如果哪一天搜索引擎真不能打开了,阿粉相信大半程序可能开展不了工作了[狗头]。

目前国内搜索引擎,百度一家独大,如果我们没有特殊的科学上网能力,那只能通过百度了。

但是现在百度搜索体验怎么样,各位读者平常使用过程应该都有点体会吧?

打开「www.baidu.com」,搜索框下方很大的版面是当天的时事新闻,然后右侧就是当天热搜。

这样的页面展示,对于普通大众来说,可以说非常方便。

但是对于开发者来说,干扰信息有点太多了。阿粉记得之前有几次搜索东西,然后就被傍边信息吸引,吃了半天的瓜。

等阿粉回过头的时候才发现,哎,我刚才要干什么来着?

如果此时你没有被这些信息干扰,输入关键信息,然后你就会看到第一条结果是广告,然后往下滑,第四条第五条还是广告。

翻了好几页,可能才找到的想要的结果。

这真不是我们开发者想要的搜索的引擎!!!

开发者搜索

百度搜索实在太难用了,所以阿粉真的好久没有使用了。最近上网的时候无意间发现一款搜索引擎,名叫「开发者搜索」,地址为:

https://kaifa.baidu.com/

从名字上就可以看出,这款产品专门针对「开发者」而开发,另外从网址我们可以看到这款产品竟然是百度开发的???

这个真的有点意外!

果然只有使用魔法才能打败魔法。

打开这个网页,可以看到这个页面真的非常简洁,只有一个简单的搜索框,其他任何信息都没有!

这个页面让阿粉想到很早之前百度的搜索页面~

输入想要搜索信息之后,可以看到搜索结果非常聚焦,没有任何广告。

页面布局与原来百度页面类似:

  • 左侧顶部是各类标签,下方则是搜索页面
  • 右侧为百度百科,以及 gitee 上开源项目

仔细看开发者搜索的搜索结果,可以发现目前搜索结果仅仅来源于知乎、CSDN以及博客园三个站点。从这一点来说,其实比较可惜了,虽然这三个网站已经包含大量的文章了,但是其实还是源源不够。

其实很多个人独立网站,比如阮一峰老师,左耳朵耗子的博客网站,里面的内容其实质量也比较高。

不过也可以理解,毕竟现在「开发者搜索」仅仅是 Beta 版本,不完善也是正常。

那这里还是希望百度可以完善这款产品,真正帮到国内的开发者。

其他搜索引擎

那说实话,现在对开发者最友好的引擎,阿粉觉得还是 「Google 」搜索了。

无论是搜索结果的准确度,又或是显示站点的质量,都比百度号上一截。

不过由于神秘的东方的力量,这网站已经不能在国内正常打开。

不过这一点,我觉得应该难不倒爱折腾的程序员们,所以如果你能打开「Google 」,那就赶紧抛弃百度吧~

最后对于一些没办法打开「Google 」的小伙伴,再推荐一个搜索引擎,那就是微软的「必应搜索」。

打开必应搜索的首页,可以看到页面非常简洁,并且背景图还是一些高清壁纸,视觉感非常舒服。

另外可以看到搜索框上可以分为国内版跟国际版,那这两者主要区别在于:

  • 国内版适合中文搜索
  • 国际版适合使用英文搜索

最后

如果大家可以打开「Google」搜索,那阿粉真心建议你们直接使用 Google。阿粉这几年用下来,使用体验真的非常好,搜索结果也比较准确,真的非常适合开发者使用。

哎,百度「开发者搜索」加把劲吧,什么时候真的把功能完善好,让国内开发者搜索更加简单高效一点~

加油吧,百度~

阅读全文 »

当代码 Review 变成形式主义过后

发表于 2021-02-27 | 分类于 Java

你做过代码 Review 吗?

Hello 大家好我是阿粉,代码 Review 相信大家一定不会陌生,但是真正在日常工作中能使用并且坚持执行下去的公司或者团队,阿粉觉得并不多,但是代码 Review 的好处大家都是有目共睹的,很多招聘岗位上面都有这样的要求,坚持执行代码 Review 对团队,对公司是很有好处的,特别是对写代码的同事!每个一起阅读代码的同事都会提出一些自己的建议 ,这些建议都是很宝贵的资源,往往都会有很大的收获。

那么如何做好一场代码 Review 呢?想要做好一场合格的代码 Review,首先我们需要知道代码 Review 的过程中都有哪些角色以及需要怎样的流程。

角色

  1. 代码的作者 Author;
  2. 阅读代码的同事 Reviewer;
  3. 主持人 Host;
  4. 记录员 Recorder。

流程

  1. 提前一天发送 Review 代码邮件给相关人员,确定 Host 和 Recorder;
  2. 由 Host 主持 Review 会议;
  3. 由 Reviewer 和 Author 进行代码走读;
  4. 由 Recorder 进行改进记录。

在做代码 Review 之前 Author 需要提前一天需要发正式邮件给相关人员,并且将需要被 Review 的代码通过邮件附件的方式,发送给相关的 Reviewer 让他们提前阅读,这样有助于 Review 的时候可以更高效的进行。并且提前沟通好代码 Review 的会议 Host 和 Recorder。Host 在会议过程中负责组织大家发言和维护秩序,让代码 Review 更高效;Recorder 则负责将整个 Review 过程中提到的需要优化和改进的点进行文档形式的记录,记录的信息需要言简意赅,将哪个文件哪一行代码,问题是什么,建议怎么优化都需要记下来,并且在会议结束过后以邮件的形式发送给 Author 和抄送大家。

Review

在进行代码 Review 的时候 Author 需要从文件的第一行开始进行一行行的代码走读,对每一行的代码进行描述,这里需要注意的是不要认为某个功能很简单,就可以一句带过,往往很多线上 Bug 都是因为这种忽略导致的。走读代码的时候 Author 需要解释清楚每一行代码的含义,说明这行代码是干嘛的,为什么要这样写。Reviewer 则需要根据 Author 的描述再结合自己之前阅读代码的理解进行提问或者改进方案。

代码走读的过程持续进行的同时 Recorder 需要及时记录需要改进的内容,并把提出的优化方案记录下来。代码走读的过程是整个 Review 的核心,在这个环节中我们需要注意很多东西。知乎上面有个提问大家的公司的 Code Review 都是怎么做的?遇到过哪些问题?,上面有个回答提出的几个点很不错,我觉得有必要分享给大家,对我们的整个 Review 会很有帮助,特别是当自己是 Reviewer 的时候,需要格外注意。

  1. 对事不对人。要记住大家都是同事,今天自己是 Reviewer 来对别人的代码进行 Review,哪天别人就会对自己的代码进行 Review,所以在整个代码 Review 的环节中,我们可以提出自己的建议,但是需要注意自己的用词和语气,虽然程序员有鄙视链,并且都认为别人的代码垃圾,但是这种时候不能瞎说,容易打架的,还是要谦虚点。
  2. 用正面的词汇进行评价。跟上面说的一样,我们需要用正面的词汇进行评价代码,考虑 Author 的情绪,即使代码写的不好。这个很好理解,毕竟代码 Review 是在很多人面前的,对 Author 来说,让别人看自己写的代码就像在别人面前裸奔一样,所以我们不仅要提出意见在好的地方我们也要进行正向的表扬。
  3. 如果有更好的方案一定要提出来。代码 Review 的目的就是优化代码,找出代码中的隐藏 BUG 以及逻辑问题。所以如果发现了代码有任何不优雅的地方或者会出现问题的地方一定要及时的提出来,不要担心自己说的不对,或者考虑 Author 的面子问题,不指出来,提供更好的解决方案,让大家一起进步。
  4. Review 会议是 Review 代码,而不是讨论需求。这一点也是很容易被带偏的一个点,经常我们会发现在 Review 的过程中,慢慢的就变成了讨论需求了,这种情况一定要避免发生,不然整个代码 Review 环节就是失败的,无法进行下去了。所以 Author 在发起代码 Review 邀请时一定要确保自己理解的需求是正确的,避免浪费大家的时间。
  5. 高效的进行。进行代码 Review 的时候我们需要注意一定要高效,如何做到高效就需要每个人都做好相应的准备,Author 需要对自己的代码进行熟练的讲解,Reviewer 需要在参加会议之前将需要 Review 的代码看一下,提前找到可能隐藏的 Bug,对于不懂地方需要做好记录,方便开会的时候及时指出。
  6. 避免形式主义。这一点也是被很多小伙伴忽略的一点,随着代码 Review 的次数越来越多,很多小伙伴可能就把这个当成一个任务,慢慢的就变成了形式主义,而不是注重实际,每次代码 Review 的时候就很随意,长期这样下去对公司,团队以及自己都不好,而且还浪费时间。

总结

阿粉今天给大家介绍了一个如何做一个合格的代码 Review,当然这只是阿粉自己的一些见解,大家有任何意见可以在评论区给我们留言,也可以加入我们的微信群,大家一起交流学习。

阅读全文 »

看完这篇文章,别再说不会 Redis 的高级特性了

发表于 2021-02-22 | 分类于 Redis

Redis 作为后端工程师必备的技能,阿粉每次面试的时候都会被问到,阿粉特意把公号前面发过的 Redis 系列文章整理出来,自己学习同时也帮助大家一起学习。

Redis 的数据类型有哪些?

Redis 五种数据类型,每种数据类型都有相关的命令,几种类型分别如下:

  1. String(字符串)

  2. List(列表)

  3. Hash(字典)

  4. Set(集合)

  5. Sorted Set(有序集合)

Redis 有五种常见的数据类型,每种数据类型都有各自的使用场景,通用的字符串类型使用最为广泛,普通的 Key/Value 都是这种类型;列表类型使用的场景经常有粉丝列表,关注列表的场景;字典类型即哈希表结构,这个类型的使用场景也很广泛,在各种系统里面都会用到,可以用来存放用户或者设备的信息,类似于 HashMap 的结构;Redis set 提供的功能与列表类型类似也是一个列表的功能,区别是 Set 是去重的;有序集合功能与 Set 一样,只不过是有顺序的。

Redis 的内存回收与Key 的过期策略

Redis 内存过期策略

过期策略的配置

Redis 随着使用的时间越来越长,占用的内存会越来越大,那么当 Redis 内存不够的时候,我们要知道 Redis 是根据什么策略来淘汰数据的,在配置文件中我们使用 maxmemory-policy 来配置策略,如下图

image-20191113214158260

我们可以看到策略的值由如下几种:

  1. volatile-lru: 在所有带有过期时间的 key 中使用 LRU 算法淘汰数据;
  2. alkeys-lru: 在所有的 key 中使用最近最少被使用 LRU 算法淘汰数据,保证新加入的数据正常;
  3. volatile-random: 在所有带有过期时间的 key 中随机淘汰数据;
  4. allkeys-random: 在所有的 key 中随机淘汰数据;
  5. volatile-ttl: 在所有带有过期时间的 key 中,淘汰最早会过期的数据;
  6. noeviction: 不回收,当达到最大内存的时候,在增加新数据的时候会返回 error,不会清除旧数据,这是 Redis 的默认策略;

volatile-lru, volatile-random, volatile-ttl 这几种情况在 Redis 中没有带有过期 Key 的时候跟 noeviction 策略是一样的。淘汰策略是可以动态调整的,调整的时候是不需要重启的,原文是这样说的,我们可以根据自己 Redis 的模式来动态调整策略。 ”To pick the right eviction policy is important depending on the access pattern of your application, however you can reconfigure the policy at runtime while the application is running, and monitor the number of cache misses and hits using the Redis INFO output in order to tune your setup.“

策略的执行过程

  1. 客户端运行命令,添加数据申请内存;
  2. Redis 会检查内存的使用情况,如果已经超过的最大限制,就是根据配置的内存淘汰策略去淘汰相应的 key,从而保证新数据正常添加;
  3. 继续执行命令。

近似的 LRU 算法

Redis 中的 LRU 算法不是精确的 LRU 算法,而是一种经过采样的LRU,我们可以通过在配置文件中设置 maxmemory-samples 5 来设置采样的大小,默认值为 5,我们可以自行调整。官方提供的采用对比如下,我们可以看到当采用数设置为 10 的时候已经很接近真实的 LRU 算法了。

image-20191113231007725

在 Redis 3.x 以上的版本的中做过优化,目前的近似 LRU 算法以及提升了很大的效率,Redis 之所以不采样实际的 LRU 算法,是因为会耗费很多的内存,原文是这样说的

The reason why Redis does not use a true LRU implementation is because it costs more memory.

Key 的过期策略

设置带有过期时间的 key

前面介绍了 Redis 的内存回收策略,下面我们看看 Key 的过期策略,提到 Key 的过期策略,我们说的当然是带有 expire 时间的 key,如下

image-20191113233350118

通过 redis> set name ziyouu ex 100 命令我们在 Redis 中设置一个 key 为 name,值为 ziyouu 的数据,从上面的截图中我们可以看到右下角有个 TTL,并且每次刷新都是在减少的,说明我们设置带有过期时间的 key 成功了。

Redis 如何清除带有过期时间的 key

对于如何清除过期的 key 通常我们很自然的可以想到就是我们可以给每个 key 加一个定时器,这样当时间到达过期时间的时候就自动删除 key,这种策略我们叫定时策略。这种方式对内存是友好的,因为可以及时清理过期的可以,但是由于每个带有过期时间的 key 都需要一个定时器,所以这种方式对 CPU 是不友好的,会占用很多的 CPU,另外这种方式是一种主动的行为。

有主动也有被动,我们可以不用定时器,而是在每次访问一个 key 的时候再去判断这个 key 是否到达过期时间了,过期了就删除掉。这种方式我们叫做惰性策略,这种方式对 CPU 是友好的,但是对应的也有一个问题,就是如果这些过期的 key 我们再也不会访问,那么永远就不会删除了。

Redis 服务器在真正实现的时候上面的两种方式都会用到,这样就可以得到一种折中的方式。另外在定时策略中,从官网我们可以看到如下说明

Specifically this is what Redis does 10 times per second:

  1. Test 20 random keys from the set of keys with an associated expire.
  2. Delete all the keys found expired.
  3. If more than 25% of keys were expired, start again from step 1.

意思是说 Redis 会在有过期时间的 Key 集合中随机 20 个出来,删掉已经过期的 Key,如果比例超过 25%,再重新执行操作。每秒中会执行 10 个这样的操作。

Redis 的发布订阅功能你知道吗?

发布订阅系统在我们日常的工作中经常会使用到,这种场景大部分情况我们都是使用消息队列的,常用的消息队列有 Kafka,RocketMQ,RabbitMQ,每一种消息队列都有其特性。其实在很多时候我们可能不需要独立部署相应的消息队列,只是简单的使用,而且数据量也不会太大,这种情况下,我们就可以考虑使用 Redis 的 Pub/Sub 模型。

使用方式

发布与订阅

Redis 的发布订阅功能主要由 PUBLISH,SUBSCRIBE,PSUBSCRIBE 命令组成,一个或者多个客户端订阅某个或者多个频道,当其他客户端向该频道发送消息的时候,订阅了该频道的客户端都会收到对应的消息。

image-20191222092832322

上图中有四个客户端,Client 02,Client 03,Client 04 订阅了同一个Sport 频道(Channel),这时当 Client 01 向 Sport Channel 发送消息 “basketball” 的时候,02-04 这三个客户端都同时收到了这条消息。

整个过程的执行命令如下:

首先开四个 Redis 的客户端,然后在 Client 02,Client 03,Client 04 中输入subscribe sport 命令,表示订阅 sport 这个频道

image-20191222093131988

然后在 Client 01 的客户端中输入publish sport basketball 表示向 sport 频道发送消息 “basketball”

image-20191222093149658

这个时候我们在去看下Client 02-04 的客户端,可以看到已经收到了消息了,每个订阅了这个频道的客户端都是一样的。

image-20191222093412149

这里 Client 02-Client 04 三个客户端订阅了 Sport 频道,我们叫做订阅者(subscriber),Client 01 发布消息,我们叫做发布者(publisher),发送的消息就是 message。

模式订阅

前面我们看到的是一个客户端订阅了一个 Channel,事实上单个客户端也可以同时订阅多个 Channel,采用模式匹配的方式,一个客户端可以同时订阅多个 Channel。

image-20191222141017693

如上图 Client 05 通过命令subscribe run 订阅了 run 频道,Client 06 通过命令psubscribe run* 订阅了 run* 匹配的频道。当 Client 07 向 run 频道发送消息 666 的时候,05 和 06 两个客户端都收到消息了;接下来 Client 07 向 run1 和 run_sport 两个频道发送消息的时候,Client 06 依旧可以收到消息,而 Client 05 就收不到了消息了。

Client 05 订阅run 频道和接收到消息:

image-20191222141441686

Client 06 订阅run* 频道和接收到消息:

image-20191222141458065

Client 07 向多个频道发送消息:

image-20191222141514914

通过上面的案例,我们学会了一个客户端可以订阅单个或者多个频道,分别通过subscribe,psubscribe 命令,客户端可以通过 publish 发送相应的消息。

在命令行中我们可以用 Ctrl + C 来取消相关订阅,对应的命令时 unsubscribe channelName。

Pub/Sub 底层存储结构

订阅 Channel

在 Redis 的底层结构中,客户端和频道的订阅关系是通过一个字典加链表的结构保存的,形式如下:

image-20191222161405136

在 Redis 的底层结构中,Redis 服务器结构体中定义了一个 pubsub_channels 字典

1
2
3
4
struct redisServer {
	//用于保存所有频道的订阅关系
	dict *pubsub_channels;
}

在这个字典中,key 代表的是频道名称,value 是一个链表,这个链表里面存放的是所有订阅这个频道的客户端。

所以当有客户端执行订阅频道的动作的时候,服务器就会将客户端与被订阅的频道在 pubsub_channels 字典中进行关联。

这个时候有两种情况:

  • 该渠道是首次被订阅:首次被订阅说明在字典中并不存在该渠道的信息,那么程序首先要创建一个对应的 key,并且要赋值一个空链表,然后将对应的客户端加入到链表中。此时链表只有一个元素。
  • 该渠道已经被其他客户端订阅过:这个时候就直接将对应的客户端信息添加到链表的末尾就好了。

比如,如果有一个新的客户端 Client 08 要订阅 run 渠道,那么上图就会变成

image-20191222161526069

如果 Client 08 要订阅一个新的渠道 new_sport ,那么就会变成

image-20191222161558999

整个订阅的过程可以采用下面伪代码来实现

1
2
3
4
5
6
7
8
9
10
Map<String, List<Object>> pubsub_channels = new HashMap<>();
    public void subscribe(String[] subscribeList, Object client) {
        //遍历所有订阅的 channel,检查是否在 pubsub_channels 中,不在则创建新的 key 和空链表
        for (int i = 0; i < subscribeList.length; i++) {
            if (!pubsub_channels.containsKey(subscribeList[i])) {
                pubsub_channels.put(subscribeList[i], new ArrayList<>());
            }
            pubsub_channels.get(subscribeList[i]).add(client);
        }
    }

取消订阅

上面介绍的是单个 Channel 的订阅,相反的如果一个客户端要取消订阅相关 Channel,则无非是找到对应的 Channel 的链表,从中删除对应的客户端,如果该客户端已经是最后一个了,则将对应 Channel 也删除。

1
2
3
4
5
6
7
8
9
10
public void unSubscribe(String[] subscribeList, Object client) {
        //遍历所有订阅的 channel,依次删除
        for (int i = 0; i < subscribeList.length; i++) {
            pubsub_channels.get(subscribeList[i]).remove(client);
            //如果长度为 0 则清楚 channel
            if (pubsub_channels.get(subscribeList[i]).size() == 0) {
                remove(subscribeList[i]);
            }
        }
    }

模式订阅结构

模式渠道的订阅与单个渠道的订阅类似,不过服务器是将所有模式的订阅关系都保存在服务器状态的pubsub_patterns 属性里面。

1
2
3
4
struct redisServer{
	//保存所有模式订阅关系
	list *pubsub_patterns;
}

与订阅单个 Channel 不同的是,pubsub_patterns 属性是一个链表,不是字典。节点的结构如下:

1
2
3
4
5
6
struct pubsubPattern{
	//订阅模式的客户端
	redisClient *client;
	//被订阅的模式
	robj *pattern;
} pubsubPattern;

其实 client 属性是用来存放对应客户端信息,pattern 是用来存放客户端对应的匹配模式。

所以对应上面的 Client-06 模式匹配的结构存储如下

image-20191222174528367

在pubsub_patterns链表中有一个节点,对应的客户端是 Client-06,对应的匹配模式是run*。

订阅模式

当某个客户端通过命令psubscribe 订阅对应模式的 Channel 时候,服务器会创建一个节点,并将 Client 属性设置为对应的客户端,pattern 属性设置成对应的模式规则,然后添加到链表尾部。

对应的伪代码如下:

1
2
3
4
5
6
7
8
9
10
List<PubSubPattern> pubsub_patterns = new ArrayList<>();
    public void psubscribe(String[] subscribeList, Object client) {
        //遍历所有订阅的 channel,创建节点
        for (int i = 0; i < subscribeList.length; i++) {
            PubSubPattern pubSubPattern = new PubSubPattern();
            pubSubPattern.client = client;
            pubSubPattern.pattern = subscribeList[i];
            pubsub_patterns.add(pubSubPattern);
        }
    }
  1. 创建新节点;
  2. 给节点的属性赋值;
  3. 将节点添加到链表的尾部;

退订模式

退订模式的命令是punsubscribe,客户端使用这个命令来退订一个或者多个模式 Channel。服务器接收到该命令后,会遍历pubsub_patterns链表,将匹配到的 client 和 pattern 属性的节点给删掉。这里需要判断 client 属性和 pattern 属性都合法的时候再进行删除。

伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
public void punsubscribe(String[] subscribeList, Object client) {
        //遍历所有订阅的 channel 相同 client 和 pattern 属性的节点会删除
        for (int i = 0; i < subscribeList.length; i++) {
            for (int j = 0; j < pubsub_patterns.size(); j++) {
                if (pubsub_patterns.get(j).client == client
                && pubsub_patterns.get(j).pattern == subscribeList[i]) {
                    remove(pubsub_patterns);
                }
            }
        }
    }

遍历所有的节点,当匹配到相同 client 属性和 pattern 属性的时候就进行节点删除。

发布消息

发布消息比较好容易理解,当一个客户端执行了publish channelName message 命令的时候,服务器会从pubsub_channels和pubsub_patterns 两个结构中找到符合channelName 的所有 Channel,进行消息的发送。在 pubsub_channels 中只要找到对应的 Channel 的 key 然后向对应的 value 链表中的客户端发送消息就好。

Redis 的持久化你了解吗

持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)。另外我们使用的 Redis 之所以快就是因为数据都存储在内存当中,为了保证在服务器出现异常过后还能恢复数据,所以就有了 Redis 的持久化,Redis 的持久化有两种方式,一种是快照形式 RDB,另一种是增量文件 AOF。

RDB

RDB 持久化方式是会在一个特定的时间间隔里面保存某个时间点的数据快照,我们拿到这个数据快照过后就可以根据这个快照完整的复制出数据。这种方式我们可以用来备份数据,把快照文件备份起来,传送到其他服务器就可以直接恢复数据。但是这只是某个时间点的全部数据,如果我们想要最新的数据,就只能定期的去生成快照文件。

RDB 的实现主要是通过创建一个子进程来实现 RDB 文件的快照生成,通过子进程来实现备份功能,不会影响主进程的性能。同时上面也提到 RDB 的快照文件是保存一定时间间隔的数据的,这就会导致如果时间间隔过长,服务器出现异常还没来得及生成快照的时候就会丢失这个间隔时间的所有数据;那有同学就会说,我们可以把时间间隔设置的短一点,适当的缩短是可以的,但是如果间隔时间段设置短一点频繁的生成快照对系统还是会有影响的,特别是在数据量大的情况下,高性能的环境下是不允许这种情况出现的。

我们可以在 redis.conf 进行 RDB 的相关配置,配置生成快照的策略,以及日志文件的路径和名称。还有定时备份规则,如下图所示,里面的注释写的很清楚,简单说就是在多少时间以内多少个 key 变化了就会触发快照。如save 300 10 表示在 5 分钟内如果有 10 个 key 发生了变化就会触发生产快照,其他的同理。

除了我们在配置文件中配置自动生成快照文件之外,Redis 本身提供了相关的命令可以让我们手动生成快照文件,分别是 SAVE 和 BGSAVE ,这两个命令功能相同但是方式和效果不一样,SAVE 命令执行完后阻塞服务器进程,阻塞过后服务器就不能处理任何请求,所以在生产上不能用,和SAVE 命令直接阻塞服务器进程的做法不同,BGSAVE 命令是生成一个子进程,通过子进程来创建 RDB 文件,主进程依旧可以处理接受到的命令,从而不会阻塞服务器,在生产上可以使用。

阿粉在这里测试一下自动生成快照,我们修改一下快照的生成策略为save 10 2,然后在本地启动Redis 服务,并用 redis-cli 链接进入,依次步骤如下

  1. 修改配置,如下

  2. 启动 Redis 服务,我们可以从启动日志中看到,默认是会先读取 RDB 文件进行恢复的

  3. 链接 Redis 服务,并在 10s 内设置 3 个 key

  4. 这个时候我们会看到 Redis 的日志里面会输出下面内容,因为触发了规则,所以开启子进程进行数据备份,同时在对应的文件路径下面,我们也看到了 rdb 文件。

从上面可以看出,我们配置的规则生效了,也成功的生成了 RDB 文件, 后续在服务器出现异常的情况,只要重新启动就会读取对应的 RDB 文件进行数据备份。

AOF

AOF 是一种追加执行命令的形式,它跟 RDB 的区别是,AOF 并不是把数据保存下来,而是保存执行的动作。在开启 AOF 功能的时候,客户端连接后执行的每一条命令都会被记录下来。这其实让阿粉想起来的 MySQL 的 binlog 日志,也是记录操作的命令,后续可以根据文件去恢复数据。

AOF 是追加命令格式的文件,同样的我们可以定义多长时间把数据同步一次,Redis 本身提供了三种策略来实现命令的同步,分别是不进行同步,每秒同步一次,以及当有查询的时候同步一次。默认的策略也是使用最多的策略就是每秒同步一次,这样我们可以知道,丢失的数据最多也就只有一秒钟的数据。有了这种机制,AOF 会比 RDB 可靠很多,但是因为文件里面存在的是执行的命令,所以AOF 的文件一般也会比 RDB 的文件大点。

Redis 的 AOF 功能,默认是没有开启的,我们可以通过在配置文件中配置appendonly yes 是功能开启,同时配置同步策略appendfsync everysec 开启每秒钟同步一次,我们拿到 AOF 文件过后,可以根据这个文件恢复数据。

同样的我们在redis.conf 中可以看到默认是没有开启 AOF 功能的,并且我们也可以指定对应的文件名称和路径。

接下来,我们测试一下开启 AOF 功能,先修改配置然后重启 Redis 的服务器,我们会发现已经没有读取 RDB 文件的日志了,并且在日志文件路径下面已经生成了一个 aof 文件。需要注意的是,因为我们重启的服务,并且开启了 AOF,所以现在 Redis 服务器里面并没有我们之前添加的数据(说明什么问题呢?)。

接下来我们使用客户端连接进入,设置如下值,接下来我们可以看看 aof 文件里面的内容

我们可以看到aof 文件里面的内容就是执行的命令,只不过是以一种固定的格式存储的,我们在备份的时候如果不需要哪些数据,可以手动删掉对应的命令就可以重新备份数据。

Redis 的有几种集群模式

虽然说单机 Redis 理论上可以达到 10 万并发而且也可以进行持久化,但是在生产环境中真正使用的时候,我相信没有哪个公司敢这样使用,当数据量达到一定的规模的时候肯定是要上 Redis 集群的。

Redis 的模式有主从复制模式,哨兵模式以及集群模式,这三种模式的涉及到篇幅内容会比较多,阿粉后面会单独写一篇文章来介绍,感兴趣的小伙伴可以先自己学习下。

阅读全文 »

从毕业到实习再到工作,感觉自己走了狗屎运

发表于 2021-02-14 | 分类于 程序人生

相信关注我们 Java 极客技术的朋友大部分都是程序员,那么你还记得自己是怎么踏上这条致(不)富(归)路的吗?

今天大年初五,距离上班没有几天的时间了,今天跟大家分享一下阿粉是如何走上程序员的道路的,阿粉的经历只是一个缩影,相信有很多人跟阿粉一样,更有很多比阿粉优秀的人也在坚持着。

阿粉记得高考结束填志愿的时候,当时志愿填的都是计算机相关的专业,那会什么也不懂,只知道计算机听上去很高大上但是具体干什么的,完全不知道。就这样懵懵懂懂的进了计算机学院并且选修了网络工程专业,刚开始我以为同学都跟我一样是因为对计算机的热爱才进了这个专业,但是慢慢的才发现本班很多人是调剂过来的,都是因为选的其他专业人数满了,最后被调剂到计算机学院的,而且当时我们学校的计算机学院是第一年招生。

但是不管怎样对外还是可以说是计算机科班出身,但是阿粉我接触电脑还是比较晚的,接触编程也是上大学才开始的,出身农村,小时候根本没用过电脑,不像有些朋友从小就接触电脑接触编程。

接触的第一门编程语言毫无疑问《谭浩强的 C 语言》,相信很多人都跟阿粉一样从这本书开始了自己的编程生涯。对于这本书的褒贬我不做评价,有人说不好,有人说好,但是既然能出书并能被纳入大学教材自然有它的道理。

刚接触编程语言的时候,第一感觉就是,这也太神奇好玩了吧!只要在屏幕上输入一些特定的字符,就能实现动画,做出网页,并且可以解决一些问题,感觉太有意思了。都说男人的成长是因为压力,大学头两年是在懵懵懂懂中度过的,有课的时候上课,没课的时候就打游戏睡觉,到处玩,一点生活压力都没有。大三的时候觉得快要实习找工作了,不能再浪费时间,然后就天天在实验室里面看各种技术视频,跟着视频一行一行敲代码,跟着视频里面的老师做了一个小小的静态网站傻傻的开心了好几天,所以说有时候成长就是一瞬间的事情还是很有道理的。

后面实验室老师接项目,让我们帮他做,给我们每个人每个月三千多块钱,那个时候当其他专业的朋友还在跟家里要生活费,各种花钱买实验设备的时候,我们这个专业的同学都已经自食其力挣钱了,既能学东西又能挣点零花钱,不用跟家里要生活费,感觉还是很棒的,特别有成就感。

后来大三暑假的时候找了一家公司进行了两个月的实习,实习的过程中就发现书本上学到的东西跟实际工作中遇到的问题真是南辕北撤,而且主要是在学校里面老师教的很多东西,在工作中已经不用了,那个时候就发现学校跟社会有一点脱轨,虽然说原理都是一样的,但是难免工作的时候动手能力还稍微差了点。

所以以过来人经验告诉那些还没有毕业,快要毕业的朋友,一定要多在网上看看资料,多看看一些招聘网站上的要求,提前多学习一下。

经过两个月的实习过后,公司觉得各方面不错阿粉也觉得机会难得,双方就签了意向书,毕业后可以直接入职。实习后既然工作已经定了,阿粉就没有再找过其他公司,而是一门心思的在提升技能(这个现在想想挺后悔的,还是要去大公司试试~~)。毕业后直接入职,留下来工作了。

就这样阿粉一步一步的走上了程序员的这条路,自从走上这条路,虽然头发日渐稀少,逐渐油腻起来,但是阿粉还是很开心和快乐的,因为热爱,因为梦想,毕竟每个程序员都有一颗让世界变的更美好的心!

互联网行业学无止境,未来的路还有很长,有的人是因为喜爱走上了这条路,有的人是因为谋生走上了这条路,有的人是因为梦想走上了这条路,那么你呢?是因为什么走上了这条路呢?

《孙子兵法》中有一句:“胜者先胜而后求战,败者先战而后求胜”。胜者之所以能常胜是因为在开战之前就已经洞悉了一切,做好了充分的准备,有着百分百必胜的把握,而后才会开战,自然必胜。而败者在开战前没有任何准备,直到战争开始才想着怎样取胜,这样失败的概率必然很大。

总结给我们的知识就是在做任何事情的时候都要做好准备,只有自己准备的充分,才能取得最大的胜利,也就是我们常说的知己知彼,百战不殆。不仅是对待某件事情,对待我们的人生都应该这样,前期做好准备,集聚能量,蓄势待发,在关键时刻必然一飞冲天。

与君共勉!

阅读全文 »

社交软件已经到达瓶颈了吗?马克思携 ClubHouse 告诉你没有!

发表于 2021-02-07 | 分类于 Java

序言

最近一款被马克斯带火的社交软件 ClubHouse 在科技圈被广泛讨论,所以最近在各大技术群和论坛里面往往看到的都是求一个邀请码,如下图所示,不得不说真是一码难求。那这个 ClubHouse 到底是个什么样的 APP,为什么这么火爆呢?

软件介绍

ClubHouse 首先是一个语音社交软件,这个软件的模式是采用手机号邀请制注册,只要注册的手机号被别人邀请到,就同样会有两个邀请名额。通过通讯录找到都注册了的朋友,然后就可以开个聊天室在里面聊天。每一个聊天室里面都会有一个主持人以及会邀请一些嘉宾,他们每个人都可以针对特定的主题进行分享,旁听的听众可以只旁听,也可以举手申请发言,只要被主持人同意就可以跟嘉宾一起交流。

看上去其实很普通的一个软件,为什么突然一下子火起来呢?阿粉主要觉得有这么几点,第一个是赶上了疫情,国外很多人在家没事做,打字聊天太慢,视频聊天太正式,而语音则刚刚好,不管自己是坐着还是躺着都可以。第二个是邀请制,就跟早期的知乎一样,如果一个软件的模式是邀请制,那么这个人群的质量就可以有保证,因为毕竟都是一个圈子的人才会互相邀请。第三个是名人效应做的好,刚开始内测的用户都是知名人士,特别是经过马克思在一次采访上面提到自己会开一个聊天室分享,这对于很多人来说能有一个跟大佬一起在一个虚拟房间聊天的机会是很可贵的。说不定举手发言被同意,还能直接跟大佬提问,这对于自己的成长绝对是“听君一席话胜读十年书”。

下载安装

阿粉经过一个下午的努力,成功的下载和注册体验了一下,总的来说整个体验还是不错的(就是好多外国人的聊天室进去听不懂),整个交互很简单,大大的创建聊天室的按钮,然后主页面就是一个个聊天室,随便点击就可以进去旁听,有的聊天室人多,有的聊天室人少。可以找到自己感兴趣的聊天室直接点击进去旁听,也可以申请发言直接交流。

最上面最左右是搜索功能,可以搜索好友或者聊天室,右边第一个邮件菜单就是邀请好友功能,每个成功被邀请注册的都有两个名额可以邀请别人(有朋友反馈在填写注册信息的时候要多选择几个自己喜欢的领域和关注一些人,不然没有邀请名额)。

上面简单说了一下软件的使用,但是在这之前我们需要先下载安装,目前 ClubHouse 这款软件只能在 iOS 上面运行,也就是只有苹果手机才能玩,另外这个软件在中国大陆的 App Store 是下载不了的,我们需要注册一个国外的 Apple Id 才能下载。所以我们要想体验这个软件,需要做如下几个事情。

  1. 注册国外的 Apple Id;
  2. 下载 ClubHouse 并注册;
  3. 找有邀请资格的朋友邀请(确保邀请的手机号和之前注册的手机号一致);

首先我们在苹果网站注册一个 Apple Id ,主要一个邮箱和手机号,然后地址记得不要选中国大陆,对于要填写的地址,邮编或电话直接在网上搜搜就可以了。

等到注册验证结束后,在手机设置里面媒体与购买项目菜单进行账号切换,切换到国外的账号,然后再到 App Store 里面去搜 ClubHouse 就可以搜索到了,然后认准下面的图标进行下载。

下载完过后通过手机号就可以进行注册,但是这个时候只能提供手机号和验证码,并不能进入软件里面,只能显示如下内容。进行到这里过后剩下的自己能做的就能到处找人邀请了。

总结

最后简单说下,阿粉在第一天使用的时候旁听了几个,感觉还是很有意思的。其中有一个聊天室标题叫做“我是美团骑手,我想跟美团 PM 聊聊” 在这个聊天室里面大家就如何帮忙残障人士点餐或者送外卖进行了讨论,虽然不一定有美团的产品经理能听到,但是感觉大家都想做点什么,还是挺有意思的。

阅读全文 »

阿粉来粉碎一下朋友圈谣言

发表于 2021-02-06 | 分类于 程序生活

这不过年了,有的朋友就和阿粉说,不敢吃辣,阿粉就有点儿懵了,咋了呀,为啥不敢吃辣嘞

阅读全文 »

看完这篇文章,小白都会开启防火墙特定端口了

发表于 2021-02-06 | 分类于 运维

手把手教

阅读全文 »

从面试题,分析面试官到底想问些什么?(面试必须)

发表于 2021-02-03 | 分类于 Java

<–more–>

序章

阿粉最近有些小学弟问阿粉,为什么在面试的过程中,自己感觉回答的还可以,但是面试官很多都是回去让我直接等结果呢?是不是我面试的有什么问题呢?但是我自己感觉我没啥问题呀。于是阿粉就详细询问了一下,原来真的是有问题的。

1.你先做个自我介绍吧

其实说这个很多面试官就是想问一下你最近的过往,以及你之前在你的项目中曾经担任过什么样子的角色,因为如果稍微大一点的公司,自己负责的模块不同,划分的职位也是不太一样的,而且你在自我介绍的时候,最好提一下自己的工作年限,比如说:

1
2
3
4
我叫xxx,毕业于xxx,曾经就职于xxxx公司,在该公司担任了Java开发,
主要负责的哪些项目中的某个模块,(如果对项目特别熟悉,那可以继续深入),
然后简单提一下自己的项目的技术栈,比如说项目中使用的Redis,或者说自己负责项目Redis哪一块内容。

其实自我介绍这个东西,在面试的时候是很容易给自己加点分的,态度诚恳一点,别慌,求职,求职,谋求一份职位,不卑不亢的交流就好了,不用面试整的心惊胆战的,没必要,就和阿粉之前的一个刚毕业的学习,在校招的时候,做个自我介绍磕磕巴巴,声音小的可怜,面试官愣是听不清楚,生生的让他做了3次自我介绍,也不是为难他,其实就是单纯的没有听清楚而已。

2.说说你比较熟悉的项目吧

这个就比较有意思了,给大家一个小小的建议,那就是,自己最熟悉的项目一定要放在最前面,而且时间上可以是近期做的这个,因为自己项目,就算这个项目时间是靠后的,那你可以把它提到前面来,尤其是可以把它放到第一页的位置,或者第二页开头的位置最佳。

面试官问这个问题的主要含义,不是让你说你们这个项目怎么样怎么样,实际上主要是想问你,你在这个项目中,你负责了哪些内容,比如说,我在这个项目中负责了什么内容,然后主要使用了什么样子的技术,比如说。

  • 我们这个项目主要是针对xxx业务的一个项目,项目主要是用于干什么样子的事情。(然后就可以开始换了)

  • 我在我们这个项目中,主要是负责哪一块的内容,比如说,负责数据层,处理前端逻辑整个开发工作,分布式rpc服务搭建,(前提来了,你说的你自己一定要会,不然你会知道什么叫后悔),比如说,爬取淘宝时尚品牌与其他电商网站商品品牌与详情等。(注意反扒机制)

在这时候,就有一个阿粉最想给大家说的,就是你自己可以提前想一下,你说完这个之后,会引发什么样子的问题,就比如上面说的,爬虫,如果你爬淘宝,那么一定会出现淘宝有反扒机制,你们是怎么处理的。

如果你负责RPC服务搭建,那你在搭建的过程中有没有遇到过问题,这个就是比较经典的了,你就算没有遇到过什么问题,你也得说你遇到过,但是别整那种太low的,你说你搭建过程中没有遇到过问题,面试官估计也不太信,你要是说我因为字母写错了找了一上午,那你这分分钟就可能在面试官心中垫底了。

自己熟练的项目,一定得拿捏的死死的,问到什么东西不会的,直接告诉面试官,这块业务我没涉及到,但是如果你对这个还有点了解,你也可以告诉面试官,说之前同事在讨论的时候,我也曾经参与过,聊过一些内容,然后就开始继续你的表演就可以了。千万别不会装会,会就是会,不会的话只是了解一点,那你就直说,你不懂装懂的代价是非常高的。

3.自己在简历上写的技术栈

这个技术栈都是自己写的内容,尤其是你自己在你的技术上写的内容,比如说,精通xxx,熟练使用xxx,来个错误示例给大家看一下。

  • 精通Java,多线程,集合等框架
  • 精通JVM,及其调优等
  • 精通Spring,SpringBoot,

这些就不列举了,这简历写的,全是精通,精通二字何其难,如果面试官觉得你真的精通,然后问了一大堆的问题,你结果一个都接不住,那你不用说,百分之90都凉了,阿粉到现在了,简历上对不敢写对Java精通,不是自卑,是真的没有到精通的地步,比如说对Redis的搭建,倒是很精通,RocketMQ的集群搭建,Hadoop搭建,倒是敢写上去。

技术栈其实可以这么写:

  • 熟悉Java语法,多线程,集合等框架
  • 对JVM原理有所了解,熟悉垃圾回收机制等
  • 熟练使用Spring,SpringMVC,Mybatis,SpringBoot等框架进行开发。
  • 精通RocketMQ,Redis集群搭建,Hadoop集群搭建。了解高并发出现雪崩和穿透等处理方案等。
  • 熟悉Linux系统,及其常用命令
  • 熟练使用MySql,SQLServer等数据库,有SQL语句调优经验

剩下的内容就不再多写了,大家也都知道我为什么这么写,如果你写精通,熟练,那么你一定要对这个技术不能只是停留在会用,而是知道为什么,毕竟“面试造飞机,入职拧螺丝”这话是很多程序员遇到的最恐怖的事情了。

而你的技术栈中的内容,一定要保证你自己会,就算你不会,你也得去让自己对他有所了解,毕竟谁让你自己给自己挖了坑,你自己不填坑,那你这面试就很难了。

4.为什么辞职呢?

这个一般都是技术面完之后,回去去找了HR来进行面试,HR都会问这个问题,但是也不排除有的技术也会关心这个问题,这个问题,别说自己的上一家公司是 SB ,就算它真的是,你也不能 Diss 人家,错误示例:

  • 感觉上一家公司太累(我不能加班,别给我太多活,让我每天摸鱼最好)

  • 上一家公司工资待遇不足(我要求涨工资被拒绝了)

  • 上一家公司领导不咋地(领导是 SB)

这些内容都会让你分分钟凉了,最好别说上一家公司的不好,

正确示例:

  • 感觉之前在之前的公司到了一个瓶颈期,对自己的技术也没什么提升了

  • 如果是异地的话,就说现在想比较稳定了,想回老家来工作了。

  • 我很重视平台的发展,我认为一个人才只有放在合适的平台才能够最大程度的发挥出自己的才干。

这种说法虽然HR心里明白是怎么回事,但是实际上还是不会说的那么透彻的,因为毕竟都了解这个事,辞职百分之80的原因都是干的不爽,工资不高,心理有落差感了。

有什么问题想问我?

这个问题一般会有两种情况出现,一个是技术问,一个是HR问,当然,回答的话肯定也会是分开不一样的。

技术问:你还有什么问题想问我?

这个时候问的,肯定是技术类相关的了,比如说,公司现在是做什么项目的,使用的技术栈是什么,也不用问薪资,因为一般HR会和你沟通的。

如果是HR问:你还有什么问题想问我?

如果公司较大,可以问一下有没有班车,一般午饭早饭怎么吃,福利状况,年假等,如果你觉得和HR小姐姐聊得特别好,可以顺带撩一下小姐姐也是可以的。

阿粉就说到这里了,希望大家在金三银四好时候找到自己心仪的工作。

阅读全文 »

一文带你看懂 Redis BitArray 如何实现高性能的位操作

发表于 2021-02-01 | 分类于 Java

Redis 作为当代互联网行业无可替代的 Key-Value 数据库,在我们日常的工作中占据主要的角色,对于常用的命令相信大家都很熟悉。今天给大家分享一个平时可能用到的少,但是也很重要的一个类型 BitArray。我们先通过简单的命令使用,了解该命令的用法,然后再给大家介绍一下底层的实现原理,帮助大家更好的了解。

简单使用

我们先看下什么是BitArray 位数组。Redis 使用字符串对象来存储位数组,一个 Byte 字节有 8 个 bit 位,通过控制每一个 bit 位为 0 或者 1来表示某个元素对应的值或者状态。通过使用 8 个 bit 位可以对复杂操作节省很多的空间。BitArray 相关的操作命令有 SETBIT,GETBIT,BITCOUNT,BITOP。下面我们依次看下命令的使用,最后再看下实现的原理。

首先我们在本地启动一个 Redis 实例,再启动一个客户端去链接如下图,

通过redis-cli 链接客户端,执行相应的命令,接下来使用一下 BitArray 相关的命令,

通过setbit test 2 1 命令我们创建了一个名为 test 的 bitarray 并将其第二位设置成 1,再使用getbit test 2 获取对应位的值。setbit命令功能是将对应的 key 指定 offset 的位置设置为 1 或 0,getbit 命令是获取指定 offset 位置的值。test 是一个位数组通过上面的命令值变成0000 0010 。

接下来我们再创建一个名为test2的位数组,并且通过多次使用 setbit 命令和 bitcount ,bitcount 命令的作用是用来统计位数组中 1 的个数,通过下面我们看到第一次使用 bitcount test2 命令时结果为 1,当使用了 setbit test2 1 1 命令后再次使用 bitcount 命令我们发现结果已经变成 2 了。其中 test2 的刚开始是0000 0100 后面变成0000 0101。

bitop 命令相信大家都能理解,都是一些与,或,异或,非的运算,就不赘述了,具体使用可以看上图。

原理

前面说到 Redis 是通过字符串对象来实现位数组的,所以字符串对象有的功能,在位数组上面都是有的,在Redis 底层位数组的存储结构也是基于 SDS (简单动态字符串)的,如下:

其中 len 字段表示包含的 buf 数组的个数,buf[i] 表示的是第i个字节数组里面具体的数值,buf[len] 是末尾的分隔符\0 。上图中的buf[0] 是一个字节,其中有 8 个 bit 位,在使用了 setbit 命令后初始值为0000 0000,buf[1] 中就是分隔符\0。

SETBIT

当我们执行setbit key offset value 命令时,我们分两步:

  1. 计算出创建多少个字节数组(offset / 8) + 1;
  2. 判断是否长度不够需要进行扩容;
  3. 计算出 offset 对应的字节位置 byte = offset / 8;
  4. 计算出 offset 对应的 bit 位,bit = (offset mod 8) + 1;
  5. 根据 offset 找到对应的位置将此处的值改成value 并返回旧值;

假设我们执行的命令时setbit test2 3 1,第一步先计算字节个数 (3 / 8) + 1 = 1,计算出来我们只需要一个字节;第二步跟原始 len 进行比较,发现不需要扩容;3. 根据 offset 计算存放的字节 3 / 8 = 0 则,存放的 buf[0] 中;第四部计算 bit,( 3 mod 8) + 1 = 4,表示的是第四个 bit 位。经过一轮 test2 就变成了 0000 1000。

setbit 命令执行的操作都是常数级别的,时间复杂度为 O(1)。

GETBIT

我们知道的setbit 命令是如何实现的,那么getbit 命令也就知道如何计算了,过程是类似的。

  1. 找到对应的字节数组 byte = offset / 8;
  2. 计算出对应的 bit 位bit = (offset mod 8) + 1;

经过上面的计算我们可以知道当执行命令 getbit test2 3 的时候,先算出 3 / 8 = 0 ,找到 buf[0],再使用(3 mod 8) + 1 = 4,找到 bit 位。

看到这里细心的小伙伴就会有疑问,会说不对啊,根据这个计算返回的值应该是 0 啊,因为上面 setbit命令执行完的结果是0000 1000 啊。

能发现这个问题的小伙伴说明很用心在看了,这里就要跟大家说下了,虽然 setbit 命令执行完结果是0000 1000 但是在 buf[0] 中存储的确实反过来的,即为0001 0000。采用的是逆序的方式来保存位数组的。

之所以采用逆序保存位数组是为了减少位数组的移动,提高性能,感兴趣的小伙伴可以自行研究一下。

BITCOUNT 命令

bitcount 命令是用来计算一个位数组中 1 的个数,说起来比较简单,但是实现起来却很有讲究。我们设想一下,统计一个位数组中 1 的个数有多少个,最简单的办法就是遍历,依次累加。但是当我们的位数组很大的时候,整个效率就会变得非常慢,因为遍历是跟长度正相关的,当存放 100MB 的位数组整个遍历需要八亿次。而当达到 500MB 时整个遍历就达到了四十亿次!

在 Redis 中采用的是查表和 variable-precision SWAR 算法,查表是指当位数组长度小于 128 时,直接根据预设的映射表找到对应 1 的个数,直接返回。而variable-precision SWAR 算法相对比较复杂,阿粉也还要再研究研究,今天就先不分享了。

BITOP 命令

bitop 命令相对简单一点,因为 Redis 底层是基于 C 语言实现的,C语言本身就支持相关的逻辑运算。因为本身就是二进制位数组,所以对应的逻辑运算会简单很多就不赘述了,相信大家都能理解。

参考资料

Redis 设计与实现(第二版)

阅读全文 »

都知道堆内存要回收垃圾,那么你也得知道如何在开发中使用对象来减少内存使用

发表于 2021-01-28 | 分类于 JDK

堆内存

我们大家都知道JVM内存是划分了一个是堆内存,一个是非堆内存,而堆内存分为了(年轻代),(老年代)这些,而非堆内存就是一个元空间了(1.8之后变更的,之前是永久代)。

相比较来说,大家肯定都非常的熟悉分代的概念,知道对象首先应该放在哪里,然后移动到哪里,最后执行什么样子的方法来进行垃圾回收。而我们今天要说的却是如何考虑运行程序的机器内存限制下,让我们的对象更小一点就能完成我们的功能。

阅读全文 »

一则道歉声明将某度推上风口浪尖

发表于 2021-01-24 | 分类于 Java

Hello 大家好,我是阿粉,相信最近大家都看到郑爽父亲的那个道歉视频,原本这件事情已经过去了,但是这则道歉视频里面的大大的某品牌 Logo 闪瞎了阿粉的狗眼,话不多说,见下图。

image-20210125231237658

说真的这种负面新闻正常的品牌应该都是退而避之,恨不得立马躲得远远的,像之前的任何娱乐圈的人出了事,各大代言的品牌都立马下架商品,而某度的这种“迎难而上”的做法,真的让人不能理解。

事情发酵的很快,随着道歉视频的出来,很多网友都发现了这个品牌的 Logo ,一时间各种批判的声音都出来了,而且据说这个事情在百度内部的高管群里都被讨论了。随后某度官微也出来道歉了,表示说诚恳的接受大家的批判,而且在这个视频中出现 Logo 不涉及任何经济行为,其中的细节作为局外人阿粉就不了解了,有没有经济行为大家自行脑补就行。

文中提到会诚恳接受大家的批评,对给网友带来的不好感受表示非常抱歉,并强调这个视频访谈不涉及任何经济行为,昨天第一时间在相关账号中下线了该内容。还表示:“孩子的话题背后公众表达的是同理心、同情心,人们希望的是事情得到最好的解决。关切越深期待越高,我们必将在未来的工作中加倍严谨、严肃,充分考虑每一位朋友的感受。“经过处理过后,部分网上的视频都做了打码处理,屏蔽了相关文字信息。

相关的道歉视频大家可以公众号后台回复【道歉】查看

我们都知道这么做是不对的,因为大家都知道品牌效应,一个品牌的 Logo 代表了一个品牌的价值观。为什么那些大牌在代言人出现负面新闻的时候都紧急下线商品,并且解约呢?这是因为如果不这样做就会影响到品牌的形象,而对于一个品牌来说,一旦品牌的形象受到负面影响那将会是重大损失,很有可能一去不复返。虽说当今的互联网是流量的时代,得流量者得天下,但是获取流量也不能通过蹭这种负面新闻的热度来获取,这种行为只会让人更加厌恶。

阅读全文 »

面试官不尊重人,让阿粉怼的拂袖离去,重新换了一位面试官!

发表于 2021-01-20 | 分类于 面试

最近这段时间因为自身的一些原因,阿粉想着年前把自己公司的事情处理好之后,然后准备准备面试,刚刚把简历弄好,然后发布出去之后,就接到了面试的电话,而电话面完之后,现场面试却是一再出现尴尬的情况,事情是这个样子的。

阅读全文 »

从 LeetCode 的题目再看 MySQL Explain

发表于 2021-01-16 | 分类于 Java

Hello 大家好,我是阿粉,作为 Java 工程师,数据库用的最多的肯定是 MySQL,而对于 MySQL 公号前面也发过很多文章,感兴趣的可以去翻翻。今天阿粉主要是想通过 LeetCode 上面的一个题目来再带大家看看 MySQL 的变量使用以及通过 Explain 的解析看看SQL 的执行过程。虽然平时在工作中对于 MySQL 使用的很多,但是相对于 MySQL 的变量使用相对还是较少的,所以阿粉在刚看到的时候还是有点懵的,不过我相信大家肯定不会像阿粉一样,毕竟能关注我们公众号的读者都是优秀的。

阅读全文 »

再来“砍一刀”,拼多多天才黑客因为不做黑客业务疑被开除

发表于 2021-01-14 | 分类于 java

最近“拼多多”这是一直在风口浪尖上了,舆论的旋涡那是一层接着一层,员工猝死,员工跳楼自杀,员工发言开除员工,操纵用户手机删除本地保存的图片等一系列的事件,让“拼多多”一直保持在热搜的位置上。

阅读全文 »

心灵奇旅:对你来说,你的火花是什么?

发表于 2021-01-12 | 分类于 程序生活

阿粉元旦的时候去看了一部电影,心灵奇旅。

阅读全文 »

【非广告】半年时间 90% 的收益就问你慌不慌

发表于 2021-01-09 | 分类于 Java

先说明这篇文章不包含任何广告内容,也不提供任何投资理财建议,股市有风险,投资需谨慎!

都说牛市来了,今年的 A 股的行情确实很不错,从上面的截图中可以看到阿粉的一只基金已经收益 90% 了。90% 是什么概念,估计连股神巴菲特都没有过,所以这几天阿粉慌的一批,除了慌的很之外,另一个就是懊悔的很,当初应该多买点的,只能说人性是贪婪的。

阅读全文 »
1 … 5 6 7 … 32
Java Geek Tech

Java Geek Tech

一群热爱 Java 的技术人

633 日志
116 分类
24 作者
RSS
GitHub 知乎
Links
  • 纯洁的微笑
  • 沉默王二
  • 子悠
  • 江南一点雨
  • 炸鸡可乐
  • 郑璐璐
  • 程序通事
  • 懿
© 2019 - 2022 Java Geek Tech
由 Jekyll 强力驱动
主题 - NexT.Mist