普通视图

发现新文章,点击刷新页面。
昨天以前思想观点

4 月以来墨问便签小程序的发布节奏

作者 MacTalk
2024年12月9日 13:33

四月以来墨问发布的新功能:

置顶笔记折叠、标签云、特别关注、个人主页改版、画廊、播客 shownotes、分享图增强、公众号互通、AI 听读、支持微信表情、AI 语音笔记、长按试试、在小程序里打开墨问笔记的超链接、引用样式、墨问 Pro 会员、发视频号等。

8 月新增引用样式、详情页改版、新手势。
9 月集中精力开发一个大版本,一步步来。
10 月墨问 Pro 版本上线
11 月付费专栏预览、发视频号
12 增加笔记引用功能……

最新发布的功能在前:

如果你不知道墨问有什么功能,建议收藏

2024年12月9日13:33:13

选择从众,还是走小众路线

作者 MacTalk
2024年12月5日 23:50

今天早上我转了一篇微信输入法的笔记,是望月老师写的。微信输入法现在发展得越来越成熟,之所以今天引起大家的注意,是因为它发了一个新的版本,在新的版本里,微信输入法可以输出花式的英文字体,非常有意思。比如这样:

𝑴𝒐𝒘𝒆𝒏 𝒊𝒔 𝒂 𝒗𝒆𝒓𝒚 𝒈𝒐𝒐𝒅 𝑴𝒊𝒏𝒊 𝑷𝒓𝒐𝒈𝒓𝒂𝒎 𝒕𝒐 𝒖𝒔𝒆.

群里有一位墨友说,因为微信输入法不支持双拼的某一种键盘布局,所以他很不习惯,然后就放弃了微信输入法。

对于工具类产品的选择,我一般会选择大众的产品功能。因为这种功能需求越大众化,它就会被厂商优化得越来越好用,而小众的东西会被逐步放弃掉。

拿输入法来说,早期的时候我们使用五笔字型输入法、双拼等等。随着输入法的进化,现在用全拼输入和全键盘打字已经变得越来越普及和方便,也是输入法里最容易上手的。

正因为最容易上手,随着电子设备的普及,越来越多的人会使用这种输入法,厂商就会不断的花精力优化这种输入法,把它做得更加智能、更加好用。五笔或者双拼输入法的优化程度、支持力度就不如全拼输入法好。

聊起这个事,其实就聊到一个选择上,我们应该从众呢,还是选择小众的东西以彰显自己的不同?还是得看场景。

对于效率优先的事情,我建议大家选用大众的产品和功能。如前所述,它们会被优化得越来越好。

一个程序员选择编程语言,那我肯定会去选择 Java 或者是 Go 这样流行的编程语言去学习,同时去观察 Rust 这种新型的语言会不会有大发展,要不要去储备这方面的知识等等。现阶段肯定是 Java 和 Go 更值得投入时间,如果现在你还抱着 PHP 或 Ruby 这样语言,很可能面临的境地是英雄无用武之地。很多大厂已经不用了,它们的语言特性发展得也会越来越慢,就是一个很简单的优胜劣汰的道理。

我是一个摄影师,那我肯定会使用 Adobe 公司的工具,比如 Photoshop、Lightroom 这样的软件。因为被大众所用,它的功能会变得越来越强大。

我们使用这些产品和功能主要是来提升效率,做出更好的作品,所以一定要去选择大众的东西。

那如果目的是玩儿和娱乐,就没必要选择大众的东西了。

大家都去听 MP3、无损音乐,那我听听黑胶也是一个不错的选择。大家都在用手机相机拍数码照片,那我去拍胶片,就能获得不同于大众的体验。

在玩和娱乐方面,大众的东西,我们是躲不开的,如果还有些小众产品相伴,就会获得非常不一样的体验和快乐。

这种小众群体如果有一定的规模,也不会被淘汰,我们可以在里面一直玩。去旅行也是,大众的景点大家都去玩,我们也去那些大众的地方就没什么意思,可以发掘一些相对冷门、有趣的地方去玩、去拍照,也挺好。

五哥在这方面就做得比较好,总能发现一些别人去不了或不愿意去的地方。把自己放置在不一样的生活场景和生活方式种,那我们的生活质量和时间也都不一样了。但五哥去做冲锋衣,肯定不会憋着去搞特别小众的东西,对吧。

这就是效率优先和娱乐优先在选择上的区别。

在选择中做趋势判断也很重要。

我 2008 年就开始使用苹果的 MacBook Pro,其实是我对这个产品有偏爱,同时看到了 iPhone 的发展趋势。随着 iOS 的发展,Mac 应该会被普及起来。

我自己深度使用了 Mac 之后,发现它确实有自己的优势,就坚定了这样的判断和信念。然后我就会去使用和推广这样的产品,同时给自己带来影响力。这是一个对趋势判断的例子。在那时可能是小众的,但是在未来有可能变成流行的东西。

比如 Rust 语言,现在这款编程语言相对还是小众的,但是未来随着它对芯片和高并发上有更好的支持,会不会成为更流行的语言呢?这其实需要判断。

如果你觉得某一个东西在未来可能成为趋势,现在虽然是小众的,但是投入精力去使用和理解它也是值得的。

那究竟是选择大众还是小众呢?场景优先 😄

这篇使用墨问 AI 语音笔记写的,你也来试试,微信搜索墨问即可。

2024年12月5日

Raycast Hoarder: 开源 AI 书签管理插件

2024年11月24日 08:00

🤖 AI 摘要

文章首先介绍了作者最新开发的Raycast Hoarder插件,这是继Raycast Sink后的又一开源作品。Hoarder是一款基于AI的开源书签管理工具,核心功能包括自动摘录网页、AI生成标签与摘要、支持图片识别及多端同步。该工具支持Docker自部署,确保了数据的自主可控。作者开发的配套Raycast插件通过API调用,在桌面端增强了Hoarder的使用体验,提供了包含网址、图片、文本预览的列表视图,支持本地权重与在线双重搜索模式,并允许用户直接在Raycast内进行书签的编辑、删除及快速添加。作者强调,选择Hoarder及自部署方案主要是出于对个人数据隐私保护、规避内容审查以及降低使用成本的考量。目前该插件已开源并上架Raycast官方商店。

上周我开发了一个基于 Sink 的短链管理插件《我开发了一个短链管理插件: Raycast Sink》,上架官方插件商店后,看到已经有几十个用户在使用。

昨天又开发了一个基于 Hoarder 的书签管理插件,有了上次的开发经验,这次从初始化项目到最后提交上架,只花了一天时间。同样开源到了我的 Github 上。

Hoarder 简介

Hoarder 是一个基于 AI 的书签管理工具,可以自动摘录、识别网页,通过 AI 分析内容、生成标签和摘要

有关这个项目的介绍,大家可以直接看官网和官方文档,官方的文档写得很详细。

优点

  • Self-Hosting,支持自部署,数据自主可控
  • 支持 Web、iOS、安卓多端,也有浏览器插件
  • 通过 AI 自动给内容打标签,理解网页和图片内容
  • 自动保存网页和截图
  • Docker 一键部署,建议用境外服务器,抓取和分析更快。
  • 开放 API 支持拓展

Raycast Hoarder

我部署完 Hoarder 之后,也安装了浏览器插件和 iOS 应用,感觉整个产品的完成度很高,加上有 API 支持,所以就顺手开发了一个 Raycast 插件,方便管理和使用 Hoarder。

列表直接查看书签,支持网址、图片、文本三种格式的预览。

增强了搜索功能,支持本地权重搜索和在线搜索。

书签详情页支持查看、编辑、删除、打开链接等操作。

支持列表和标签列表。

由于 Hoarder API有点问题,暂时支持快捷增加文本和链接的书签。

为什么要自部署

在上面的优点中,我特意提到了 Self-Hosting,自部署是我最关注的一个点。

去年我曾经写过一篇文章《脱钩: 我的个人网络安全策略》,由于工作原因,我已经将很多数据都下云或者放到境外服务器。

其实市面上有许多书签管理和笔记管理工具及服务。但是,出于众所周知的原因,在国内的所有内容平台服务都需要进行内容审查。国外也有很多优秀服务,但价格高昂或速度慢。

Hoarder这种完全开源、可以自行部署、数据可控性强的工具对于像我这样有技术背景的人来说是一个好选择。

长沙马拉松:回到 5 小时内

2025年12月4日 03:07

我从 2013 年开始跑步训练,在 2014 年跑了第一个马拉松。2014 年到 2018 年那几年,我每年都跑马拉松,累计已经跑了 7 场全程马拉松。

2019 到 2022 这三年,由于疫情和工作的原因,中断了三年,去年我开始恢复自己的「人生马拉松」计划,跑了第 8 场全马。

今年的第 9 场,我选择了长沙马拉松。虽然我是一个湖南人,但是我却没有去过长沙。我的表弟定居长沙,8 月乔迁新居,刚好趁着这次机会在他家住了几天,也顺便在长沙玩了五天。

行程

现在回湖南高铁很方便,不管是从东莞还是深圳,坐高铁只需要 3 个小时左右到达长沙南站。在高铁上还遇到后座两位大叔也来跑马拉松,下车的时候还聊了一下,约伴一起去领取参赛包。

检录

马拉松在周日举行,我周五提前到,从长沙南站坐地铁一站到国际会展中心。

由于是周五,很多参赛者还没来(大多会在周六报道),人不太多,我领取物资的过程十分顺利,但是周六那天,听说由于人太多,很多人排队两三个小时,被很多跑友吐槽组织得不行。

领取物资的地方。

这次领取到的参赛包。

我的号码牌,这次我长沙马拉松报名错过了报名时间,是通过赞助商渠道弄到的名额,分配我到了 F 区,属于比较后的区。

今年是长沙马拉松第 10 年,参赛包里的物资展开(也有一些我在博览会打卡领取的物资),感觉一般般,广告和廉价的食物比较多。

赛前闲逛

我表弟的房子在梅溪湖,属于长沙的新区,新楼盘很多,环境不错,晚上吃完饭去梅溪湖边逛了逛,湖边的夜景很美,有不少散步的人。比起一线城市的房价,长沙的房价相对要健康很多。

周六闲着没事,直奔国金中心,一出地铁,人潮涌动,真热闹。

长沙市中心五一广场逛了逛,人真多,街边很多餐饮店,长沙不愧是网红城市。

长沙最高楼国金中心,楼下有 Apple Store,楼上也是一个网红打卡地。

坡子街这个派出所也是一个网红打卡点,很多人慕名而来,之前是不允许拍照,后来警察可能觉得拦也拦不住,把牌子改成了「请文明拍照打卡」,但是依旧顶不住游客顶风作案。挺好玩的一个地方。

街头执勤的交警在给一个大妈指路。

我去年减肥之后,已经戒糖很久了。但是都来长沙了,一杯茶颜悦色还是必须的,完成打卡任务。

在五一广场逛了逛,走到了江边,又来到了文和友,也算是长沙的一个餐饮网红,前几年也开到了广州和深圳,最近看新闻听说要倒闭了。

逛得差不多了,穿过坡子街,走回五一广场地铁站,坐车回家休息。

晚上冲完凉,把第二天的装备收纳了一番,放到床头。这次装备跟上次差不多,除了凡士林忘带了,其他东西都齐全。

比赛日

早上 5 点半就起床了,吃了两个面包,换好衣服,坐地铁前往起跑点,今天马拉松参赛者乘坐长沙地铁免费,地铁为了方便跑友,还提前到 5:30 发车。

到达五一广场换乘去贺龙体育中心,密密麻麻基本都是跑马的人。

马拉松 7点30 起跑,我到达才 6点45,还有半个多小时。此时人已经很多了。

由于我在 F 区,发枪时间是 8:00,在这里等的时候让跑友给我拍了张照片,腰上鼓鼓的里面装满了补给用的能量胶。

前面 A 区已经发枪了,我们 EF 两区也慢慢开始往前挪动。

7点 55,慢慢到了起跑线,大家在等等起跑。

发枪咯,从我的位置到通过计时毯,只用了 1 分钟,这也是分区发枪(间隔15、30分钟)的好处。

今天长沙的天气属于多云,虽然不是阳光明媚,但这种温度还挺适合跑步的。

起跑没多久,上桥进入橘子洲。

到达 5Km,第一个补给点。橘子洲里的道路比较窄。

到达毛泽东头像,来都来了,自拍打个卡,不少跑友还专门停下来留影。

跑了没多久,我追上了第三枪 445 的兔子。他们比我们先 15 分钟发枪,我跑马前半程的速度还是比较稳,基本都能保持 10公里/h 的速度。

跑了两个多小时,终于到达半程,对于半马的人来说他们今天的比赛已经结束了,而我们全马的比赛才刚刚开始。

去年跑完珠海马拉松之后,2024 整个一年,我都没有跑过超过 10km,一般就在健身房跑 5km,过了 25 公里之后,我就能感觉到自己的体能下降了,后面开始慢慢走走跑跑结合。

到达 30 公里,除了跑不动,身体倒没有其他什么不适,用时 3 小时 17分钟,这 10 公里比前面慢了 15 分钟,虽然我平时跑步少,但是一直坚持健身,今年的身体素质还是明显强了很多。

终于到达40公里,30 公里到 40 公里这一段,我一直在看自己的配速,计算如果我这次要跑进 5 小时,需要保持多少的速度。一开始 30 到 35 公里还比较紧张,越到后面,发现自己好像能稳定,倒轻松不少。

最后 400 米,终点就在前面,长沙马拉松最后两公里是一个长上坡,很折磨人。

终点就在前面,最后这几百米,加快了速度又跑了起来。

成功达到终点,十分开心,完成了今年的「人生马拉松」。

看了下自己的手表计时 4 小时 56 分,也达成了我的目标(进入 5 小时以内)。

领完奖牌和纪念品,开心再记录几张。

过了一会,比赛确认的成绩发送到了我的手机。跟我手表记录的时间差不多。

长沙马拉松的关门时间是 6 小时 15 分钟,离开之前,还能看到不少跑友在继续努力。

赛后闲逛:湖南博物院

跑完之后就回家了,吃完饭后,洗完澡就睡了一觉,醒来后腿十分酸痛,起立转身都感觉困难。

第二天周一,腿脚好了点,但是也懒得出去,睡到中午起床,在家休息了一天。周二,跟我堂弟去湖南博物院看了看。

湖南博物院是一个很多人推荐的地方,预约参观,人很多。

博物馆展示湖南地区历年来的不少文物。

一个萌萌的鼎。 看到 Yale 的文凭,这个也是现在「雅礼中学」的前身。

湖南博物院最具特色的「长沙马王堆汉墓」相关展览。

马王堆辛追夫人生前用过的木杖(难以想象这是 2000 多年前的文物)。

逛完博物馆,感觉湖南博物馆可以放到我去过的博物馆的前几名,马王堆汉墓这些十分令人惊叹,值得一逛。

逛完博物馆,时间尚早,又去五一广场附近逛了逛,这个黄金楼也是一个网红点。

吃了黑色经典臭豆腐,味道不错。

看到一个辣条博物馆,给老婆拍了张照,感觉下次可以带老婆再来玩一玩。

还看到一个俄罗斯国家馆。

成绩

这次一个人去长沙跑马,还是挺爽的,湖南菜好吃,长沙也好玩。最后再看看成绩,这次全马的成绩终于回到了 5 小时以内,感觉加大跑量的话,明年回到 4 小时 30 分钟还是挺有希望的。

香港徒步:麦理浩径第二段

2025年12月4日 03:07

香港的徒步路线非常知名。只需搜索「香港、户外、徒步」,就可以找到许多路线。

虽然我家在深圳,并且也去过香港很多次,但之前并没有专门去香港徒步过。过去曾和朋友计划徒步几次,但要么是天气太热,要么是工作太忙,由于各种原因都没有实现。

这个国庆假期,我和弟弟轻装上阵,走了一趟麦理浩径第二段。

行程

时间 用时(h) 地点 花费 备注
9:50 0 深圳湾口岸 $60/人 过海关,永东巴士到钻石山
10:50 1 钻石山 $65/人 4人拼车,打的到万宜水库东坝,共$260
12:00 2 万宜水库东坝 0 到达徒步起点麦理浩径二段起点
13:15 3.5 浪茄湾 0
14:30 4.5 西湾山顶 0
16:0 6 西湾 0
16:30 6.5 咸田湾 $160/人 坐船回西贡码头
17:30 8.5 西贡码头
19:00 8.5 钻石山 $60/人 永东巴士回深圳湾口岸
20:00 10 深圳湾口岸

从深圳出发去麦理浩径徒步,无论你选择哪个口岸过关,都有详尽的线路经验可供参考。你可以直接在小红书上搜索相关信息,我就不再赘述了。

在这里推荐下香港当地的徒步路上,从照片到地图,极其详细:

我唯一的建议是:如果条件允许,请优先选择打车。拼车所需的交通成本并不高昂,并且能大幅节省时间

徒步

麦理浩径二段的起点在万宜水库东坝。今天的天气不错,气温 28 度,适合徒步。

的士可以直达这里,省时省力。

这片就是万宜水库,是香港储水量最大的水库。我还是第一次见到靠着海边的水库,也算是一大特色吧。

东坝有歇息的凉亭,也有洗手间。不少人在这里修整。

著名的破边洲,印在港币 500 元纸币上的景点,也是著名的网红打卡点,不少人会专门来这里拍照。沿着堤坝走过去,但是爬上去需要一定的功夫。

绕过头几座山头,看到第一个沙滩浪茄湾。

麦理浩径的指引牌,我们今天的起点也是一二段交界的位置。

从东坝出发约一个多小时,到达浪茄湾,这里有片小沙滩,岸边也有绿荫,不少人在这里歇息。这个枯木,也是一个网红打卡点,不少人在这里摆拍。

在浪茄湾没有停留多久,我们就继续出发了,正午十分,买了一瓶可乐,花了 $30 港币。

麦理浩径一路标记齐全,不用担心迷路。

我们是正午出发,温度虽然不高,但是一路爬坡,我和我弟只穿了普通的休闲衣物,没有带登山杖,还是挺费功夫。

今天这次徒步我也没有计划走完,就穿了普通的装备。背了一个小鹰的登山包,带了4瓶水。帽子、面罩、袖套也是必备,可惜忘了拿墨镜。

从西湾山顶看到的三湾。

今天徒步的人不少,大多都是内地过来的游客。

下午4点,已经徒步约四个小时,到达西湾,这里也是麦理浩径二段的中间点。有个小村庄,也有商店,不少人会在这里坐船或者坐车结束行程。

我和我弟看时间尚早,准备继续走一段到后面的咸田湾。

下面就是我们今天的目的地。

海边的礁石。

过了西湾之后,徒步的人就少了很多了。不同于登西湾山颇费体力,这段路就比较平坦,轻微起伏。远处的大洲和尖洲,香港本地称作史努比岛。。

到达咸田湾,这里也有一些民居,今天也很累了,就在这里乘船回西贡码头,结束了今天的徒步行程。

由于没有码头,需要涉水登船,船票 $160 港币/人。 快艇速度很快,今天风浪不大,沿着海岸线一路向西,也能看到不少沿岸风光。 恰逢傍晚,一天登山劳累,吹着海风,看着夕阳,也算是不错的体验。

轨迹

从早上9点30从家出发,到晚上8点半回家,实际徒步约 4 个半小时,坐船 1 个小时。

麦理浩径的确是不错的徒步线路,准备下个月同老婆再来走一趟。

2024 桌面升级: BenQ RD280U - 为程序员打造的显示器

2025年12月4日 03:07

对于程序员这个群体来说,使用最多的设备,除了键盘,就是显示器了;前者是用来输出和创作,后者则是每天长时间的观看和阅读。

不管是在 V2EX 还是 B 站,在程序员这个群体的外设话题中,问得最多的话题就是「用什么键盘」或者「用什么显示器」。

我是一个程序员,回想我拥有的数码和外设产品,除了一把 2017 年老婆送的 HHKB 键盘,基本没有什么产品号称「为程序员打造」的外设。

对于键盘,经过这几年国内外各种品牌和配件厂商极其夸张的内卷和竞争,从成品到客制化键盘,不管是阵列、轴体还是键帽,每个程序员都可以找到合适自己的键盘。我自己目前主力使用的键盘分别是一把经过 YDKB 无线套件改装的 HHKB Pro 2,另外一把是同样 HHKB 配列 60键的客制化键盘 Qwerty 60

这两把键盘,陪我走过了职场的大多时光,写下了无数代码,也敲下了许多文字。

对于显示器,我现在家中有三台显示器,构成了我的办公桌和工作台。上面这张图,也是过去这几年我的办公桌的变迁。两台 LG,一台 Dell,都是 27英寸 4K 显示器。

对于互联网行业用户来说,如果你的职位是设计师,还有专门的「设计显示器」,强调色域和色准,之前在公司曾经负责过给设计师采购显示器,也稍微了解了一下这个领域。

但是对于程序员来说,可做的选择基本只是在 1920x10802560x14403840x2160 这三个分辨率中,选择一款合适价格的型号罢了。

直到上个月,我第一次知道这台号称「专门为程序员打造的显示器」,截至今天,也已经使用了将近一个月的时间。

开箱

这还是我第一次使用 BenQ 的显示器,纸盒收纳,外观上印着 Ultimate Coding Productivity 「终极编码效率」的字样。

正面的设计,一台笔记本,一个横向正屏,一个竖向副屏,恰好也是我现在自己桌面的组合。

从纸盒下方卡扣打开包装,显示器的支架和底座放在外层。纸盒内部也印有如何打开包装和指引。

见到显示器的第一眼,这是一台28英寸的显示器,加上走的专业路线,非轻薄向,看着分量不轻。

随机附送了一根电源线,一根 HDMI 线,一根 DP 线,一根 USB Type-C线,还有一根 USB-B 线。

底座十分宽大,常见的银灰色的配色,支架支持上下移动和旋转。

支架背后有一个皮质的线材收纳扣,可以将电源和数据线材穿过去。

外观

在放到办公桌之前,先来张背影照片。背后采用纹理凹凸设计,看着比较有质感,但是有预期的是,后续需要注意灰尘和清洁。

BenQ RD280U 背后有一个亮点,圆环其实是一圈可调节的背光灯,被称作Moonhalo

背面的接口,有一个 DP 接口,一个 HDMI 接口,两个全功能 Type-C,一个 USB-B。其中 Type-C 支持 90W 充电,大多情况下满足我的 MBP 的充电需求,我的 MBP 是 M2 Max,但是从来没触发过极限功率,之前 LG 和 Dell 的显示器也都是 90W 充电。

显示器背后的防盗锁孔,又称肯辛顿 Kensington 锁孔,在办公向的笔记本电脑中也常见(这个我也是查 ChatGPT 才知道的知识)。看得出 RD280U 也是有着商务办公的定位。

尺寸

搬到电视柜上,与我家 75 英寸和 16 英寸的 MacBook Pro 放到一起,比比大小。 BenQ RD280U 的3:2的显示比例还是一眼就能看出差异。

搬来我的 Dell U2720QM,我特意把屏幕的实际显示部分从底部对齐,可以看出两者物理上的差距。

设备 尺寸 分辨率 显示比例
BenQ RD280U 28.2 英寸 3840 × 2560 3:2
Dell U2720QM 27 英寸 3840 x 2160 16:9

通过计算可得出,理论上 RD280U 3840 × 2560 = 9,830,400 比传统的 27英寸 4K 显示器3840 x 2160 =8,294,400 会多显示 18.5% 的内容

BenQ RD280U 表明采用了雾面屏。在客厅拍摄这张时候,正是正午,阳台的阳光很强,拍这张照片的时候,发现两个显示器对于反光的处理十分明显。

如果你的办公室的工位敲好靠近窗户,一定能感受到这种抗反光的好处。会想到之前在公司上班的时候,由于我的工位靠近窗户,每天早上到工位的第一件事情就是拉下窗帘,要不白天压根看不清屏幕。

使用和编程体验

接下来把 RD280U 搬到办公桌,又费了不少功夫,为了整理桌面的线材,还专门买了几个小配件改装办公桌。

下面再分享一下使用过程中的一些记录。

BenQ RD280U 支持 VESA 支架,但是我暂时还是先使用自带的支架。

支架除了支持上下110mm的纵向调节,也支持前后正5度到负20度的调节,我的桌子不是电动升降桌,对于我来说垂直就可以了。

还是第一次用上3840x2560这个分辨率, macOS 上我缩放到2560x1707,与我之前使用 27 英寸显示器对应2560x1440的分辨率统一。

如果使用 Type-C 或者 DP 线连接,可以达到 4K 60帧的刷新率,但是如果使用 HDMI 线,则只有 4K 50帧,询问客服说是 HDMI 标准的限制。

使用 Type-C 连接 Macbook Pro,提示是通过 USB3.2 连接,我的 MBP 有三个雷电,分别接上两个显示器刚好。

作为一个编程显示器,除了规格上的特殊,软件上也做了许多的特殊适配,打开 OSD 调节面板,我第一次见到这么多选项的显示器。

显示器正面的 Logo 下方,是一块触摸键,支持自定义触摸切换到指定的模式。

RD280U 这台显示器对于编程场景时有做场景优化,尤其是对于暗夜模式亮色字体时这种反差较大的场景,会通过增加背景对代码的对比度,增强代码的显示效果,我在白天室内拍了一张照片,正面和侧边的视角都有,可以看出不同的显示器,同样 VSCode 显示的内容,RD280U 的更黑点,字体反差效果也更明显。

夜间效果

不同的程序员有自己不同的配色偏好,有些人喜欢亮色主题,有些人喜欢暗夜主题,使用 IDE 终端,人眼在白天和晚上,对于亮色和暗色模式的显示感知差异很大。RD280U 允许切换不同的主题模式。

同时 RD280U 有一个 EyeCare 猫头鹰模式,通过固件调整亮度,提供调整到极低的亮度与色彩平衡方案。

开启猫头鹰模式,显示器面板上会亮起一盏小小的猫头鹰灯。

接下来也是 RD280U 的亮点 MoonHalo,支持 270° 和 360° 的照明,也支持 7 档亮度和色温调节,从暗到亮,从暖到冷。

照片就是我的书房,关掉了所有的灯,使用固定的光圈和 ISO 拍摄的照片。 之前我的另外一台显示器使用的是屏幕挂灯的解决方案,相比一下 MoonHalo 这种在背后的补光方案,更加适合程序员这种不需要太多光线的工作环境。

深夜时分,如果还想敲代码,开启 Moonhalo,只让自己的工作台有一点点的灯光,就可以通过增加环境光,缓和屏幕直射光对眼睛的刺激,在营造沉浸的工作氛围的同时,缓解眼睛的疲劳。

桌面控制

除了支持通过物理或者触摸按键调节显示器。BenQ RD280U 还可以通过 BenQ 的 Display Pilot 2 软件控制显示器。

OSD 上所有的功能开关,模式,均可以通过软件控制,也可以通过软件自定义配置文件,一键切换。

对于开发者来说就十分方便了,可以直接通过 UI 切换模式、调整屏幕亮度、调节灯光等等。

更多

最后为了更直观展示 RD280U 的特殊,我又把办公桌重新布置了一下,与 Dell 组了个双屏。一个28寸、一个27寸的屏幕并排放到桌面上,一下子觉得眼睛看不下来。一般而言会组成有弧度的排列,这里我并排展示,是为了更好展示两者的差异,非最佳的工作角度。

两台显示器采用左右布局,分辨率分别缩放到 2560x17072560x1440 的分辨率,这样让两台显示器在视觉上保持一致。

两个屏幕同时打卡哔哩哔哩首页,可以看到 RD280U 展现的内容多了整整一行。

两个屏幕同时左右分屏,打开 V2EX 和 Chiphell,也可以看到 RD280U 展现的内容更多,3:2在这种日常资讯浏览的情况下,确实有优势。

再来到一个程序员常见的开发常见,一遍开着浏览器预览开发页面,另外一边开着 VSCode,也可以看到 RD280U 展现的内容更多。

但是屏幕大是有大的好处,也会有一个尴尬,如果你用 RD280U 想看看视频,尤其实在白天全屏看视频,你就会发现上下的黑边就特别明显了。

还有一点就是 Xbox 和 AppleTV 均无法使用正确比例的分辨率,如果你外接 Xbox 或者 ATV,会尴尬发现显示的内容都变形了,看来对于这种特殊比例的显示器,还是要看看设备的支持情况。

总结

这款显示器是一款定位十分明确的产品,专门为程序员打造,而程序员的需求相对就简单多了: 看得多而清楚,看得久而不累

市面上之前并没有这么专属职业属性消费级显示器产品,相比传统的 LG 和 Dell 同级别显示器,也并没有贵多少。

最近半年,我开始居家办公,每天绝大多数时间都是在自己的书房 Coding,不出意外的话,我会继续使用 BenQ RD280U,作为我的主力显示器。

显示器太多,眼睛有点不够用了。现在倒考虑重新部署下办公桌,考虑 DIY 一张超长的光轴木桌,再重新布置下工作台。

大家如果对于这块有什么建议,也可以给点建议聊聊。

我订阅了 7 年的服务:常用的 Setapp 软件推荐

2025年12月4日 03:07

今天来分享一个我已经订阅了 7 年的 Mac 软件服务: Setapp,上一次介绍 Setapp 已经是 2018 年的 1 月。

过去几年,我几乎所有的软件和服务都已经正版化。在过去几年的云账单中,也有介绍我订阅的一些软件和服务,大家如果感兴趣,也可以看看我过去的云账单:

Setapp 是什么?

从 2012 年开始,我就使用 Mac 作为自己的主力机器,平时工作、娱乐大多也是在 macOS。

Mac 平台上有不少优秀的应用和服务,有些是买断制,有些是订阅制。Setapp 是一个包括了 240 多款应用的订阅服务,只要你订阅了 Setapp ,就可以使用其中的应用,不用再额外购买了。

这是当前 Setapp 中评价最高的一些应用,还是有许多熟悉的面孔。范围涉及效率、开发、设计、工具等等,基本覆盖了大多数人的需求。2020 年后,Setapp 也开始支持 iOS 应用了,如果在 iOS 上也有对应的 App,可以 1 Mac + 1 iOS 同时享受订阅服务。

套餐和价格

目前 Setapp 提供了个人、团队和家庭三种套餐方案,除此之外,也提供了教育优惠,具体的大家可以自己去官网比较。

目前 Setapp 针对新用户,提供免费试用,目前官方默认提供的是 7 天的免费试用,如果使用下面我的邀请链接,则可以获得 30 天的免费试用。

首先说结论,如果想要最划算的套餐,建议找人拼团,一起使用家庭套餐,$19.99 美元 4 个人,年付是$215.88,算下来每人约¥388/年,如果是叠加教育优惠 50% 折扣,则差不多¥200/年

有关订阅和价格的资料,可以参考下面的几篇文章:

我现在使用的套餐

由于我很早就开始使用 Setapp,目前依旧是老的家庭套餐,$14.99 ,支持 1 主账号 + 5家庭成员共 6 人,年付$161.892,每人约¥190/年,目前偶尔还有一些老车有空位,可以留意下 V2EX 之类的论坛。

优点

  • 省心:常用的一些 App 都有了,不用再额外给钱
  • 订阅中包含的 App 会逐步增加(但是速度不快)

缺点

  • 如果之前已经买断过里面的 App,就不划算了。
  • 随着年限的增加,如果更新的 App 不够,性价比会随着时间推移降低。
  • App 里实用的也就几十个,大多还是用不上。

我常用的 App

软件 类型 原价 推荐指数
Spark Mail 邮件客户端 $7.99/月 ⭐⭐⭐⭐⭐
Paste 剪贴板管理 $20/买断 ⭐⭐⭐⭐⭐
iStat Menus 系统监控 $11.99/年 ⭐⭐⭐⭐⭐
Sip 取色工具 $24.99/年 ⭐⭐⭐⭐⭐
Mosaic 窗口管理 £14.99/买断 ⭐⭐⭐⭐⭐
CleanShot X 截屏工具 $29/买断 ⭐⭐⭐⭐⭐
CleanMyMac X 软件管理 $34.95/买断 ⭐⭐⭐⭐
Timing 时间记录 $9/月 ⭐⭐⭐⭐
TablePlus 数据库管理 $89/买断 ⭐⭐⭐⭐
CloudMounter 云盘管理 $29.99/年 ⭐⭐⭐⭐
Downie 视频下载 $19.99/买断 ⭐⭐⭐
MindNode 思维导图 $24.99/年 ⭐⭐⭐

Spark Mail

Spark Mail 是我日常使用的邮件客户端,支持 iOS、Android、Mac、Windows,也支持多端同步,去年开始集成 AI 功能,几年前调研了一圈邮箱客户端,最终还是选择了 Spark。

Paste

Paste 是一款剪贴管理软件,支持复制、粘贴、同步文本、图像等等,对于日常需要经常复制代码、文字的我来说,极大提高了效率。

iStat Menus

iStat Menus 可以在菜单栏显示 CPU、内存、硬盘、网络等系统信息,有时候需要观察CPU、内存和网络情况,直接在 iStat Menus 上就能看到,就不用进去 Activity Monitor 了。

Sip

Sip 也是 macOS 上经典的取色工具了,对于程序员、设计师来说都十分实用,可以快速取色,支持多种格式,也能把常用色保存到自己的调色板中。

Mosaic

Mosaic 是一款窗口管理软件,支持快捷键调整窗口大小、位置,我经常用来快速切换桌面的窗口布局。设置了上下、左右几个常用布局的快捷键。

CleanShot X

CleanShot X 也是我高频使用的截屏软件,经常使用 CleanShot X 来进行窗口截图,里面的标注、马赛克、自定义窗口背景很实用,拿来进行初步的图片处理后,就可以直接发布了。

CleanMyMac X

CleanMyMac X 也是 macOS 上经典的软件管理工具,平时用来清理垃圾文件、卸载软件、优化系统,也是我常用的软件之一。

Timing

Timing 是 macOS 上一款时间追踪管理软件,可以自动记录你在电脑上工作的时间,使用的软件和频率和时长。可以用于评估自己真实的工作效率。

TablePlus

TablePlus 是一款数据库管理工具,支持 MySQL、Redis、MongoDB 等主流数据库,TablePlus 算是比较轻量和好用的,满足了我的日常开发需求。

CloudMounter

CloudMounter 是一款云盘管理工具,支持 Google Drive、Dropbox、OneDrive、Amazon S3、Cloudflare R2 等,也支持 WebDAV。

CloudMounter 可以将云盘以目录的形式挂载到本地,方便直接在 Finder 中操作和管理文件,使用起来比较方便。

Downie

Downie 是一款视频下载工具,支持 YouTube、Twitter、哔哩哔哩等主流视频网站。有时候看到视频想保存下来,Downie 就是一个不错的选择。

MindNode

MindNode 是一款思维导图软件,有时候我写文章和拍视频之前,会先用 MindNode 来整理思路。

其他应用

除了上面的常用应用,我也在使用 Setapp 提供的一些其他应用,鉴于篇幅就不再单独介绍:

  • Bartender: 老牌菜单栏管理工具,说句题外话,我觉得 macOS 的菜单栏设计真的很烂,逼着第三方软件来解决。
  • Lungo: 防止 Mac 进入睡眠模式
  • Squash: 图片压缩和批量处理工具
  • Proxyman : 代理和抓包工具
  • Gemini: 查找和删除重复文件
  • Archiver: 压缩和解压缩工具
  • Renamer: 文件重命名工具
  • Wifi Explorer: Mac 下的 WiFi 网络扫描和分析工具
  • Disk Drill: 数据和文件恢复工具
  • AIDente Pro: Mac 电源管理和优化工具

总结

我从 2017 年开始使用Setapp,尽管通过家庭套餐降低了月租费用,但这几年累计的花销仍然不小。

近年来,越来越多的 App 转向订阅制。在这种趋势下,即使排除提供一次性购买的 App,Setapp 相比之下还是更划算且省心。

如果你家里有多台 Mac 设备,或者有多个家庭成员都在使用 Mac,还是推荐可以搞一个 Setapp。

我的老婆确诊肺癌,希望能得到你的帮助

2024年12月8日 14:18

为什么要写这篇文章? ​

今天是 2024 年 3 月 19 日,对于我来说,这是一篇严肃的博客。这篇文章,不是募捐,也不会以此话题来贩卖悲情、吸引流量

如同我在标题中所写到的:我的老婆确诊肺癌了

在写这篇文章之前,我也与老婆认真讨论了「是否有必要把家庭和个人的隐私公之于众?」,在经过她的同意后,我还是决定把这件事认真、严谨地记录下来。

我是一个程序员,理性与逻辑,是我向来行为做事追求的准则。写下这篇文章,我有两个目的:

  • 一方面,是收集资讯、经验,用以帮助我的老婆在后续治疗、康复的过程中,尽量少走弯路,避坑的利己诉求;
  • 另一方面,也是希望我的这篇抗癌笔记,能够帮助可能也遇到这类病状的家庭和朋友,能够给你们带来参考,实现利他的价值。

在此,真诚地向所有看到这篇文章的各位朋友求助,如果你有任何有关肺癌的资讯、经验、或者是治疗和康复方案,都希望你能够留下相关的信息。非常感激你的帮助。

这篇文章我也分享到了推特,截至 3 月 21 日下午,已经有 100 多万的浏览量,超过 1 千条评论,在留言中也收获了许多有价值的信息:

更新 Changelog ​

本文会随时间陆续更新,在此栏记录本文的重大更新时间和内容:

  • 2024.04.19:补充中山大学肿瘤防治中心就医时间线,基因检查结果确认。
  • 2024.03.20:补充深圳人民医院「术后病理活体组织检验报告书」。
  • 2024.03.19:文章发布,介绍了老婆的病情背景和治疗关键时间节点。

关键时间节点 ​

时间 时间周期 机构 事项 备注
2023.12.23 D+0 深圳美年体检 肺部 CT 发现肺结节
2024.01.22 D+30 华中科技大学协和深圳医院 肺部 CT 第一个医生:CT 发现肺部 13mmx8mm 磨玻璃,提示有可能是癌前病变,医生建议入院手术
2024.01.24 D+32 香港大学深圳医院 胸外科门诊 第二个医生:医生建议手术
2024.02.29 D+68 深圳市人民医院 胸外科专家门诊 第三个医生:深圳市最好的胸外科医生之一,医生建议入院手术
2024.03.13 D+81 深圳市人民医院 入院 术前系列检查
2024.03.15 D+83 深圳市人民医院 手术 全麻,切除部分肺组织
2024.03.17 D+85 深圳市人民医院 术中病理报告 主治医生术后第一次交流,初步确诊肺微浸润性腺癌
2024.03.18 D+86 深圳市人民医院 癌症分期 右上肺微浸润性腺癌(T1a(mi)N0M0, IA1
2024.03.20 D+88 深圳市人民医院 病理活体组织检验报告书 大病理结果:肺浸润性腺癌中分化,腺泡+乳头状(约50%),可见微乳头结构(约10%),肿瘤大小 1cmX1cmX0.8cm .可见脉管累犯: 胸膜及吻合钉切缘未见癌
2024.03.28 D+96 深圳市人民医院 术后复查 主治医生第二次交流,解释术后其他方案的可行性和必要性(建议不要过度治疗)
2024.04.09 D+108 中山大学肿瘤防治中心(广州) 专家门诊 综合网友建议和评估,全国肿瘤医院排名第三,广州最好的专科医院,专家赵洪云门诊,医生重新安排增强CT、MR、血液、基因检查等项目
2024.04.10 D+109 中山大学肿瘤防治中心 门诊检查 血液相关检查、心电图检查、全身骨显像CT
2024.04.12 D+111 中山大学肿瘤防治中心 门诊检查 病理会诊,提交深圳人民医院病理组织切片等供基因检查
2024.04.13 D+112 中山大学肿瘤防治中心 门诊检查 上腹、胸部增强CT,颅脑磁共振MR查
2024.04.19 D+118 中山大学肿瘤防治中心 分子诊断检查报告 确定变异基因 EGFR: p.E746_S752delinsV

就诊经历简介 ​

我的老婆在 2023 年底体检时,通过 CT 发现肺部有「肺结节」,后续体检机构的工作人员多次电话联系,提醒我的老婆务必去医院复查。

老婆在隔年的 1 月,去南山医院(华中科技大学协和深圳医院)做了进一步检查,也做了 CT,第一个医生,提示我老婆肺结节有可能癌前病变,医生建议入院手术切除。

为了保险起见,我们又预约了香港大学深圳医院的门诊,港大医院的医生也建议入院手术。

经过两家医院的问诊后,我和老婆确定了手术治疗的计划,由于春节假期,中间间隔了一个月,节后我们就立马预约了深圳市人民医院杨林医生的专家门诊,杨医生也是直接给出了手术的建议,并给出了入院通知书。随后,我们在 3 月中旬入院,并在 3 月 15 日顺利进行了手术。

从发现症状,到最后的手术,只用了 80+ 天的时间。虽然时间很快,但是从始至终,我和老婆对于这个病的心理预期,都停留在「肺结节」这个病症的范畴,压根没有想到进一步的癌症

在手术完了后的第三天,在与主治医生交谈时,当我们看到《术中冰冻病理诊断报告单》上写着的「至少肺微浸润性腺癌」这个结果时,以及后续大病理《病理活体组织检验报告书》的最终诊断「肺浸润性腺癌」,我和老婆都非常震惊。

术后医生解释:术中病理报告 ​

在手术后的第一次主治医生交流,由于病理报告上的信息不多,更多的是医生向我们解释内容,从对话之中,我们了解的信息如下:

  • 确诊肺微浸润性腺癌,是肺癌的一种,现在是早期癌症,具体的分期需要等后续的检查报告出来。
  • 微浸润性,说明有可能开始转移(后续升级为浸润性)
  • 这是一个恶性肿瘤
  • 进行手术切除,是早期癌症最好的处理方案。
  • 解释了下为啥切除这么大的原因(手术过程中会把切除下来的组织给家属目视确认)
  • 需要定期复查,半年,一年,五年内都需要定期复查,以观察是否有复发。
  • 如果再次复发,那就一定是晚期(说明已经扩散)
  • 介绍后续治疗方法主要有:靶向药、化疗
  • 说明深圳医保覆盖了大部分靶向药的报销,如果后续需要靶向药,每个月自费几百块即可。
  • 解释基因检测对于癌症治疗的作用,提到医院内有基本基因检测,也可以联系外面基因检测公司做全基因检测,费用从几千到上万不等,但是需要自费,可助于后期筛选靶向药。
  • 如果后面有生育计划,由于需要定期 CT,不建议近两年怀孕。

在交流过程中,我有向医生提问「基于我老婆当前这个年龄和癌症早期的病情,后续康复或者复发,在统计学上的概率大概是多少?」,但是比较疑惑的是,医生并没有回答我的这个问题。

术后第一次与主治医生交谈,得出的病情基本结论是「确诊肺癌,早期,无法保证不复发,后续定期检查即可」,我自己根据医生的解释,大概感觉是虽然这是一个癌症,但是感觉好像不太严重。但医生并没有给出「没啥大问题」之类的主观描述话语,不知道是不是出于医疗严谨用语的需要。

癌症 TNM 分期 ​

手术之后的第三天,由于我老婆的恢复较好,我们就被安排出院了,在出院前拿到的出院疾病证明中,说明了癌症的分期为T1a(mi)N0M0, IA1。TNM 是一种国际通用的癌症分期标准。

具体到我老婆的癌症分期级别的解释,我用 ChatGPT 查了下,解释如下:

  • T1a(mi):T代表肿瘤的大小和局部扩散的情况。在这个分类中,T1a表示肿瘤的最大直径小于或等于1cm,(mi)是微浸润的缩写,意味着肿瘤的侵袭性较低,局部非常有限。T 代表原发肿瘤(Tumor)
  • N0:N代表肿瘤是否有淋巴结转移。N0表示在检查时没有发现淋巴结转移,即肿瘤尚未影响到淋巴系统。"N"代表淋巴结状态(Lymph Nodes)
  • M0:M代表远处转移的情况。M0意味着没有远处转移,即肿瘤没有扩散到身体的其他部位。"M"代表远处转移(Metastasis)
  • IA1:这是根据TNM分期得出的总体分期结果。IA1是非小细胞肺癌(NSCLC)的最早期阶段之一,表示这是一个非常早期的癌症,局限于原发部位,且大小极小,没有淋巴结转移和远处转移。

综上所述,"右上肺微浸润性腺癌(T1a(mi)N0M0, IA1)"的诊断意味着患者的肺癌处于非常早期的阶段,肿瘤小,局限于肺部一个很小的区域,没有发现淋巴结转移和远处转移。这种早期的发现通常预后良好,治疗方法可能包括手术切除肿瘤、可能的放疗和/或化疗,具体治疗方案需要根据患者的整体健康状况和具体情况来定。

病理活体组织检验报告书 ​

3 月 20 日中午,接到主治医生的电话,收到了《病理活体组织检验报告书》,俗称大病理。医生表示比术中病理结果要严重一点。由微浸润改为浸润中分化微乳头,并且有脉管累犯,上面加粗部分,都是恶化和消极因素,但是癌症的分期不变,更新的结果表明,复发的可能性更高

术后院外专家咨询 ​

拿到诊断报告和癌症分期后,我也分别电话咨询了内地某省会城市的医生朋友、深圳某医院的医生同学。其中一人有胸外科 10+ 年的经验,另外一人也有肿瘤相关的研究经验。

在我提供病历、诊断报表、病理诊断报告单之后,两人都给出了类似的意见:

内地医生朋友 ​

  • 情况比较好,属于早期癌症,不太太过担心。
  • 近年来周围也有认识的人得了肺结节和早期肺癌,预后都不错。
  • 现在这个阶段做基因检测没啥太大用,一般都是在筛选靶向药的时候用。

深圳医生同学 ​

  • 从分期结果看,早期五年生存率有 90%
  • 腺癌是肺癌的几种亚型里面相对较好的。
  • 依照医嘱,务必远离烟酒,保持良好的生活习惯。
  • 尤其强调了保持良好心态对预后治疗和康复的重要性,并给出了研究数据解释。

接下来我该做什么? ​

在过去这三个月的问诊、治疗的过程中,我和老婆基本都保持理性、尊重专业知识和专家指导。但是过去这一周,当我面临着「老婆确诊肺癌」这个事实时,对这个领域和相关知识的匮乏,依旧让我觉得无力。因此,如同写这篇文章的目的,我需要尽量地收集、了解更多信息,供我们后续的决策和计划使用。

也许从我上面表述的情况来看,好像能得出我的老婆虽然得了癌症,但是好像还可以,没啥大问题的乐观结论。

但是临床的数据,尤其是 TNM 分期结果对应的 早期五年生存率90%到95% 的数字,依旧是一把悬在我心头的达摩克利斯之剑。

数字放到群体中,只是一个概率,但是这个概率落在个人身上,就是 0 或 1。我不想让老婆承担这 5%到10% 的风险,对于我的内心来说,是难以接受的。

虽然我知道从医学的角度,按照医嘱:定期复查,大概率已经是不错的后续策略。但是我依旧想在这未来的这段时间,尽量提高老婆治愈的百分比。

接下来,我想到的大概确定能做的事情有:

  • 等老婆度过手术恢复期,再去广州肿瘤和胸外科领域更权威的医院,再做一次门诊。
  • 基因检测:虽然几个医生朋友说现在做基因检测没啥大用,但是这个价格尚在接受范围,也欢迎有经验的朋友推荐相关渠道。

除此之外,我并没有太多其他的计划,在此,就希望有经验的朋友,能够给出一些建议和点拨,我发散地想了想:

  • 后续治疗和康复过程中可能会遇到的骗子、骗局?
  • 治疗和康复过程中,可能存在的过度治疗?
  • 从营养学的角度,是否有什么饮食相关的建议?
  • 是否有什么靠谱的网站、论坛、App 推荐?
  • 有哪些可能是我自己无法主动感知的问题?

给大家的建议 ​

花了几个小时,斟酌文字,写完这篇文章。年过 30,对于我和老婆来说,这是人生的一个大事,也是一个挑战。

在这里,我想给各位朋友一些建议:

  • 定期体检:尤其是 30 岁以上的朋友,尽量每年做一次全面体检,现在很多体检不包含 CT ,建议可以加上。
  • 保险:不管是医保、重疾险还是其他商业保险,梳理你已有的保险,如果有不足的地方,尽量补齐。在遇到这种情况的时候,能极大的减轻经济负担,也能给自己更多的选择。
  • 保持健康生活习惯:远离烟酒、运动,健康饮食,这个也算是老生常谈了。

在开始着笔写这篇文章的时候,我一开始想的文章标题是《理性乐观:我的老婆确诊癌症,希望能得到你的帮助 》,相信保持理性和乐观,一定会给老婆带来好的结果。

最后,再次感谢你的阅读,如果你有任何有关肺癌的资讯、经验、或者是治疗和康复方案,都希望你能够留下相关的信息。谢谢。🤝

如何在墨问上赚钱?

作者 MacTalk
2024年3月14日 18:25

我们在墨问小程序这个产品的设置里有一个加入墨问社群的入口,这样就会有源源不断的用户进入我们的社群体系,并带来一些新问题和新的思考。

今天有用户就问我墨问和某某笔记软件有什么区别,一开始我挺纳闷的,就分享了一个付费笔记到群里,说,小程序卡片,分享,评论,付费,合集,包括微信群里的展示样式,这就没一点一样的啊。后来发现用户刚刚接触这个软件,以为墨问只有记录功能呢。

墨问早期一直侧重输入端,首页就是笔记列表,方便用户写东西。后续会更重视消费端,给大家整点好内容,这样一上来用户就有吃的,感观就会不一样。

还有用户问,在墨问是不是只能通过做付费专栏赚钱呢?具体怎么玩呢?

关于这个问题,值得我好好说说。

在墨问上获取收入,最直接的办法就是创作付费专栏。比如我们自己就这么干的,卖桃者说迁移到墨问上之后,新增了 1500 个订阅,还有墨问读书会,老池 36 计,大师的工程师专栏等等,都是收入来源。玉伯、冯老师、五哥、八老师、二爷、建德等一大批创作者,都是这么创作和获得收入的。

创作有乐趣,又能有钱赚,何乐而不为?显然事情并没有这么简单。

举个例子,玉伯是前阿里 P10,在互联网领域成名甚早,还是语雀创始人,有用户基础,信用值也高,所以玉伯一篇免费的文章都没写,直接开了两个付费专栏,贵的 299,订阅用户近 700 人。

李建德,有才华,文字能力,职场经验,音乐和英语能力都是一流的,但只有他身边的人知道他牛逼,我以前根本不认识,他自己也没什么受众用户。他的做法是很早就开始在墨问创作,写东西做音乐做播客,几个月下来吸引了很多墨问用户,也有了自己的私域群,等墨问上线付费订阅功能的时候,建德做了职场、音乐和音乐相关的专辑,非常受欢迎。我全部订阅了,但我在做墨问之前,根本不知道建德这个人。
另外一个作者五哥更有意思,他在微博有一百万粉丝,知名摄影师,写作能力很强,他来墨问的时候已经可以直接开付费专栏然后去微博推广了,但五哥并没有,而是一个多月以狂风暴雨般的速度,在墨问写了一百篇文章,挂起了五哥旋风,直接在墨问信用值拉满,然后做了三个付费专栏,质量超高。定阅者众。

所以,想通过创作虚拟内容(文字音频图片等)获得收入,

第一、建立自己的独特价值,你能提供大部分人提供不了的东西。这个是根本。
第二、如果你已经在其他公域有读者了,直接开个付费专栏也是可以的,因为你的信用体系和用户都有,缺的是个好工具。
第三、你很牛,但现在还是个素人,没人知道你,那就走建德的路子,通过创作输出自己的价值,建立信用体系,找到自己的受众,然后找个机会做付费内容产品,就水到渠成了。
我看到不少用户,完全不知道干嘛的,直接墨问来开个付费专栏,作品简介写的一塌糊涂,就像你在一个无人区开了个店铺,店铺干嘛的,不知所云,怎么会有顾客呢?

说完付费专栏,还有其他模式吗?

有的,这是墨问自己生长出来的能力,也就是墨问的工具属性。

比如五哥发了一篇介绍酱牛肉的文章:

介绍完之后,插入一个快团团小程序的购买链接,用户喜欢和信任五哥,就可以直接购买,我看卖了 60 多单。

另外,墨问还是很好的私域工具,我看有个用户叫墨斗堂书法,就用墨问小程序交付书法作业点评。还有用户用来进行思考力训练的,推荐餐馆的(近期我们要增加一个地址功能),售卖自己绘画作品的,英语打卡的,即时语音分享训练的,交付摄影作品的,等等。墨问可以成为你商业模式里的一个工具利器,成为你赚钱的一部分。

还有些行为,和商业无关,比如写作和互动,家庭相册,语音记录,知识管理等等,这些就是针对自我的能力提升,或者满足某些用户的个人使用价值。

记录即创作,创作即分享,分享即传播。这就是墨问的理念,不要把创作想的多玄乎,你想怎么用,就怎么用,工具嘛,本来就是为你服务的,就看你自己的创意啦。

2024年03月14日 18:24:55

我如何在 7 年内胖了 40 斤,又用 90 天减掉 20 斤

2024年9月17日 23:05

过去这几年,基本上每个老同学或者老朋友,见到我的第一句话就是「罗磊你怎么这么胖了」。

2022 年中旬,我的体重飙升到 191 斤,2022 年 6 月开始,我知道自己不能再如此放纵自己了,决定正儿八经开始减肥,经过 90 天的坚持,最终减肥 20 斤,截止 2022 年底,我的体重稳定在 164-165 斤。

关注我有些年头的朋友们,应该知道我曾经是一个马拉松跑者,最好的全程马拉松成绩是 4 小时 28 分,10 公里公跑则是 47 分钟。

上面三张照片分别是我 2014、2015、2016 三年的照片,从照片上可以看出身材还是比较标准健康的。2013 年至 2015 年,是我身体状态最好的时期,鉴于坚持跑步和去健身房,那段时间我的体重一直稳定在 70-75 kg左右。

可是,随着 2015 年认识我老婆、2017 年结婚后(当然,身材和健康管理与爱情和婚姻并没有必然的联系),我的身材逐渐失控。

我从2015年开始使用智能秤来记录体重,为了方便大家直观对比,我分别截图了三个阶段的身体数据变化。

  • 2015年6月-2022年12月: 7 年时间的体重整体变化。
  • 失控期(2016年8月-2022年): 从2016年8月开始,我的体重逐渐增加,2022年2月达到最高点 96 kg/ 191.8 斤,在这7年时间里,我的体重累计增加了将近20 kg / 40斤。
  • 减肥期(2022年6月到2022年9月): 经过90天的减肥,成功回到 85 kg / 165 斤。

这是智能秤 App 里的具体数据(数据不太精确,但是也足够普通人日常参考了)。2015 年的整体身体指标还是「健康」,2022 年 2 月时,各项指标都已经是「肥胖」了,减肥后的 2022 年 9 月,大部分指标则回到了「偏胖」。

这几年的体检报告中,也开始体现一些跟肥胖相关的健康问题,下面是我从 2021 和 2022 两年的体检报告中摘录的一些内容:

2021年11月体检报告 ​

  • 体重指数增⾼
  • ⽢油三酯增⾼
  • 中度脂肪肝(因为这个我老婆、我妈都骂了我一顿)

2022年11月体检报告 ​

  • 体重指数增⾼
  • 总胆固醇增⾼
  • 轻度脂肪肝(相比2021年有改善)

减肥前后对比 ​

上图就是我 190 斤和 167 斤的对比,BMI 指数分别是 30 和 26,体型分别是「肥胖」和「偏胖」。可以看出去掉20多斤肉后,视觉上整个人的体态好了很多。

为什么要减肥? ​

在回答「为什么要减肥?」这个问题前,我倒更想说说「我为什么胖了这么多?」。

有句话叫「结婚会让人变胖」,所谓「幸福肥」,也经常可以在小红书、抖音看到老公结婚前后的对比。对于很多男性来说,结婚之后,往往也伴随着事业的发展,工作和家庭的种种压力,往往也意味着锻炼的减少,一些不健康的生活习惯。

对于我而言,我反思了一下自己过去这7年体重上升的几个原因,稍微总结下:

  • 锻炼不足: 2016 年之前我还坚持每周跑步和去健身房撸铁,2017 年后运动量急剧减少。
  • 不健康饮食:2015-2018 三年,我在上海上班,每天早上基本是吃麦当劳、肯德基、早餐摊,午餐晚餐快餐或者外卖居多。
  • 焦虑

对于我而言,2018 年后辞职创业期间,从事自媒体相关工作,我曾经有很长一段时间处于挺不好的状态,最近思考一些问题,觉得坦诚面对自己的过去,对于自己也是一种改变和成长。在 2020 年的有段时间,疫情封控加上工作不顺,有一段时间,我整天呆在家中,日夜颠倒,晚上玩游戏,白天睡觉,饿了就点外卖。我的体重在 2018 到 2020 年飙升,也是跟我那段时间的焦虑有关。

2021 年,我回归职场,2021 到 2022 年这两年,相比之前在上海的工作,面临的压力更大,加班时间也更多,大多时间我午餐和晚餐都在公司楼下解决,也是在这一年,我的体重从刚入职时的 170 斤,上升到 190 斤的高点。

2022 年中的时候,我开始更多重新思考工作、生活、学习、成长,也是在这个时间点,我决定重新把握自己,开始减肥。

对于我自己而言,减肥并不是不撞南墙不回头的被迫行为,我有挺强的自我驱动力,我知道自己曾经做到过(我既然过去能跑马拉松,为啥我现在就不能减肥呢?),刚好就趁着调整自己个人状态的这个时间,重新把握自己的身体和健康。

减肥分析 ​

由于我过去曾经有长跑、骑行、健身房器材、游泳的运动史,所以对于运动和锻炼方面,我倒没有太多需要重新探索的地方。

所谓「管住嘴,迈开腿」,迈开腿这部分对于我而言,更多只是「重新抬起腿」,更加重要的是「管住嘴」

有关这个减肥减脂方法,不管是在哔哩哔哩还是知乎,都有很多还算科学和靠谱的介绍,在这里我就不再重复,推荐两个健身UP主供大家参考:

因减肥这块向来是商业植入重灾区,我个人建议如果想了解减肥健身知识,还是注意鉴别内容。

减肥饮食 ​

很多人减肥有个误区就是「少吃」,或者吃「水果」不吃正餐,这次对于我自己而言,我在饮食上的改变总结下来就是一句话:

  • 改变饮食结构,减少碳水、糖摄入量

减肥不仅仅是物理上的控制饮食量,也是心理上的与对抗人类天性中的「向往甜食」。这次减肥过程中我压根不痛苦,因为我根本没怎么少吃。

  • 早餐:鸡蛋、煎牛排、西兰花、牛奶
  • 午餐:回家吃妈给我的饭,没有啥特别注意的,以肉类、蔬菜为主,只搭配 1/4-1/3 碗米饭
  • 晚餐:回家吃老婆做的饭,在开始减肥的 1-2 个月,只吃牛肉、鱼、排骨、豆芽、西兰花等,不吃米饭。
  • 其他:完全戒掉了含糖的饮料,也不怎么吃甜食。

因为早中晚三餐都还算正常,家人做饭也保证了饮食丰富度,头两个月的减肥阶段,在吃这方面倒没有太多的痛苦。

减肥运动 ​

由于工作时间的关系,我现在都没有空去健身房了。所以也改变自己的减肥运动,主要由「HIIT 高强度间歇性训练」搭配骑行。

我一般在早晨 6 点 45 分起来,卧室电动窗帘自动打开,让阳光来照醒自己。

运动时间一般是在早晨 7 点到 8 点,之所以选择早上运动是因为这个时间基本属于完全可控。

早上运动完后,再去洗个冷水澡(6-10月夏天),老婆会在我运动的时间,给我准备好早餐,洗完澡后,跟老婆一起吃早餐,换好衣服就开车去公司上班了。坚持了两个月后,我老婆的生活也更健康了,这样也算得上是互帮互助、一起进步吧。

我做的 HIIT 运动是我 2013 年就接触的 Insanity 系列,很经典的一套适合上班族的高强度间歇性训练。Insanity 有两套,我头一个月做的 Insanity,第二个月开始则开始做增加了强度的 Insanity MAX30

建议家里准备一个瑜伽垫配合一些动作。一般早上的这套 HIIT 会消耗300卡左右。

除了室内 HIIT,我后面也开始在一周中穿插一些户外骑行。我现在住的地方靠近松山湖,有时候早上我会环绕松山湖骑行一圈,总里程 15-20 公里,一圈 1 个多小时,回家冲个凉再去上班。

外出骑行属于户外运动,一方面能呼吸到新鲜的空气,另外也能提高自己的精神。

未来计划 ​

2022 年最后一周,我得了新冠,康复后不太敢恢复锻炼,1 月底过年,导致我过完年后,体重又上升到 169 斤。

现在是 2023 年 2 月 1 日,我返工上班的第一天,刚好也是趁着这个日子,继续我的减肥训练,新的目标是 152 斤,当然,第一阶段目标是回到 15x 开头的体重。

减肥心得 ​

这次减肥,我在头 2 个月,基本保持了每个星期减 2-3 斤的节奏(从我的体重曲线也能看到),大概到了第 3 个月的时候,体重下降的速度开始下降(算是进入到瓶颈期)。对于我个人感受而言还算轻松。

减肥 20 斤,我也养成了早睡早起、晚上学习、整点入睡的规律生活。整个人的生活、工作状态也变得越来越好了,算是正向反馈。

写完这篇文章后,我又想了一下,其实有些条件也是挺难得的。在「吃」这一部分,我还是得尤其感谢我的老婆和老妈。

为了我的减肥,我老婆也改变了她睡懒觉的习惯,早上给我准备早餐;晚上也会给我准备晚餐。每天中午回我妈家吃饭,我妈也会特意给我准备配合减肥的食物。

话虽如此,但是减肥归根到底还是看个人,配合一些小技巧,也能够让减肥少些痛苦,多些快乐。

  • 买一个智能称,记录(更科学的是每隔几天)自己的体重变化,让真实的数据反馈自己的训练成功,也更容易给自己激励。
  • 可以先大概了解下常见的食物的热量卡路里,在吃东西之前知道自己今天已经摄入的热量大概有多少,相比吃了就吃了的盲目,配合卡路里这个可量化的指标,可以更好的在心理上暗示控制饮食。
  • 早上运动,运动时间更可控。
  • 良好的作息,保证足够的休息量。有条件可以装个电动窗帘控制早晨卧室光线,有助于早起。

最后,祝各位朋友在新的一年也能收获健康的身体。

我在读什么: 2023 春节读书分享

2024年3月10日 20:17

距离我在 2017 年初的书单分享《年終卷 | 2016年我读了哪些书?》,已经过去6年。

2017 年结婚后,随着生活中心的转移,我的读书数量断崖式下降。

我是一个喜欢读书的人,看了下豆瓣 2008 年以来的记录,有记录已经读了 280 本书。算下遗漏的,这 15 年平均下来,每年也算能读个 20 本书。

当然,不同年份的读书量的差别也很大,多的年份如  2013、2009,一年分别读了 59 本和 36 本书,少的年份如过去的 2019 到 2022 这4年,每年只有区区 1 本。

我读书的偏好倾向「偏虚构类」,文学类书总量不多,多为经典名著,读书大头是社会、科技、政治、商业这些领域。我在过去读过的这些书,很大程度塑造了我如今的价值观和思维方式。

过去这 4 年,我经历了辞职创业和回归职场,平心而论,虽然我获取信息的渠道已经不再只限于读书,也会从其他渠道诸如 Podcasts 和视频来获取新的资讯和信息。但是每年个位数的读书量,一定程度上也反应了我在「个人成长」的停滞。

从去年下半年开始,我刻意地放慢了自己的节奏,更加注重平衡工作、生活和成长,也思考了更多。

这个春节假期,趁着新冠结束放开,同老婆回了老婆的四川娘家,外地女婿回家过年,闲着的时间有点多,特意给 Kindle 下载了豆瓣2022年度读书榜单的几本推荐书籍,在老婆家里的这 8 天,重新开始阅读。也趁着这个机会,今天这篇文章,分享一下这个假期读的 6 本书。

书单 ​

1.《双重冲击 : 大国博弈的未来与未来的世界经济》 / 李晓 /  机械工业出版社

假期第一天,读了一本经管类的书《双重冲击》,讲现在中美竞争背后的一些系统原因,作者本身是经济学出身,严肃的那种学者。介绍了不少美元体系和当今国际金融秩序规则,也从金融角度分析了一些历史大事件。

中美对抗是过去这几年的世界一个核心问题,所谓百年未有之大变局,身处这个时代,我们自己也算是亲历了这种大国博弈带来的冲击。

看完后一个感受,虽然作者全书观点讲得还是很克制,但是综合历史、当前实力对比、文化和政治影响力来说,给人一个感觉就是中国输定了。

2.《现代中国的形成(1600—1949)》 / 李怀印 / 广西师范大学出版社

假期读的第二本书《现代中国的形成》,偏理论角度,从地缘、财政、军事、政治认同角度。分析为啥中国居然能在过去这两百多年,经历内乱,分裂、入侵最终还能以「中国」这个普遍共同认知存在和发展。结合前段时间重温的《走向共和》,对大清倒又有了些新的印象。不同时期中央地方财政收入分析角度有趣。

3.《制造消费者 : 消费主义全球史》 / [法]安东尼·加卢佐  / 广东人民出版社

假期读的第三本书《制造消费者》,过去这两年出了不少诸如宣扬「非必要不消费」思潮的书。一开始我也以为又是一本反消费主义的畅销书,读毕才发现,原来是介绍欧美消费主义诞生和发展的书,视角从传统的法国农镇集市到工业时代的巴黎都市百货商场,再到印刷、广播电视媒体兴起后的美国。 交通效率的提高带来了商品流通模式的变革,也促成了跨区品牌这个概念的诞生。消遣购物这种「习惯」从资产阶层的文化符号逐渐向工薪阶层下沉;公关和广告塑造消费意识形态,思潮从从众到追逐个性,穿插性和女权,书里描述19和20世纪初的欧美家庭条件和消费习惯,感觉就是和中国 80 年代的家庭差了 100 年。

4.《可能性的艺术 : 比较政治学30讲》 / 刘瑜 / 广西师范大学出版社

假期读的第四本书《可能性的艺术:比较政治学30讲》,搜了下豆瓣记录,上次读刘瑜的《民主的细节》已经是 13 年前的 2010 年,《可》这书是从音频节目转文字而来,读起来轻松。这十多年过来,这些老公知都消失殆尽了,要不是看到书单推荐,我都不知道刘瑜出新书。公开的观念水位,已化为一张轻薄白纸。

现在的我,已经很少在公共事务上发表评论,有时候看到一些话题,看到一些言论,乃至看到一些周围认识的人的言论,都会刻意回避。

5.《心中有数 : 生活中的数学思维》 / 刘雪峰 / 人民邮电出版社

假期第五本书《心中有数:生活中的数学思维》,挺好玩的一本书,适合学生,也适合程序员和IT从业者消遣看。把人们在日常生活工作的思维和思考方式,用比较基础的数学公式函数、算法、编程理念来做解释。感觉以后教育家里小朋友可以用到这些例子。

6.《创造 : 用非传统方式做有价值的事》 / [美] 托尼·法德尔  / 中信出版集团

这个假期的报复性读书差不多收尾了,第六本书《创造》,这本书评分很高,虽然感觉这个分数背后,作者 iPod 之父和 Nest 创始人带来的光环效应加成有点多。一个硅谷成功人士碎片化分享成长、工作、创业、管理的经验和心得,中间穿插了一些业界八卦。有幸存者偏差,适当性借鉴其中的一些工作方法和思维

导航 ​

这是男人想要的手电筒模样 | warsun 沃尔森 Y40 太阳能 LED 灯

2023年9月29日 16:03

视频 ​

开箱 ​

不管是居家还是自驾,我都还是建议大家,可以备一个稍微好点的手电筒,万一停电了,或者走夜路,有个手电筒,还是能够解决掉不少问题,某些危急时刻,甚至能够救命和保命。

双十一新买了一个LED灯,觉得挺实用的,分享给大家,产品是自购,也拍了一个视频,也可以看上面的B站的视频,更加直观。

之前刷抖音,看到有视频分享能够折叠的LED工作灯,当时觉得好像这种灯功能还挺多的,心里种下了草。

搜索和对比了一下淘宝后,蹭着双十一优惠买的沃尔森 Y40,80元人民币不到。大概功能如下。

  • 1*LED COB面板
  • 1*LED 电筒灯
  • 内置4000毫安电池
  • 1*USBA 接口
  • 1*TypeC 接口(仅供充电)
  • 太阳能充电
  • 带磁铁能够固定在金属件表面

包装内就一个LED灯,我这个是稍微贵点的,绿色,很工业风,还有一个低端点的型号是黄色。正面这个黄色的是LED COB面板,正面有一个按钮,进行开关和模式切换。表皮是硬塑料,应该能摔几下。

我买的这个型号支持太阳能充电,但是对于这种小型设备的太阳能充电我觉得不是很靠谱,聊胜于无。

顶部的LED灯泡,手电筒模式的时候,能够进行远射,亮部一般般,凑合用用可以。

机器外面有一个可展开收纳的支架,方便根据需求放置LED灯。

支架底部有两个强力磁铁,能够吸附在金属物件表面,我在视频中也有展示,能够轻松地吸在车金属表面。

支架展开后就能立在桌面上,适合不同的照明场景。

充电口是TypeC,好评,出门少带一根线,接口处有橡胶,有一定的防水功效。遗憾的是只支持给LED灯充电,不支持向外充电,但是也能理解,毕竟充电宝不是它的主要功能。

另外一边就是USB A的输出口,能够给其他设备充电,内置4000毫安电池,不虚标的话应该能给手机充满一次电,应急用用应该还是可以的。

晚上的时候测试了下,开启面板COB灯的时候,亮部还是很可以的。

支架上的磁铁,能够很方便就随手贴在车上金属处,这个设计很巧妙。

COB面板支持3挡亮度调节,照明亮度对于夜间近距离的作业是完全够用的,拿来露营灯啥的也可以。

有了这灯后,后备箱里找东西就方便多了,一般车自带的后备箱灯亮度都很惨,我车自己后备箱灯已经换了一个LED灯了,亮度还是不怎么够。

这个灯还支持一个红光模式,长按按钮LED就发红光,能在夜间当警示灯。

经过对比,红光模式下,灯光亮度跟汽车自带的尾灯亮度差不多(我车尾灯是LED)。

亮度测试&对比 ​

晚上专门找了一个灯光比较昏暗的地方,拍了两组照片,对比了一下Y40这个灯和我现在已有的两个LED灯的区别。

  • NITECORE EC4S 手电筒:最高2150流明
  • Iwata GL-01 LED面板
  • iPhone12 手机自带补光灯

其中 NITECORE EC4S 属于专业的手电筒,京东上面售价要400多人民币,Iwata 是摄影补光灯,也要200多人民币。

场景A ​

场景A是弱光环境,只有远处微弱路灯,拍摄相机是索尼A73,调节参数到基本跟人眼观感差不多。对比下来,Y40在面板灯3档的时候,与Iwata GL-01 100%亮度时接近,当然,跟2150流明的 NITECORE 专业手电筒相比,上面的其他LED都是弟弟了。

场景B ​

第二个场景在小树林里,几乎全黑,老婆距离我大概5米,iPhone12开启补光灯后,基本就只能看到一个轮廓(视频中效果比较清楚),Y40面板灯3档的时候,照射范围还是挺广的,EC4S依旧秒杀其他灯。

总结 ​

我自己在家和车里,平时都各备了一个 NITECORE 的专业手电筒,之所以又买了这个Y40 LED灯,主要还是图方便,过去的两个 NITECORE 手电筒,都是使用18650电池,充电比较麻烦,有时候要用的时候发现没电了,还是挺尴尬的。

Y40 这个手电筒,价格不贵,功能也比较实用,个人觉得还是挺值的,买一个丢车里,也不占地方。

优点 ​

  • 体积小:跟 iPhone 差不多大小,收纳方便。
  • LED亮度够:至少跟我的 Iwata 比起来不差了。
  • 磁吸功能实用:简直专门为车主朋友设计。
  • 红光和爆闪模式:应急的时候实用。
  • TypeC接口

缺点 ​

  • 太阳能充电鸡肋:这种价格和尺寸的太阳能充电就是残废,都不用测试了。
  • 电池质量不明:由于我才刚到手用了没多久,不知道实际电池到底咋样,感觉应该会虚标。

我个人是挺期待,某个大厂,基于这种LED设计和规格,出一个10000毫安 + PD双向充电 + LED面板灯 的工作灯。售价200以内我都能接受。中国制造加油呀。

我看 WWDC

作者 MacTalk
2023年6月6日 15:27

苹果公司一向特立独行,很少跟风热炒,当满世界都在炒AI、大模型、GPT 的时候,苹果对此一直保持沉默,平静如水。即便是在 WWDC 大会上,库克压根就没提这词。小盖昨天看了直播,说,唯一接近的表达是在讲输入法时,提到了 Transformer 语言模型;讲 iOS 17 优化项时,提到了机器学习技术。

但机器学习,不是苹果每次介绍自家软硬件都会提的词眼么?

比如,新的日记应用 Journal 利用机器学习技术,可以为用户提供具体的写作建议,还能生成具体的问题,降低写作门槛。

嗷嚎,难道是墨问便签的竞争对手,我一下就警觉起来了,嗯,怎么肥事?

苹果一贯风格是,要么开创先河,要么后来居上,一个技术不成熟的时候,绝不会发布。强者风范,当然,坐拥几万亿市值,很难低调。人家做到了,就是低调的强者。

WWDC 这次除了家常菜,还端了一盘硬菜上席:One More Thing ……最后半小时,库克在经典的乔布斯环节,推出了万众瞩目的 AR 眼镜 Vision Pro,对比平平无奇的软件更新,Vision Pro 可以说是本次发布会的最大惊喜。

七年磨一剑,从 2015 年苹果开始组建 XR 开发团队,到不断加大投入,收购相关公司,每年的 WWDC 大会上,ARKit 和 RealityKit 的版本更新,都在为苹果的增强现实技术的底蕴添砖加瓦。发布会上 Vision Pro 的宣传效果相当惊艳,充满了科幻色彩。虹膜解锁、眼手语音交互、4K 清晰度、Eyesight 解决虚拟与现实如何融合的问题,M2+R1 解决延迟眩晕问题。

看完视频宣传片之后,小盖擦擦口水说,想象一下可以躺着办公看电影写文章看照片,哗,格局一下就打开了。

社交、办公、3D、电影娱乐,“超沉浸式把数字化内容融入生活,将带我们去往新时代——空间计算时代。”苹果保持了自己的风格:我提供的不是新产品,而是新的生活方式。

除了令人激动的头显 Vision Pro 外,我更感兴趣的则是 iOS 17 带来的一款全新的应用程序:Journal。你可以理解为苹果官方出品的一款日记应用,产品初心是帮用户记录日常的生活和感受。

为啥感兴趣?毕竟,墨问也是笔记类型的产品啊。

类似 Journal 这样的日记应用在应用商店中已经有很多。比如,Day One,从交互到功能都很出色,简单且优雅。我早些年公众号的初稿都是在 Day One 完成的,后来用 Bear,现在呢,只用墨问便签了。

为什么苹果要做这个 App 呢?我想,应该是苹果开始关注“心理健康”领域了。

库克曾经说过,苹果对人类最大的贡献将会在健康领域。这些年,不管是 Apple Watch,还是不断更新健康 App,都在朝着这个方向前进。这次 iOS 17 中,健康 App 就增加了设备与眼睛之间的距离检测,有助于降低儿童患近视的风险,还可以帮助成人用户缓解数字眼疲劳症状。

除了身体健康外,我还有个明显的体感,这些年大家心理健康方面的问题越来越多。前段时间,我们还写文章说这可能和大家的手机瘾有关系,因为通过手机获取简单的、肤浅的快乐实在太容易了。而那种肤浅的快乐会让人们精神上更脆弱,会让人们逐步失去做事的耐心,进而引发心理问题。

我想,Journal 是他们在心理健康方面的首次尝试。一系列的心理研究表明,写日记,我们可以重温某个特殊时刻的感受,反思自己的情绪,并表达感激之情,这些都能有效改善人们精神健康状况。

咦,这不也是我们的创业初衷么?

既然出手,应用联动不可或缺。与其他日记应用不同,Journal 无缝打通了 iPhone 的位置、照片、音乐、信息、播客、体能训练等应用程序。它可以根据这些信息来分析用户的行为,并结合设备端的机器学习技术,给出具体的个性化写作建议。比如,你大部分时间都在北京,但六月有一天突然去了海边,苹果会觉得这一天可能会比较特殊,于是,它整合当时的数据,比如听的音乐、拍的照片、具体的位置,然后给出写作建议。

从截图可以看出来,记录时,Journal 会给出写作 prompt,比如一些具体的问题,降低用户写日记的门槛。比如你去了北戴河,它会问你,“这次短暂的旅行,有哪些让你印象深刻的回忆?”从这个视角看,我倒觉得它比 Day One 更胜一筹。通过机器学习的能力,Journal 可以像一个心理医生一样,基于已有的数据,向我们提问,帮我们反思、重温特殊时刻,梳理情绪和感受。

隐私和安全也是必然会提及的。为了保护隐私,Journal 可以单独锁定以防止偷窥,同时,它的数据是端到端加密的,苹果自己也无法查看。用户可以具体控制使用哪些设备数据来生成 Journal 的建议,以及什么时候推送日记写作的通知。另外,苹果还计划为开发人员提供一个 API,以帮助他们在自己的应用程序中集成这些个性化的写作建议。

不出意外的话,Journal(手记)今年 9 月推出。

到时候墨问和手记强强联手,无敌于天下,否则,这个产品能否让国内用户用起来,我保留看法。毕竟,上一个无边记 App,我到现在什么都没记。身边的朋友,用者寥寥。

这次 Journal 的中文叫手记,你会用么?

写到这里小盖说,池哥,先吃饭吧,吃饱了有力气吹牛。

我说,好的。

2023年6月6日 下午 2:10

咔嚓咔嚓,非常棒

作者 MacTalk
2022年7月4日 17:25

设计简单并且具备生命力的产品并不容易。2012 年,龙哥说产品经理是站在上帝身边的人。为什么这么说呢?

首先龙哥想恭维一下产品经理,其次,要做简单的产品。

上帝制定了世界运行的规则,这些规则都很简单。大约在 1605 年,开普勒就发现了行星的移动遵守著三条相当简单的定律,这就是著名的开普勒三大定律。这是咱们高中学习的,想必读者也已经忘的干干净净。但是,如果你是个产品经理,要记住,世界的规则是简单的,简单的才是美的,有生命力的。到了我们有能力做产品的时候,也不要把事情搞复杂。

自然的、简单的、有规则的产品,才会自我传播,才会被用户推荐给用户。

晚上收到了四卷冲扫出来的照片,其中有一卷漏光了。回北京的时候,我特别想拍官厅水库的风车,于是把车停在水库的边上,拽出那个腰平取景的胶片相机拍摄,不知道挂在哪了,居然卡吧一声,后背暗盒打开了。这可是不得了的事儿,就是说,暴露在光线里的底片,成了废片。我手忙脚乱,赶紧合上后背检查,也没发现是咋打开的。

上网搜了一下,觉得大部分胶片还有救,何况还有没拍完的。于是重新按下快门,拍下了这张非常漂亮的风车照。

最终发现,只有五六张是废片,整体非常棒。

创业也一百多天了,已经喊完了“预备、开始”,并且进入了拍摄状态。

咔嚓咔嚓,非常棒。

徕卡自由列车,一段隐秘历史

作者 MacTalk
2022年5月31日 13:33

最近在家时间比较多,没地方拍照片,于是看了不少摄影相关的资料,比如《一生的凝视》《传奇布列松》《马格南世纪经典》《20 时机》《无畏的天才》《我们这一代》等等,当然,做为一个准器材党,我还在阅读《顶级摄影器材》这样的书籍。

热爱并不是一个很轻松的事情,这往往意味着奉献和创作。爱一个人,喜欢一个东西,都需要付出你自己拥有的资源,比如时间、金钱、热情等等,大部分时候,更多的热爱会形成创作,比如拍照片、画画、作曲、演唱、写作等等。很多人会说我热爱运动,但如果你只是瘫在沙发里用手指在屏幕上做点戳和划动运动的话,那只能说明你热爱刷手机,并且处在一个非常低级的阶段。

高级的热爱是什么?比如老罗喜欢手机,于是人家自己做了好多款。这叫热爱,虽然没成功,但很高级。

我热爱摄影,但我做不了相机,我可以欣赏摄影作品,也可以自己创作影像,我还可以去了解摄影的历史……所以我最近还看了很多视频博主的 vlog……终于,有一天,我发现了一段关于徕卡的隐秘历史。

在讲这个故事之前,我们可以先聊聊徕卡。

喜欢玩相机的朋友,估计都听说过徕卡这个名字,135 相机翘楚,旁轴之神,相机界的爱马仕,摄影领域的寇德卡……徕卡的最主要的特点是:贵。二爷对此嗤之以鼻,他说,贵不能叫做特点,比如,你能和别人说,我很穷是我的特点吗?不能。二爷斩钉截铁。

二爷说得很有道理,如果你觉得一个东西贵,那么多从自身找找原因吧。

至于其他的特点,那就很多了,比如颜值高、工艺精良、坚固可靠、成像素质好、性能快等等。当年徕卡的全机械相机极为耐用,以至于二战时期,徕卡成了随军记者的标配。

另外,相对于其他相机品牌,徕卡最牛的是:有无数传奇的摄影大师,用他们手中的徕卡拍出了传世的照片,比如:

徕卡的第一部 35 毫米相机叫做 Ur-Leica,是由工程师奥斯卡·巴纳克手工打造而成,这也是世界上第一部 35 毫米相机,从此开启了徕卡的传奇故事。不过,徕卡公司的创始人并不是奥斯卡·巴纳克,他叫做恩斯特·徕兹,公司名字也不叫徕卡,而是叫做,嗯,还是叫徕兹。后来奥斯卡·巴纳克发明了 35毫米的 Ur-Leica 相机,徕兹的相机生意越做越大,公司就改叫徕卡了,徕卡是由徕茨(Leitz)和照相机(Camera)的前音节组合而成。

在研究徕卡相机和徕卡历史的过程中,我发现了一个隐藏在背后的故事,这个故事叫做 The Leica Freedom Train,也就是徕卡自由列车。

这个故事讲的并不是写徕卡和火车,而是徕卡公司在二战时期拯救犹太人的故事。Freedom Train,一辆通往自由和承载生命的列车。

据说,徕卡自由列车的故事已经在一百多个不同的摄影网站上讲了一百多次。但是,如果你像我一样刚开始了解徕卡故事,那这个故事,很可能就是冷知识。

1933,纳粹开始逐步执行驱逐犹太人的政策,同年 4 月1 日,纳粹第一次进行了全国性的有计划反犹行动:抵制犹太人工商业。与此同时,恩斯特·徕兹二世和他的女儿艾尔茜开始了帮助犹太人离开德国的计划,这个计划就是徕卡自由列车。随着世事变迁,战争的阴影逐步笼罩欧洲,纳粹对犹太人的敌意越来越浓厚,恩斯特开始加快徕卡自由列车,他们要做的就是把自己的犹太员工,送往徕卡的海外办事处,比如法国巴黎、英国伦敦,美国纽约,中国香港等地。

恩斯特的努力早已超出了一个企业家的范围,甚至,他“雇佣”犹太人的唯一目的就是把他们送出德国。

到了后来,很多犹太人都不是徕卡员工了,简单培训一下,包装成徕卡工人之后就送到海外办事处。据说很多人被送到海外之后,徕卡公司会为他们提供一台徕卡相机,这并不是让大家拍照玩,那会儿饭都没得吃了。在那个特殊的时候,他们可以把这台相机卖掉,以获得足以生存下来的生活资料。

关于这个细节,在 这篇文章 里,有精彩的描述。

虽然徕兹拯救的确切生命数量永远不会被统计,但据估计,这个数字至少在几百人左右。他的善意远远超出了作为一个简单的“摆渡人”。徕卡自由列车上的许多人赤贫,在异国他乡会面临不确定的新生活,他们不仅需要离开,而且需要安全着陆。
在纽约市,德国“雇员”从不来梅号远洋班轮上岸,并立即向徕兹的曼哈顿办公室报告。在那里,每个上岸者都会收到一台全新的徕卡相机,并且还能获得一小笔津贴,尽管他们对公司没有真正的技能或合法价值,但徕卡公司表达的信息是明确的:你不会被忘记,你会得到照顾。

这一个浪漫的礼物,希望他们能够通过摄影的力量,看到一个新的世界,通过一个新的眼睛去观察。现实情况是,对于许多难民来说,徕卡相机被当作一种保险单,一种可以交换的物品,可以换成足够的钞票和物资。

面对一个一心想摧毁自己人民的种族灭绝政府, 逃离祖国,并不是一件浪漫的事情。

在战争爆发前期,也就是 1939 年前后,这个行动到达了一个高潮,公司动用了所有资源日夜不停的把犹太员工运往海外,每周的船只来往不休,直到 1939 年德国闪击波兰,国境线关闭,这项计划才不得不停止。

战后,这个家族一直保守了这个秘密,从来没有对别人讲过,直到一位知情作家在 2002 年出版了一本书,书名叫做《恩斯特家族的伟大发明:徕卡自由列车》,书中披露了当年的往事,人们才知道徕卡公司这段传奇经历。

这本书的英文名叫做:”The Greatest Invention of the Leitz Family: The Leica Freedom Train”,作者是,FRANK DABBA SMITH,美国摄影历史学会出版。

最后,第二次世界大战进入尾声,有一首《战报记者之歌》开始发行,这首歌里有这么一首歌词:

带着徕卡和笔记本,和机关枪
我们穿越过火焰和寒冷

这样,我也就理解了,为什么徕卡这个百年品牌,能够流传至今。很多东西,成为传奇,真不是运气的成分,有善良,有力量,有科技,有爱,有美好。

老想着成功干嘛?就好像我们的智力不足以应付不追求成功的生活似的

作者 MacTalk
2022年5月20日 15:46

有用户在认识到自己是普通人之后,挺苦恼,怎么努力,也是中上水准,问我咋办。

在回答这个问题的时候,我刚好看到雷军的一个视频。雷军这些年取得的成就可谓有目共睹,谈到自己的成绩,他对记者是这么说的,聪明的人,优秀的人,勤奋的人,太多了,轮不到你成功。怎么办,顺势而为,找到自己的风口。这也许就是风口说的起源。

那怎么顺势而为呢?需要判断、选择,需要运气,但我们今天不聊这个,我想和你说的是,大部分人都是老实人、普通人,要接受这一点,然后再去努力,就比较容易做到自洽。

是的,别人很好,但我也很棒。你纵然精彩万千,但那是你的生活,而我,过好当下的每一天,每天比昨天进步一点,每天有一个小时做自己喜欢的事情,每天都很快乐,这就是我的生活。我也很棒。

这就是我要送给你的话,在能够养活自己,照顾好家人朋友的情况下,快乐地过自己的生活,就是一种意义。

还是那句话,老想着成功干嘛?就好像我们的智力不足以应付不追求成功的生活似的。

说起智力,比较令人伤怀的是,如果你老老实实地经历了中国教育的所有阶段,不得不承认,咱已经没那么优秀了。等到进入工作状态,你会惊奇的发现,咦,我竟然是个普通人,牛人去哪了?

牛人的经历可能是这样的,当我们在中学瞎晃的时候,他们已经大学毕业了,当我们在大学谈恋爱的时候,他们已经博士或博士后毕业了。还有一些野生的天纵奇才,当你还在利用工作前三年寻找方向的时候,你发现这些野蛮生长的家伙已经第二次创业了,并且做的不错。

你做企业三年做了一个亿,小目标实现了吧,别人三年都上市了,市值好几百亿,还是美金。

愣去比较,往往适得其反,动作变形,做人,做企业都是如此。

有位伟大的作家曾经自嘲,多么伟大的作家,不过都是在描述自己生活的片面罢了。伟大作家尚且如此,每个人来到世间,都不是演绎完美,我们是来经历的,做点自己没做过的事情(合理合法),不断突破自己,就很好了。

人生的真相就是,每个人都有自己的苦。都不容易,当你觉着自己的生活难、不容易、不公平,其实你的朋友、同事、上下级,甚至你的亲人爱人,也这么想,谁比谁轻松多少呢?

《夜航西飞》的作者柏瑞尔在书中描写非洲的干旱:

有一年,恩乔罗地区所有的种子都死了,恩乔罗附近所有农场的情况也一样,无论是低处、山上还是林中的田地,无论大农庄还是仅靠一把犁与一个希望开垦的农田。因为得不到营养,种子都死了,它们绝望地渴盼着雨水。

第一天早晨,天空如窗户般明净,第二天早晨依旧如此,接下来的每个早晨也都一样,直到人们不再记得下雨是什么感觉,也不再记得田野看起来是什么样。它们曾绿意盎然,浸润着生命,赤足可踩踏其间。一切都停止了生长,叶片蜷缩,所有生物都背朝太阳。

或许在别处 —— 伦敦、孟买、波士顿,某家报纸上写了一个标题:旱情威胁英属东非。或许有人看到了这条新闻,抬起头来看着他头顶的那片天空 —— 就和我们头顶上的这片一样清朗,他可能觉得非洲最边缘的干旱根本算不上新闻。

这就是生活的真相。

先接受,再改变。怎么改变呢?从书中学,和高人聊,往事上练。持之以恒,时间会为你证明。


新的胶片洗出来了,上图

Golang 泛型实践

2022年5月17日 08:00

泛型

泛型是什么

开宗明义,泛型,类型形参和类型实参

在函数定义中,定义要求的参数叫形参,实际传入的参数叫实参,在强类型编程语言中要求形参和实参的类型一致。

这样就会有一个问题,导致我们的函数限制非常强,特别是在 Golang 的数字类型有很多种的情况下。

package main

import "fmt"

func MinInt(a, b int) int {
    if a <= b {
        return a
    } else {
        return b
    }
}

func main() {
    fmt.Println(MinInt(1, 2))
}

像这样比较大小的函数,(a,b) 就是形参,(1,2) 就是实参,我们可能对于不同的类型都要分别再写一个函数。

如果能够不限制形参类型,在函数调用的时候再指定具体类型,这个问题是不是就可以解决了呢?

那么我们就需要引入类型形参 (Type Parameter) 和类型实参 (Type Argument) 的概念。


假设有这样一个函数,就是在函数定义的时候使用类型形参,然后将参数类型也当成参数传入,进行类型的参数化。

在函数调用的时候,不但需要传入参数,还需要传入参数的类型,可以把类型像方法的参数那样传递,指定实际执行的参数类型。

func min[T Numeric](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(min[int](45, 74))
    fmt.Println(min[float64](4.141, 2.01))
}

这样就能让一个函数处理多种不同类型数据,可以在函数调用的时候再明确参数类型,这种方式就被称为泛型编程。

那么其实并不是泛型编程中就不需要关心参数类型,只是将参数类型进行后验确定,在 Golang 编译时还是会根据实际参数类型对函数进行展开编译。

所以在 Go1.18 引入泛型之后,对编译性能会一定的影响,大概会有 18% 的性能下降,但是对运行性能无影响。

引入泛型的好处

  1. 在编译期间对类型进行检查以提高类型安全
  2. 通过指定类型消除强制类型转换
  3. 能够减少代码重复性,提供更通用的功能函数。

但是也泛型也不是银弹,不是所有场景下都需要泛型编程,主要在针对不同类型的数据写完全相同的逻辑代码的情况下,可以考虑使用泛型。

泛型怎么用

泛型就是泛指的类型,即类型参数化,在定义时无需确定参数的类型,可以在调用的时候再指定参数类型。

在这里我们去掉了函数,因为实际泛型的用处不止在函数中,也可以在类型,接口,函数等各种地方。

泛型的应用主要有三种

  1. 泛型类型
  2. 泛型接口
  3. 泛型函数

泛型类型

常用于一些集合类型,比如使用泛型栈的设计,因为 Golang 强类型语言,一般的栈只能是固定类型,泛型栈可以在实例化的再确定具体的元素类型。

比如设计一个支持整形和字符串的列表和字典,这里的泛型占位符 T,K,V 都可以,只需要保持在同一个定义中前后一致就可以。

package main

import "fmt"

type ListType[T int | int32 | int64 | string] []T

type MapType[K int | int32, V int64 | string] map[K]V

func main() {
    var intList ListType[int]
    intList = []int{1, 2, 3}
    fmt.Println(intList)
    strList := ListType[string]{"1", "2", "3"}
    fmt.Println(strList)

    intMap := MapType[int, string]{1: "1", 2: "2"}
    int32Map := MapType[int32, int64]{1: 2, 3: 4}
    fmt.Println(intMap)
    fmt.Println(int32Map)
}

还有一个简单的泛型类的例子


这里的泛型占位符 T 就是类型形参,在 T 的可选类型中,int | int32 就是 T 的类型约束 (Type Constraint)

就是在实际传入类型实参的时候,只能使用类型参数列表中限定的类型,其中 any 表示任意类型,同 interface{}

泛型接口

接口是对类的进一步抽象,将类抽象为接口是一种基本技能,在接口定义时只需要给出函数签名而无需完成其具体逻辑。

Golang 中会自动查找类上定义的方法,如果某个类实现了接口定义的全部函数,即可认为类实现了这个接口。

在面向对象的编程语言中,使用接口的时,在函数定义中无需指定其具体的实现类,只需要传入参数对象实现了接口定义的方法即可。

在函数定义的时候,也可以将接口作为参数类型,这样就可以传入任意一个实现了该接口的对象。

注意 在这里的接口都是指 interface, 而非 interface{}.

因为 interface{} 作为一个空接口在 Golang 中也被认为是基础对象类型,类似于 Java 中的 Object

所以为了避免歧义以及减少书写成本, 在 Go1.18 之后,新增 any 类型代替 interface{} 类型,可以在代码中做全量替换。

可以使用这行代码进行全量替换 gofmt -w -r 'interface{} -> any' ./...

对于一个泛型接口,可以在接口定义的时候声明泛型,不限制其具体的实现类型。

同样的像上面提到的泛型栈,就可以使用泛型接口来进行抽象。

type GenericStackInterface[T any] interface {
    Push(element T)
    Pop() T
}

除了泛型接口,在 Golang 1.18 中 interface 还新增了类型集合的概念,可以在接口定义中添加多种类型。

注意相当于之前的接口是函数集合,可以用来声明一些函数,但是在 Go1.18 中扩充了类型集合,可以在接口中声明一些类型,在同一个接口中,可以同时存在类型集合和函数集合。

type Numeric interface {
    int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}

像这种就是类型集合,以前的接口就是原始的方法集合,也被称为基本接口,基本接口也是一个空的类型集合。

在类型集合中,在同一行内的多种类型,使用 | 相连,表示类型取并集,如果分成多行,则各行之间取交集。

如果交集为空,则为取空集,空集不等于空接口,空接口表示可以使用任意类型,空集表示无法使用任何类型。

在泛型接口定义时,泛型类型集合与接口集合,不能同时使用类型形参和类型集合,在类型集合中也不能使用递归定义。

泛型函数

泛型除了可以在类定义和接口定义中,可以在函数定义中使用,作为入参或出参的类型。

比如在上面泛型栈的设计中,对于入栈和出栈的操作,就已经用到了泛型函数的定义。

再比如,一开始就提到的比较大小的场景,我们可以定义类型形参,允许多种不同类型的整形比较

func minInt[T int | int8 | int16 | int32](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func maxInt[T int | int8 | int16 | int32](a, b T) T {
    if a > b {
        return a
    }
    return b
}

但是这样的话,每个函数中都需要对各种数字类型给排列一遍,我们可以使用类型集合,将所有数字类型先定义出来。

比如优化后的比大小操作

package main

type Numeric interface {
    int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}

func min[T Numeric](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func max[T Numeric](a, b T) T {
    if a > b {
        return a
    }
    return b
}

这里的所有的数字类型,都被包含在 Numric 中,这样就可以在泛型函数中比较大小,这种带类型形参的函数被称为泛型函数。

前面提到泛型函数在调用的时候是需要指定其具体类型的,但是对于某些简单类型也会自动推导,可以省略。

func main() {
    maths.MinInt(1, 2)
    fmt.Println(min(45, 74))
    fmt.Println(min[int](45, 74))
    fmt.Println(min[int32](45, 74))
    fmt.Println(max(4.141, 2.01))
    fmt.Println(max[float64](4.141, 2.01))
    // 编译报错:cannot use 4.141 (untyped float constant) as int64 value in argument to max[int64] (truncated)
    fmt.Println(max[int64](4.141, 2.01))
    // IDE报错:Cannot use string as the type Numeric
    // 编译报错:string does not implement Numeric
    fmt.Println(max[string](4.141, 2.01))
}

但是注意两点

  1. 可以执行类型,如果指定类型和实际传入参数类型不一致,就有编译报错
  2. 不指定类型,编译器会自动识别类型,如果类型不一致,也会有编译报错

因为 Golang 的泛型是在编译时会根据指定的具体类型将泛型确定下来,运行时还是有强类型限制的

对于在通用的数字类型,Golang 1.18 中也内置了 constraints.Ordered 表示所有可供排序的内置类型,所以上面的泛型函数可以改写成这样

package main

import (
    "golang.org/x/exp/constraints"
)

func minType[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func maxType[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

如果进入源代码查看其具体代码的话,会发现在 Ordered 类型中也是有各种数字类型组合起来的。

但是有一点奇怪的地方就是这里的类型集合中,各种类型前加了一个波浪线~, 表示衍生类型,即使用 type 自定义的类型也可以被识别到,只要底层类型一致即可。

比如 ~int 可以包含 inttype MyInt int 等多种类型

// Signed is a constraint that permits any signed integer type.
// If future releases of Go add new predeclared signed integer types,
// this constraint will be modified to include them.
type Signed interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64
}

// Unsigned is a constraint that permits any unsigned integer type.
// If future releases of Go add new predeclared unsigned integer types,
// this constraint will be modified to include them.
type Unsigned interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}

// Integer is a constraint that permits any integer type.
// If future releases of Go add new predeclared integer types,
// this constraint will be modified to include them.
type Integer interface {
    Signed | Unsigned
}

// Float is a constraint that permits any floating-point type.
// If future releases of Go add new predeclared floating-point types,
// this constraint will be modified to include them.
type Float interface {
    ~float32 | ~float64
}

// Complex is a constraint that permits any complex numeric type.
// If future releases of Go add new predeclared complex numeric types,
// this constraint will be modified to include them.
type Complex interface {
    ~complex64 | ~complex128
}

// Ordered is a constraint that permits any ordered type: any type
// that supports the operators < <= >= >.
// If future releases of Go add new ordered types,
// this constraint will be modified to include them.
type Ordered interface {
    Integer | Float | ~string
}

除了 Ordered 类型之外,还提供了一个内置接口类型, comparable ,这里的可比较指 的是可以用 ==!= 是否相同进行比较,而非可以进行 >< 进行大小比较,但是注意在接口集合中,不能使用 comparable 接口和其他类型的并集。

在泛型的使用时,可以用在类型定义和接口定义中,作为类型约束和类型集合使用,也可以用在通道 (Channel) 中。

但是也有一些不能用的时候,比如

  1. 不能单独定义泛型,
  2. 比如匿名结构体不能使用泛型,
  3. 比如匿名函数不能自己定义类型形参。
  4. 有就是只能在泛型类中使用泛型方法,不能在非泛型类中使用泛型方法。

类型形参只能被定义一次,类型实参也只能被传入确定一次,不能用类型实参确认多次。

参考链接

Go 1.18 泛型全面讲解:一篇讲清泛型的全部
泛型的作用与定义
泛型是双刃剑?Go1.18 编译会慢近 20%
Go 1.18 正式发布了!支持泛型、性能优化…
浅谈Go1.18中的泛型编程
Golang 泛型浅析
Java 泛型优点之编译时类型检查
为什么要使用泛型?
秒懂Java之泛型
Go语言泛型设计
函数式编程在 Go 泛型下的实用性探索
Go泛型快速入门
Go 泛型简明教程
Go 泛型初步
Go 1.18新特性学习笔记04: Go泛型的基本语法
Go 泛型的这 3 个核心设计,你都知道吗?
Go泛型介绍
Tutorial: Getting started with generics
An Introduction To Generics

拼图游戏

2021年10月8日 08:00

缘起

在今年五月份得到的第一份拼图,看起来平平无奇,当时觉得也不是很难的样子。

然后拼了两个月,使用各种姿势都尝试过,都没有拼起来,甚至在怀疑这是个真的拼图么?😭,还是一个故意没有解法的拼图,让你浪费时间。

但是看到其包装盒上写的是 the most difficult of puzzle designs, 无奈弃之一旁,再无瓜葛。

解决

这个拼图是真的很难的,根据拼图难度分级据说是十级难度,就是最高难度。九片拼图,十级难度。

因为它会给你很多误导,看起来很多种尝试都可以完美的结合在一起,但是实际上缺因小失大,和其他的分片不能兼容。

其中最难的是有一个直角边,看起来就是在某一个角的位置,十分的规则,结果竟然是最不规则的一个,直接斜着放入。

其实我一直都没有解决,在网上的一个视频上偶然发现了这个拼图的解法,对这个拼图顿时多了几分敬意。

respect !!!

终于,在得到这个拼图五个月后,第一次完整的拼起来。

泪目,✿✿ヽ(°▽°)ノ✿ 完结撒花。

日历

一般的拼图游戏,或者其他的益智解谜类游戏,无论过程是多么的辛苦,一路经过多少的挫折,花了多长的时间。

一但完成之后就会立刻变得索然无味,因为再次实现就会非常的简单。

但是这款日历拼图就不一样,它能够让你一年之内都爱不释手,每天一个新的挑战。

【A Puzzle A Day】

解法

这款日历拼图其实并不难,一般十几分钟都能解决,但有的时候就是拼不出来,十分难受,憋屈的很,心慌意乱,然后就会花更长的时间。

有一个在线地址,可以试玩一下。

然后刚开始拼起来还挺好玩的,每天都有一个小小的成就感,但是真的需要耐心,性子急的人不适合。

不然就是一天一个拼图,一个拼图拼一天。A Puzzle A Day,A Day For A Puzzle。

国庆节七天果然一天都没拼出来,气死我了,😤,气死我了,😡。

不就是拼图嘛,我写代码解一个,暴力遍历,递归加回溯,实现 OnePuzzle

可以解出来任意天的日历拼图,虽然时间也有长有短,但是比手动还是要快了不少,我又快乐了,哈哈,😄。

国庆七天乐

使用也很简单,直接 pip install OnePuzzle 即可安装。

然后使用 one_puzzle 命令就可以自动解图了,比如 one_puzzle 10 11 就会给你10月11日的一个解法。

加上 -d 参数就会展示计算次数,一般都需要经过几十万甚至上千万次计算才能够有一个合适的解,计算机还是快吖。

加上 -a 参数就会计算所有的解法,因为代码问题,或者算法问题,现在这里的计算还是需要很长很长时间的,可以后续优化一波。😂

updated: 0.2.0 版本优化之后,速度明显加快,单个的解密大概在一分钟之内,全量的方案大概在十分钟之内,👍

其实一开始我就知道,有一个在线解法,Calendar Puzzle Solver,它能够根据当前日期,得到当天解法,而且只有当天的解法。

这样其实挺好的,每次只给当天的解法,这一天的全部解法,🐂🍺。

探究 Golang interface

2021年3月16日 08:00

Golang 是一门强类型的高级编程语言,有人说

与 C++ 相比,Go语言并不包括如异常处理,继承,泛型,断言,虚函数等功能,但增加了 slice 型,并发,管道,垃圾回收,接口(interface) 等特性的语言级支持。

对于此,我只能说,Go 虽然没有这么多的高级功能,但是它难写难用吖,Go 是垃圾(并不)。

interface

在 Golang 中的 interface 有两种意思,一种是和 Java 等语言中一样的接口 Interface 类似的含义,一种是和 Python 中的无指定基类对象 object 类似的含义。

接口定义

在 Golang 中虽然没有泛型,但是也可以定义 interface 接口,在函数中使用 interface 实现调用。

package main

import (
	"errors"
	"fmt"
	"log"
)

type Animal interface {
	call() string
	eat(string) error
}

func feedAnimal(animal Animal) {
	err := animal.eat("grass")
	if err != nil {
		log.Printf("eat error:%+v\n", err)
		err = animal.eat("meat")
		if err != nil {
			log.Printf("eat error again:%+v\n", err)
			fmt.Printf("animal %#v died", animal)
			return
		}
	}

	fmt.Printf("animal %#v eat finished\n", animal)
	fmt.Printf("animal call:%s\n", animal.call())
}

type Sheep struct {
}

func (sheep Sheep) call() string {
	return "Baa"
}

func (sheep Sheep) eat(food string) error {
	if food != "grass" {
		return errors.New("I dislike this")
	}
	return nil
}

type Tiger struct {
}

func (tiger Tiger) call() string {
	return "Wailing"
}

func (tiger Tiger) eat(food string) error {
	if food != "meat" {
		return errors.New("I need meat")
	}
	return nil
}

func main() {

	zoo := []Animal{Sheep{}, Tiger{}}
	for _, animal := range zoo {
		feedAnimal(animal)
	}
}

基类对象

Golang 是强类型语言,如果想把多种类型的数据放在一起,就需要使用 基类对象 interface,可以表示为任意对象类型。

对于 interface 类型,可以使用 . 来做类型校验和转换,也只有 interface 类型可以使用 . 方法校验转换。

package main

import (
	"errors"
	"fmt"
)

type Bird interface {
	fly()
}

type Mahjong struct {
}

func (m Mahjong) fly() {}

type Pigeon struct {
}

func (p Pigeon) fly() {}

type Horse struct {
}

func guess(o interface{}) error {
	_, ok := o.(Pigeon)
	fmt.Printf("I guess it's a pigeon\n")
	if ok {
		fmt.Printf("Congratulations,you are right\n")
		return nil
	} else {
		fmt.Printf("Oops, you ares wrong.\n")
	}

	_, ok = o.(Horse)
	fmt.Printf("I guess it's a horse\n")
	if ok {
		fmt.Printf("Congratulations,you are right\n")
		return nil
	} else {
		fmt.Printf("Oops, you ares wrong.\n")
	}

	_, ok = o.(Bird)
	fmt.Printf("I guess it's a bird\n")
	if ok {
		fmt.Printf("Congratulations,you are right\n")
		return nil
	} else {
		fmt.Printf("Oops, you ares wrong.\n")
	}

	_, ok = o.(int)
	fmt.Printf("I guess it's int number\n")
	if ok {
		fmt.Printf("Congratulations,you are right\n")
		return nil
	} else {
		fmt.Printf("Oops, you ares wrong.\n")
	}

	_, ok = o.(string)
	fmt.Printf("I guess it's string\n")
	if ok {
		fmt.Printf("Congratulations,you are right\n")
		return nil
	} else {
		fmt.Printf("Oops, you ares wrong.\n")
	}

	_, ok = o.(map[interface{}]interface{})
	fmt.Printf("I guess it's map\n")
	if ok {
		fmt.Printf("Congratulations,you are right\n")
		return nil
	} else {
		fmt.Printf("Oops, you ares wrong.\n")
	}
	return errors.New("OMG, I don't known what's it")
}

func main() {
	mess := []interface{}{1, 2, "hello", Mahjong{}, Pigeon{}, Horse{}, map[string]string{}}
	for _, m := range mess {
		fmt.Printf("start to guess:%#v\n", m)
		err := guess(m)
		if err != nil {
			fmt.Printf("haha, you fail:%+v\n", err)
		}
	}
	
}

当然,如果只是为了判断类型,使用 switch 会更方便一些。

package main

import (
	"errors"
	"fmt"
)

type Bird interface {
	fly()
}

type Mahjong struct {
}

func (m Mahjong) fly() {}

type Pigeon struct {
}

func (p Pigeon) fly() {}

type Horse struct {
}

func switchGuess(o interface{}) error {
	switch o.(type) {
	case Pigeon:
		fmt.Printf("I guess it's a pigeon\n")
	case Horse:
		fmt.Printf("I guess it's a horse\n")
	case Bird:
		fmt.Printf("I guess it's a bird\n")
	case int, int8, int16, int32, int64:
		fmt.Printf("I guess it's int number\n")
	case string:
		fmt.Printf("I guess it's string\n")
	case map[interface{}]interface{}:
		fmt.Printf("I guess it's map\n")
	default:
		return errors.New("OMG, I don't known what's it")
	}
	return nil
}

func main() {
	mess := []interface{}{1, 2, "hello", Mahjong{}, Pigeon{}, Horse{}, map[string]string{}}
	for _, m := range mess {
		fmt.Printf("start to guess:%#v\n", m)
		err := switchGuess(m)
		if err != nil {
			fmt.Printf("haha, you fail:%+v\n", err)
		}
	}

}

使用 switch 就简洁很多了,而且不用一个一个判断,直接判断给出正确的类型,不过使用 switch 有几点需要注意

  1. o.(type) 同样是只有 interface 才有的功能,其他的类型无法使用类型判断
  2. 每个 case 中无需 break ,和其他的编程语言中的 switch 不太一样
  3. 对于多个 case 合并,使用逗号 , 分割即可

    如果使用两个单独的空 case ,并不能合并,只是表示空 case 而已。

  4. 对于 list 或者 map 类型的判断,是要求子元素的类型也要完全一致才匹配的,使用 interface{} 也不能做通用判断

空值

对于 Golang 中的空值有两种含义

  • 对于基础数据类型或者结构体来说,空值就是初始值,初始值就是零值。
  • 对于指针类型来说,空指针就是 nil, 如果对 nil 进行操作,就会触发 panic ,类似于 Java 中的 NPE, 但是会更可怕。

一般的常见类型在初始化时就会赋予零值,包括在函数签名中定义的入参和出参,也会被自动初始化

package main

import "fmt"

type Phone struct {
	Brand string
}

func main() {
	var iphone Phone
	iphone.Brand = "Apple"
	fmt.Printf("introduce my phohe to you:%+v\n", iphone)

	miPhone := Phone{}
	miPhone.Brand = "mi"
	fmt.Printf("introduce another phohe to you:%+v\n", miPhone)

	var TempList []string
	TempList = append(TempList, "phone")
	fmt.Printf("temp list is:%+v\n", TempList)

	var TempInt int
	fmt.Printf("init int is:%+v\n", TempInt)

}

但是对于指针类型,并不会在初始化的时候赋值,需要先创建对象,然后赋值给指针,才能够使用.

创建指针类型的几种方式

  1. 创建正常的对象数据结构,使用 & 取对象指针地址,在指针类型上,使用 * 取值
  2. Golang 中的函数调用都是值传递,使用指针类型才能进行地址传递
  3. 在创建对象指针时,还可以使用 new 方法直接进行创建,返回创建对象的指针
  4. 特别需要注意的是,基础数据类型中的 map 结构的初始化并没有分配内存地址,list 结构的初始化有分配内存地址
  5. 所以对于 map 类型初始化的之后需要分配地址才能使用,或者使用字面量初始化并分配地址的方式 map[string]string{}
  6. 可以使用 make 创建 map 或者 list 类型数据结构,返回的是数据值而非数据指针。
package main

import "fmt"

type Phone struct {
	Brand string
}

func first() {
	var iphone Phone
	iphone.Brand = "Apple"
	fmt.Printf("introduce my phohe to you:%+v\n", iphone)

	miPhone := Phone{}
	miPhone.Brand = "mi"
	fmt.Printf("introduce another phohe to you:%+v\n", miPhone)

	var TempList []string
	TempList = append(TempList, "phone")
	fmt.Printf("temp list is:%+v\n", TempList)

	var TempInt int
	fmt.Printf("init int is:%+v\n", TempInt)

}

func main() {
	var iphone *Phone
	// panic: runtime error: invalid memory address or nil pointer dereference
	// iphone.Brand = "Apple"
	fmt.Printf("pointer not init:%+v\n", iphone)
	iphone = &Phone{}
	iphone.Brand = "Apple"
	fmt.Printf("introduce my phohe to you:%+v\n", iphone)

	miPhone := &Phone{}
	miPhone.Brand = "mi"
	fmt.Printf("introduce another phohe to you:%+v\n", miPhone)

	oPhone := new(Phone)
	oPhone.Brand = "OPPO"
	fmt.Printf("introduce Find X3 to you:%+v\n", oPhone)

	var TempList *[]string
	TempList = &[]string{}
	*TempList = append(*TempList, "phone")
	fmt.Printf("temp list is:%+v\n", TempList)

	var TempMap map[string]string
	fmt.Printf("map init with nil:%+v\n", TempMap)
	// panic: assignment to entry in nil map
	// TempMap["phone"] = "iphone"

	//TempMap = map[string]string{}
	TempMap = make(map[string]string)
	TempMap["phone"] = "iphone"
	fmt.Printf("temp map is:%+v\n", TempMap)

	OtherMap := map[string]string{}
	OtherMap["phone"] = "iphone"
	fmt.Printf("map value is:%+v\n", OtherMap)
}

好,以上只是一些基础知识都介绍完了,接下来要开始本文的重点部分。

判空

判空,意如其名,如何判断一个对象是否为空值,或者为空指针。

  1. 如果是值类型,就直接判断是否为初始值即可。
  2. 如果是指针类型,就判断是否为 nil

很快吖,就可以写出这样的两个方法

package main

import (
	"fmt"
	"log"
)

func checkValueEmpty(o interface{}) bool {
	switch o.(type) {
	case int:
		return o == 0
	case string:
		return o == ""
	case []interface{}:
		return len(o.([]interface{})) == 0
	case map[interface{}]interface{}:
		return len(o.(map[interface{}]interface{})) == 0
	default:
		log.Printf("can't detect o type:%#v\n", o)
		return false
	}
}

func checkPointerNil(o interface{}) bool {
	return o == nil
}

func main() {
	mess := []interface{}{0, 1, 2, "", "hello", map[string]string{}, []int{}, []interface{}{}}
	for _, m := range mess {
		fmt.Printf("%#v is zero:%+v\n", m, checkValueEmpty(m))
	}
	for _, m := range mess {
		fmt.Printf("%#v is nil:%+v\n", m, checkPointerNil(m))
	}
}

看起来都很完美,但是遥远的天边飘着两朵乌云

  1. interface 的零值真的未 nil 么?
  2. 如何判断是值类型还是指针类型呢?

在解决这个问题之前,我们先思考一个问题🤔,如果 interface 可以代表任意类型,那么它可以代表指针类型么?*interface{} 可以代表指针的指针类型么?

答案是可以的

package main

import "fmt"

func main() {
	var o interface{}
	var op *interface{}
	var i *int
	var j *int
	var s *string
	fmt.Printf("o:%#v is nil?:%#v\n", o, o == nil)
	fmt.Printf("op:%#v is nil?:%#v\n", op, op == nil)
	fmt.Printf("i:%#v is nil?:%#v\n", i, i == nil)
	fmt.Printf("j:%#v is nil?:%#v\n", j, j == nil)
	fmt.Printf("s:%#v is nil?:%#v\n", s, s == nil)
	i = j
	fmt.Printf("i:%#v is nil?:%#v\n", i, i == nil)

	o = op
	fmt.Printf("o:%#v is nil?:%#v\n", o, o == nil)
	o = i
	fmt.Printf("o:%#v is nil?:%#v\n", o, o == nil)
	o = s
	fmt.Printf("o:%#v is nil?:%#v\n", o, o == nil)
	o = (*int)(nil)
	fmt.Printf("o:%#v is nil?:%#v\n", o, o == nil)

	// panic: runtime error: invalid memory address or nil pointer dereference
	// 指针未初始化,无法重新赋值
	// *op = &i
	// fmt.Printf("op:%#v is nil?:%#v\n", op, op == nil)

}


输出结果是

o:<nil> is nil?:true
op:(*interface {})(nil) is nil?:true
i:(*int)(nil) is nil?:true
j:(*int)(nil) is nil?:true
s:(*string)(nil) is nil?:true
i:(*int)(nil) is nil?:true
o:(*interface {})(nil) is nil?:false
o:(*int)(nil) is nil?:false
o:(*string)(nil) is nil?:false
o:(*int)(nil) is nil?:false

为什么 interface 明明都是 nil ,但是最后四个的判断却是为 false 呢?

因为 interface 实际包含两个值,type 和 value;需要 type 和 value 都为 nil 才会与 nil 相等。

注意,这里的 interface 只代表 interface 类型,也就是说其他的指针类型判断还是只校验 nil,当 interface 的指针校验就要判断 type 和 value 。

所以当对于 interface 的指针空值判断的时候,我们只需要使用反射取到其值的内容,然后判断是否为 nil 就可以了。

package main

import (
	"fmt"
	"reflect"
)


func isNil(o interface{}) bool {
	vi := reflect.ValueOf(o)
	if vi.Kind() == reflect.Ptr {
		fmt.Printf("vi kind is pointer\n")
		return vi.IsNil()
	}
	return false
}

func main() {
	var o interface{}
	var op *interface{}
	var i *int
	var j *int
	var s *string
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNil(o))
	fmt.Printf("op:%#v is nil?:%#v\n", op, isNil(op))
	fmt.Printf("i:%#v is nil?:%#v\n", i, isNil(i))
	fmt.Printf("j:%#v is nil?:%#v\n", j, isNil(j))
	fmt.Printf("s:%#v is nil?:%#v\n", s, isNil(s))
	i = j
	fmt.Printf("i:%#v is nil?:%#v\n", i, isNil(i))

	o = op
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNil(o))
	o = i
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNil(o))
	o = s
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNil(o))
	o = (*int)(nil)
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNil(o))

	var tempList []interface{}
	var tempMap map[interface{}]interface{}

	fmt.Printf("tempList:%#v is nil?:%#v\n", tempList, isNil(tempList))
	fmt.Printf("tempMap:%#v is nil?:%#v\n", tempMap, isNil(tempMap))
	fmt.Printf("tempList:%#v is nil?:%#v\n", tempList, tempList == nil)
	fmt.Printf("tempMap:%#v is nil?:%#v\n", tempMap, tempMap == nil)

	var tempListPointer *[]interface{}
	var tempMapPointer *map[interface{}]interface{}

	fmt.Printf("tempListPointer:%#v is nil?:%#v\n", tempListPointer, isNil(tempListPointer))
	fmt.Printf("tempMapPointer:%#v is nil?:%#v\n", tempMapPointer, isNil(tempMapPointer))

}

结果是

o:<nil> is nil?:false
vi kind is pointer
op:(*interface {})(nil) is nil?:true
vi kind is pointer
i:(*int)(nil) is nil?:true
vi kind is pointer
j:(*int)(nil) is nil?:true
vi kind is pointer
s:(*string)(nil) is nil?:true
vi kind is pointer
i:(*int)(nil) is nil?:true
vi kind is pointer
o:(*interface {})(nil) is nil?:true
vi kind is pointer
o:(*int)(nil) is nil?:true
vi kind is pointer
o:(*string)(nil) is nil?:true
vi kind is pointer
o:(*int)(nil) is nil?:true
tempList:[]interface {}(nil) is nil?:false
tempMap:map[interface {}]interface {}(nil) is nil?:false
tempList:[]interface {}(nil) is nil?:true
tempMap:map[interface {}]interface {}(nil) is nil?:true
vi kind is pointer
tempListPointer:(*[]interface {})(nil) is nil?:true
vi kind is pointer
tempMapPointer:(*map[interface {}]interface {})(nil) is nil?:true

对于指针类型的判 nil 是准确的,但是对于值的判断有问题。

那我们对于判断 empty 和判断 nil 的两种判空场景都可以实现了,如何判断一个对象是指针还是值呢?

😂,这个不太好判断

不过我们可以根据 reflect 库中的 IsZero 方法进行一个差不多的预判

package main

import (
	"fmt"
	"reflect"
)

func isNilOrEmpty(o interface{}) bool {
	vi := reflect.ValueOf(o)
	// 对 interface 的特例
	if o == nil {
		return true
	}
	return vi.IsZero()
}

func main() {
	mess := []interface{}{0, 1, 2, "", "hello", map[string]string{}, []int{}, []interface{}{}}
	for _, m := range mess {
		fmt.Printf("%#v is empty:%+v, is nil:%+v\n", m, isNilOrEmpty(m), m == nil)
	}

	var o interface{}
	var op *interface{}
	var i *int
	var j *int
	var s *string
	//fmt.Printf("o:%#v is nil?:%#v\n", o, isNilOrEmpty(o))
	fmt.Printf("op:%#v is nil?:%#v\n", op, isNilOrEmpty(op))
	fmt.Printf("i:%#v is nil?:%#v\n", i, isNilOrEmpty(i))
	fmt.Printf("j:%#v is nil?:%#v\n", j, isNilOrEmpty(j))
	fmt.Printf("s:%#v is nil?:%#v\n", s, isNilOrEmpty(s))
	i = j
	fmt.Printf("i:%#v is nil?:%#v\n", i, isNilOrEmpty(i))

	o = op
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNilOrEmpty(o))
	o = i
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNilOrEmpty(o))
	o = s
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNilOrEmpty(o))
	o = (*int)(nil)
	fmt.Printf("o:%#v is nil?:%#v\n", o, isNilOrEmpty(o))

	var tempList []interface{}
	var tempMap map[interface{}]interface{}

	fmt.Printf("tempList:%#v is nil?:%#v\n", tempList, isNilOrEmpty(tempList))
	fmt.Printf("tempMap:%#v is nil?:%#v\n", tempMap, isNilOrEmpty(tempMap))

	fmt.Printf("tempList:%#v is nil?:%#v\n", tempList, tempList == nil)
	fmt.Printf("tempMap:%#v is nil?:%#v\n", tempMap, tempMap == nil)

	var tempListPointer *[]interface{}
	var tempMapPointer *map[interface{}]interface{}

	fmt.Printf("tempListPointer:%#v is nil?:%#v\n", tempListPointer, isNilOrEmpty(tempListPointer))
	fmt.Printf("tempMapPointer:%#v is nil?:%#v\n", tempMapPointer, isNilOrEmpty(tempMapPointer))

}

其实有几点需要注意

  1. listmap 初始化其实都是 nil, 但是 list 会自动扩容,map 不会。
  2. listmap 在外面都是 nil 但是传入函数判断时就变成不是 nil 了。
  3. 所有的 nil 对象都不能被取类型 reflect.TypeOf
  4. interfacenil 在取值 reflect.ValueOf 得到的结果类型 KindInvalid ,不能判断零值。

真的是,不能深究,深究就有点乱。只需要记住两点

  1. nil 不一定是 nil, 如果是有类型的 nil 不能直接用等号判断, 为什么会出现有类型的 nil ,就是因为将一个指针的 nil 传给了 interface
  2. interface 比较特殊有类型和值,如果值是指针的话,那就直接判断指针为 nil 就可以了。

参考链接

Go 面试题:Go interface 的一个 “坑” 及原理分析

❌
❌