2021年9月

PHP 仍然是统治服务器端的编程语言

根据 W3Techs 今天发布的一份报告,PHP 在 2010 年的市场份额为 72.5%,而在 2021 年占比达到 78.9%。只有一个其他的服务器端语言曾经突破 10% 的份额,这个竞争对手是 ASP.NET,它在 2010 年拥有令人印象深刻的 24.4% 的份额,但在 1 月份下降到 9.3%,这个月下降到 8.3%。本次调查涉及热门网站,而非仅限于一些精英网站,并且在调查中避免来自域名停放服务和垃圾邮件发送者的数据歪曲,尽量契合当前的互联网现实。

PHP 确实是最好的语言!

用户可以从微软账户中彻底删除密码了

微软表示密码机制本身已经成为安全软肋,每年遭受的相关攻击多达 180 亿次。从今天开始,用户可以将微软账户中的密码彻底删除了。当然,在步入无密码时代之前,你需要在自己的智能手机上安装微软验证器应用,并与你账户连接。之后根据实际使用位置的不同,通过安全密钥,或者是通过邮件、手机或 Outlook、OneDrive 等其他兼容性应用或服务接收到的验证码进行登录。此前微软在 2018 年启用了安全密钥,并在 2019 年成功使 Windows 10 实现无密码登录。超过 2 亿的商业客户已经在使用无密码登录。

不知不觉中,大家习以为常的密码其实已经是落后的、可淘汰的安全缺陷了。

Azure 默认 Linux 配置曝出严重的远程代码执行漏洞

安全研究专家指出,他们在诸多流行的 Azure 服务中,发现了开放管理基础设施(OMI)软件代理中存在的一系列严重漏洞。问题在于当 Azure 客户在云端设置 Linux 虚拟机服务时,OMI 代理会在他们不知情的情况下自动部署。更糟糕的是,黑客只需发送一个剔除了身份验证标头的数据包,即可渗透并取得远程机器上的 root 访问权限。微软已经发布了补丁,同时建议客户手动执行更新。据推测 Azure 上多达 65% 的 Linux 部署都受到影响。

最讨厌云服务商偷偷往系统镜像里面塞东西,而且这些软件还经常有验证漏洞。

听说,你已经开始学习 Java 编程了?很好。

你想在你的 Linux 系统上运行 Java 程序?那就更好了。

让我告诉你如何在 Ubuntu 和其他 Linux 发行版的终端中运行 Java。

在 Ubuntu 中运行 Java 程序

让我们在这里按正确的步骤进行。

第一步:安装 Java 编译器

要运行一个 Java 程序,你需要先编译该程序。为此你需要 Java 编译器。

Java 编译器是 JDK Java 开发工具包 Java Development Kit )的一部分。你需要安装 JDK,以便编译和运行 Java 程序。

首先,检查你的系统上是否已经安装了 Java 编译器:

javac --version

如果你看到类似 “Command ‘javac’ not found, but can be installed with” 的错误,这意味着你需要安装 Java 开发工具包。

Check if Java compiler is already installed or not

在 Ubuntu 上安装 JDK 的最简单方法是使用 Ubuntu 的默认包:

sudo apt install default-jdk

你会被要求输入你的账户密码。当你输入密码时,屏幕上什么也看不到。这很正常。直接输入密码即可。当询问时,按回车键或 Y 键。

Installing JDK that also contains the Java compiler

上述命令应该适用于其他基于 Debian 和 Ubuntu 的发行版,如 Linux Mint、Elementary OS 等。对于其他发行版,请使用你的发行版的包管理器。包的名称也可能不同。

安装完毕后,验证 javac 现在是否可用。

Verify that Java compiler can be used now

第二步:在 Linux 中编译 Java 程序

要编译的话,你首先需要有一个 Java 程序文件。假设你创建了一个名为 HelloWorld.java 的新的 Java 程序文件,它的内容如下:

class HelloWorld{
    public static void main(String args[]){
     System.out.println("Hello World");
    }
}

你可以 使用终端下的 Nano 编辑器 或 Gedit 图形化文本编辑器来编写你的 Java 程序。

javac HelloWorld.java

如果没有错误,上面的命令不会产生输出。

当你编译 Java 程序时,它会生成一个 .class 文件,文件名是你在程序中使用的类。你需要运行这个类文件。

第三步:运行 Java 类文件

你不需要在这里指定类的扩展名。只需要类的名称。而这一次,你使用 java 命令,而不是 javac

java HelloWorld

我的程序将在屏幕上打印 “Hello World”。

Running java programs in the Linux terminal

这就是你如何在 Linux 终端中运行一个 Java 程序。

这是最简单的一个例子。这个示例程序只有一个类。Java 编译器为你程序中的每个类都创建一个类文件。对于较大的程序和项目来说,事情会变得很复杂。

这就是为什么我建议 在 Ubuntu 上安装 Eclipse 来进行 Java 编程。在 IDE 中编程更容易。

希望本教程对你有所帮助。有问题或建议吗?评论区都是你的。


via: https://itsfoss.com/run-java-program-ubuntu/

作者:Abhishek Prakash 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

安装 Debian 的轻松程度依赖于选择什么镜像。

如果你使用 Debain 官网的默认 ISO 镜像,安装 Debian 就比较费劲。你会卡在这种界面,让你从外部可移动介质上安装网络驱动。

对于新用户来说,从默认的 ISO 中安装 Debian 是有问题的

当然你可以花时间去排除这个故障,但这让事情变得没有必要的复杂。

不用担心,让我来展示如何轻松地简单安装 Debian。

轻松安装 Debian 桌面系统的方法

在你查看这些步骤之前,请确认以下准备工作:

  • 一个至少 4GB 大小的 USB 盘。
  • 一个连接了互联网的系统(可以是要安装 Debian 的同一个机器)。
  • 一个要安装的 Debian 的机器。它将会清理掉系统上所有数据,因此请复制重要数据到其他外部磁盘

你需要为 Debian 准备什么样配置的机器?这取决于你想用什么类型的 桌面环境。例如,GNOME 桌面系统可以在 4GB 内存上运行,但在 8GB 内存上更流畅一些。如果你只有 4GB 或更少的内存,还是建议尝试 KDE、Cinnamon 或 Xfce 桌面系统。

Debian 支持 32 位和 64 位的指令架构。你需要根据你的 CPU 指令架构选择对应的 Debian ISO 镜像。

你的系统应该至少要有 25GB 可用的硬盘空间。越多越好。

警告!

这个方法会移除磁盘上所有其他操作系统及其数据。

你可以保存你后面还需要用的个人信息、文档、照片等到外部 USB 盘或云存储中。

在这个教程中,我将展示安装带有 GNOME 桌面环境的 Debian 11 Bullseye 的步骤。即使你选择其他的桌面环境,步骤也应该是一样的。

这个教程是在 GPT 分区的 UEFI 系统上测试的。如果你的系统是 MBR 而不是 GPT,或是 传统的 BIOS 而不是 UEFI,那么创建 临场 Live USB 盘的步骤有一点不同。

步骤 1:获取正确的 Debian ISO 镜像

在安装 Debian 过程中,选择正确的 ISO 镜像就已经成功一半了。令人惊讶的是,对于一个新的 Debian 用户来说,要浏览 Debian 的网站并找到最轻松好用的 ISO 真的很困难。

如果你点击 Debian 官网的下载按钮,它会下载一个最小化的网络安装文件,这对普通用户来说是非常复杂的。请 不要 使用这个。

反而,你应该用 临场 Live ISO。但这里要注意,有一些单独的含有非自由软件(以包括网络硬件的驱动程序)的版本。

你应该下载这个非自由版的临场 ISO 镜像。不过另一个问题是,你不会在网站的显著位置注意到它,而且有各种架构的 BT 种子或直接下载的 URL。

让我把这些链接放在这里:

你会看到几个文件,文件名中提到了桌面环境。选择一种你要的桌面环境。直接下载的话,直接点击 .iso 结尾的链接即可。

下载非自由版的临场 Debian ISO

一旦你有了对应的 ISO 下载包,剩下就是和其他 Linux 发行版一样的标准安装流程了。

步骤 2: 创建一个 Debian 的临场 USB 盘

将 USB 盘插入你的系统。在用之前最好格式化一下,反正它最终也会被格式化的。

你可以根据你的选择使用任何临场 USB 创建工具。如果你使用 Windows,可以使用 Rufus。我们在这里使用 Etcher,因为这个工具在 Windows 和 Linux 都可以用。

从它的官网下载 Etcher。

我专门写过一篇 在 Linux 下使用 Etcher 的教程,因此这里我就不深入介绍了。只要运行下载的可执行程序,浏览找到 Debian 的 ISO 镜像,确认选择正确的 USB 盘,然后点击 “Flash” 按钮即可。

用 Etcher 创建 Debian 的临场 USB 盘

不一会就创建好临场 USB 盘了。创建好之后,就可以开机引导了。

步骤 3:从临场 USB 盘引导启动

重启你要安装 Debian 的机器。当显示制造商标识的时候,按下 F2F10F12 等键进入开机引导设置界面。你也可以从 Windows 进入到 UEFI 固件设置界面

有些机器如果启用了 安全启动 secure boot 功能,就不允许从临场 USB 盘引导。如果是这种情况,请 从 BIOS 设置里禁用安全启动

不同的的制造商在界面上会有一些差异。

你在 BIOS 里做了修改之后,按下 F10 保存并退出。你的系统将会重新启动。

再一次,当看到制造商的标识后按下 F2F10F12 查看引导配置。你应该可以看到从 USB 引导的选项,然后选中它。

一会儿就会看到如下图的显示界面,选择第一个选项。

Debian 启动界面

步骤 4: 开始安装 Debian

当你进入临场 Debian 会话,如果你使用 GNONE 桌面,它呈现一个欢迎界面,可以在此选择你的键盘和语言。当你看到这些界面时,只需要点击下一步。

Debian 临场欢迎界面

欢迎界面之后,按下 Windows / Super 键进入活动区。你应该可以看到 Debian 的安装按钮。

开始安装 Debian

它会打开一个友好的 Calamares 图形安装器。从这里开始事情就比较简单了,

Debian 11 Calamares 图形安装器

它会让你选择你的地理位置和时区。

选择你的地理位置和时区

下一个界面,会让你选择键盘类型。这儿请 注意。你的键盘会根据你所选的位置自动选择。例如,我的位置是印度,它会自动默认选择印度键盘和印地语。我需要将其改为印度英语。

选择键盘类型

下一个界面是关于磁盘分区和要安装 Debian 的地方。在本文中,把 Debian 作为你电脑上唯一的操作系统来安装。

最简单的方法是直接选择 “ 擦除磁盘 Erase Disk ”。除了必须的 ESP 分区和交换分区外,Debian 会把其他所有东西都放在根挂载点(/)上。实际上,下面显示了你所选的安装方式后的磁盘布局。

磁盘分区

如果你想把事情掌握在自己手中,你也可以选择手动分区,选择分配给 //home/boot 或交换分区的大小。只有在你知道自己在做什么时,才可以这样做。

下一界面,你需要提供用户名和密码。但它不会设置 root 的密码,并将其保持为空。

设置用户名和密码

这也意味着你可以用新创建的用户使用 sudo 。在“复杂的 Debian 安装”中,你也可以设置 root 密码,但这样你就必须手动将普通用户添加到 sudoer 列表。看看,这种安装过程是不是对新手来说很容易?

在继续实际安装之前,它会呈现你所做的选择的汇总信息。如果没有问题,就可以点击“ 安装 Install ”按钮。

安装配置的汇总信息

现在只需要等待安装完成。

安装 Debian

几分钟后就会完成安装。当安装完成,它会提示重启。

完成 Debian 安装

重启系统后如果一切顺利,你应该可以看到 Debian 的 GRUB 界面。

Debian 启动画面

疑难解答(如果系统没有启动到 Debian)

我遇到情况是,我的 Dell 系统不能识别任何要引导的操作系统。这很奇怪,我看见 Debian 经创建了一个 ESP 分区。

如果你也是同样的情况,进去 BIOS 配置里。检查 启动顺序 Boot Sequence ,如果你看不到任何东西,就点击“ 新增引导选项 Add Boot Option ”。

增加新的启动选项

它会提供一个增加 EFI 文件的选项。

选择 EFI 文件

由于在安装过程中 Debian 创建了 ESP 分区,因此一个包含必要文件的 EFI 目录已经创建好了。

选择 EFI 目录

它会显示一个 Debian 目录及其他目录。选择 Debian 目录。

选择 Debian 目录

Debian 目录,你将看到 grubx64.efishimx64.efi 等文件。请选择 shimx64.efi

选择 shimx64.efi

你需要给这个文件一个合适的名字。最后的界面应该如下:

增加 efi 文件的新启动选项

现在你应该有了这个启动选项。因为我命名为 Debian,它显示了两个 Debian 引导选项(我猜其中一个是从 efi 文件来的)。按下 F10 保存退出 BIOS 的配置。

新增的启动选项

现在启动你的系统,你可以看到带有 Debian 启动选项的 GRUB 界面了。你现在可以体验 Debian 了。

你能安装 Debian 吗?

我写这篇文章的目的是让事情变得轻松点。并不是说你不能从默认的网络安装程序 ISO 来安装,只是它需要花更多的精力。

这个教程对你安装 Debian 有帮助吗?你如果还是有问题,请在下面留言给我,我会尽力提供帮助。


via: https://itsfoss.com/install-debian-easily/

作者:Abhishek Prakash 选题:lujun9972 译者:巴龙 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

树莓派将进入太空进行 Python 编程挑战

这不是树莓派第一次进入太空,早在 2015 年,欧洲航天局的宇航员就将一块树莓派带入了太空。欧洲航天局今年的“零号任务”中,邀请人们编写一个 Python 程序,在国际空间站上读取湿度,并向宇航员显示个性化的信息。“空间实验室任务”则让团队在国际空间站上的 Astro Pi 单元上进行科学实验。欧洲航天局的树莓派 Astro Pi 是树莓派 4B,有 8GB 内存,带有谷歌的 Coral 机器学习加速器,以及测量湿度、温度和压力的传感器。此外,还有一个用于视觉反馈的 LED 矩阵。它将于今年 12 月被运送到太空并投入使用。

能编写运行在太空的程序,虽然只是实验挑战,但是也很有趣啊!

科学家们现在可以在几分钟内用个人电脑组装整个基因组

研究人员已经开发出一种在个人电脑上重建全基因组的技术,包括人类基因组。这项技术比目前最先进的方法快一百倍,并且使用五分之一的资源。它对基因组数据进行更紧凑的表示,其灵感来自于采用单词而不是字母而为语言模型提供浓缩构建块的方式。这种新技术可以在 13 分钟内搜索整个抗菌素抗性基因集合,而使用标准序列排列则需要 7 小时。

虽然搞不懂,但是感觉很厉害。

Java 17 发布 GA 版本,Java 小程序 API 被废弃

Java 17 将是一个长期支持版本,带来了大量改进。在其中,值得注意的是,实验性的提前(AOT)和及时(JIT)编译器由于进展甚微而被删除,而 Java 级的 JVM 编译器接口(JVMCI)接口仍将为外部构建的编译器版本所保留。同时,鉴于大多数 Web 浏览器已经清除了对 Java 小程序(Applet)的支持,Java 小程序 API 在进一步删除前先将其弃用。

Java 小程序落幕了,可能很多人都不知道它吧。

有很多的图形化工具可以用来创建 临场 live USB 驱动器。Linux 上的 Etcher 可能是最受欢迎的。为此,Ubuntu 也开发了自己的启动盘创建工具。

但是,资深 Linux 用户可能更喜欢使用 dd 命令在 Linux 终端中创建临场 USB,这会更快速便捷。

dd 命令是一个 命令行 工具,它提供了用来复制和转换文件的强大功能。

一个常见的使用示例是,用户使用 dd 命令将 ISO 文件写入到他们的外部存储设备(例如 USB 驱动盘),以用来给他们的电脑或者笔记本安装一个新的 Linux 发行版。

这就是我将在本教程中展示的内容。我将带你认识需要的命令,从终端找到我们的 USB 驱动器,然后对 ISO 文件进行实际刷写。

使用 dd 命令从 ISO 镜像创建临场 USB

在我向你展示步骤前,让我带你快速过一下你将要使用到的命令并解释它的作用。

这是一个使用命令刷写 ISO 的例子:

dd if="./filename.iso" of="/dev/sdb" status="progress" conv="fsync"

让我们来看看 dd 命令 实际都做了些什么。

理解 dd 命令

Explanation of the dd command for live USB creation

首先,你输入 dd。没错,这就是你要运行的程序的名称。

接下来,你指定 if="./filename.iso"if 代表 输入文件 input file ,告诉 dd 命令你将要向外部存储设备写入哪个文件。

之后,你输入 of="/dev/sdb"。和 if 一样,of 代表的是 输出文件 output file

要记住的是,输出文件在技术上不必是系统上的文件。你还可以指定诸如外部设备路径之类的内容(如示例所示),它看起来像系统上的普通文件,但实际上指向连接到你机器的设备。

status 可以设定为 3 个选项:nonenoxferprogress

  • 你设置的 progress 选项将使 dd 任务显示有关已将多少 ISO 文件传输到存储驱动器的定期统计信息,以及对 dd 任务完成前需要多长时间的估计。
  • 如果你改为设置 none 选项,dd 任务在写入 ISO 文件期间只会打印错误消息,并且删除进度条之类的内容。
  • noxfer 选项隐藏了传输完成后打印的一些信息,例如从开始到完成所用的时间。

最后,你将 conv 选项设置为 fsync。这会导致 dd 任务在整个 ISO 文件写入 USB 驱动器之前不会报告成功写入。

如果你省略这个选项,dd 任务会工作的很好(并且实际上可能看起来运行得更快),但你可能会发现你的系统需要很长时间才能告诉你移除 USB 驱动器是安全的,因为它会在后台完成 ISO 的内容写入,从而允许你在此期间做其它事情。

现在你明白了你必须做什么,让我们看看如何去做。

注意事项

命令行是把双刃剑。当你在命令行使用类似于 dd 命令时必须十分小心。你必须确保你目标输出文件是正确的设备。一个错误的步骤就可能会格式化你的系统硬盘,你的操作系统也会因此而损坏。

第 0 步: 下载所需的 ISO 镜像

不用说,你需要有一个 ISO 镜像文件才能将其刷写到 USB 上。

我将使用 Ubuntu 20.04 ISO(可在此处下载)来测试我之前介绍的 dd 命令。

第 1 步: 获取 USB 盘符

插入你的 USB 驱动器。

我为 of 参数输入的具体路径是 /dev/sdb。USB 磁盘通常会标记为 /dev/sdb,但这不是硬性规定。

此路径可能因你的系统而异,你可以使用 lsblk 命令确认 USB 磁盘的路径。只需从列表中查找一个看起来像你的 USB 磁盘大小的驱动器,就可以了。

如果你更熟悉 GUI 程序,还可以使用 GNOME Disks 等工具找到驱动器的路径。

现在你已经确认了外部驱动器的路径,让我们开始创建临场 USB。

第 2 步:将 ISO 文件写入 USB 磁盘

在下载 ISO 文件的目录打开一个终端,然后运行以下命令(如果 /dev/sdb 与你的存储设备名称不同,请记住将其替换):

sudo dd if="./ubuntu-20.04.2.0-desktop-amd64.iso" of="/dev/sdb" status="progress" conv="fsync"

之后,让 dd 去做剩下的事情,它会在完成后打印一条完成消息:

就像这样,你已经在 Linux 终端中使用 dd 命令刷写了 ISO 文件!

总结

现在,你可以通过终端做更多的事情,让你的工作效率大大提高。

dd 命令有任何没解决的问题,或者无法正常工作?请随时在下面的评论部分中留下你的问题。


via: https://itsfoss.com/live-usb-with-dd-command/

作者:Hunter Wittenborn 选题:lujun9972 译者:perfiffer 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

大家好!昨天我与一位朋友聊天,他正在准备编程面试,并试图学习一些算法基础知识。

我们聊到了 二次时间 quadratic-time 线性时间 linear-time 算法的话题,我认为在这里写这篇文章会很有趣,因为避免二次时间算法不仅在面试中很重要——有时在现实生活中了解一下也是很好的!后面我会快速解释一下什么是“二次时间算法” :)

以下是我们将要讨论的 3 件事:

  1. 二次时间函数比线性时间函数慢得非常非常多
  2. 有时可以通过使用 hashmap 把二次算法变成线性算法
  3. 这是因为 hashmap 查找非常快(即时查询!)

我会尽量避免使用数学术语,重点关注真实的代码示例以及它们到底有多快/多慢。

目标问题:取两个列表的交集

我们来讨论一个简单的面试式问题:获取 2 个数字列表的交集。 例如,intersect([1,2,3], [2,4,5]) 应该返回 [2]

这个问题也是有些现实应用的——你可以假设有一个真实程序,其需求正是取两个 ID 列表的交集。

“显而易见”的解决方案:

我们来写一些获取 2 个列表交集的代码。下面是一个实现此需求的程序,命名为 quadratic.py

import sys

# 实际运行的代码
def intersection(list1, list2):
    result = []
    for x in list1:
        for y in list2:
            if x == y:
                result.append(y)
    return result

# 一些样板,便于我们从命令行运行程序,处理不同大小的列表
def run(n):
    # 定义两个有 n+1 个元素的列表
    list1 = list(range(3, n)) + [2]
    list2 = list(range(n+1, 2*n)) + [2]
    # 取其交集并输出结果
    print(list(intersection(list1, list2)))

# 使用第一个命令行参数作为输入,运行程序
run(int(sys.argv[1]))

程序名为 quadratic.py(LCTT 译注:“quadratic”意为“二次方的”)的原因是:如果 list1list2 的大小为 n,那么内层循环(if x == y)会运行 n^2 次。在数学中,像 x^2 这样的函数就称为“二次”函数。

quadratic.py 有多慢?

用一些不同长度的列表来运行这个程序,两个列表的交集总是相同的:[2]

$ time python3 quadratic.py 10
[2]

real    0m0.037s
$ time python3 quadratic.py 100
[2]

real    0m0.053s
$ time python3 quadratic.py 1000
[2]

real    0m0.051s
$ time python3 quadratic.py 10000 # 10,000
[2]

real    0m1.661s

到目前为止,一切都还不错——程序仍然只花费不到 2 秒的时间。

然后运行该程序处理两个包含 100,000 个元素的列表,我不得不等待了很长时间。结果如下:

$ time python3 quadratic.py 100000 # 100,000
[2]

real    2m41.059s

这可以说相当慢了!总共花费了 160 秒,几乎是在 10,000 个元素上运行时(1.6 秒)的 100 倍。所以我们可以看到,在某个点之后,每次我们将列表扩大 10 倍,程序运行的时间就会增加大约 100 倍。

我没有尝试在 1,000,000 个元素上运行这个程序,因为我知道它会花费又 100 倍的时间——可能大约需要 3 个小时。我没时间这样做!

你现在大概明白了为什么二次时间算法会成为一个问题——即使是这个非常简单的程序也会很快变得非常缓慢。

快速版:linear.py

好,接下来我们编写一个快速版的程序。我先给你看看程序的样子,然后再分析。

import sys

# 实际执行的算法
def intersection(list1, list2):
    set1 = set(list1) # this is a hash set
    result = []
    for y in list2:
        if y in set1:
            result.append(y)
    return result

# 一些样板,便于我们从命令行运行程序,处理不同大小的列表
def run(n):
    # 定义两个有 n+1 个元素的列表
    list1 = range(3, n) + [2]
    list2 = range(n+1, 2*n) + [2]
    # 输出交集结果
    print(intersection(list1, list2))

run(int(sys.argv[1]))

(这不是最惯用的 Python 使用方式,但我想在尽量避免使用太多 Python 思想的前提下编写代码,以便不了解 Python 的人能够更容易理解)

这里我们做了两件与慢速版程序不同的事:

  1. list1 转换成名为 set1 的 set 集合
  2. 只使用一个 for 循环而不是两个

看看 linear.py 程序有多快

在讨论 为什么 这个程序快之前,我们先在一些大型列表上运行该程序,以此证明它确实是很快的。此处演示该程序依次在大小为 10 到 10,000,000 的列表上运行的过程。(请记住,我们上一个的程序在 100,000 个元素上运行时开始变得非常非常慢)

$ time python3 linear.py 100
[2]

real    0m0.056s
$ time python3 linear.py 1000
[2]

real    0m0.036s
$ time python3 linear.py 10000 # 10,000
[2]

real    0m0.028s
$ time python3 linear.py 100000 # 100,000
[2]

real    0m0.048s <-- quadratic.py took 2 minutes in this case! we're doing it in 0.04 seconds now!!! so fast!
$ time python3 linear.py 1000000 # 1,000,000
[2]

real    0m0.178s
$ time python3 linear.py 10000000 # 10,000,000
[2]

real    0m1.560s

在极大型列表上运行 linear.py

如果我们试着在一个非常非常大的列表(100 亿 / 10,000,000,000 个元素)上运行它,那么实际上会遇到另一个问题:它足够 了(该列表仅比花费 4.2 秒的列表大 100 倍,因此我们大概应该能在不超过 420 秒的时间内完成),但我的计算机没有足够的内存来存储列表的所有元素,因此程序在运行结束之前崩溃了。

$ time python3 linear.py 10000000000
Traceback (most recent call last):
  File "/home/bork/work/homepage/linear.py", line 18, in <module>
    run(int(sys.argv[1]))
  File "/home/bork/work/homepage/linear.py", line 13, in run
    list1 = [1] * n + [2]
MemoryError

real    0m0.090s
user    0m0.034s
sys 0m0.018s

不过本文不讨论内存使用,所以我们可以忽略这个问题。

那么,为什么 linear.py 很快呢?

现在我将试着解释为什么 linear.py 很快。

再看一下我们的代码:

def intersection(list1, list2):
    set1 = set(list1) # this is a hash set
    result = []
    for y in list2:
        if y in set1:
            result.append(y)
    return result

假设 list1list2 都是大约 10,000,000 个不同元素的列表,这样的元素数量可以说是很大了!

那么为什么它还能够运行得如此之快呢?因为 hashmap!!!

hashmap 查找是即时的(“常数级时间”)

我们看一下快速版程序中的 if 语句:

if y in set1:
    result.append(y)

你可能会认为如果 set1 包含 1000 万个元素,那么这个查找——if y in set1 会比 set1 包含 1000 个元素时慢。但事实并非如此!无论 set1 有多大,所需时间基本是相同的(超级快)。

这是因为 set1 是一个哈希集合,它是一种只有键没有值的 hashmap(hashtable)结构。

我不准备在本文中解释 为什么 hashmap 查找是即时的,但是神奇的 Vaidehi Joshi 的 basecs 系列中有关于 hash tablehash 函数 的解释,其中讨论了 hashmap 即时查找的原因。

不经意的二次方:现实中的二次算法!

二次时间算法真的很慢,我们看到的的这个问题实际上在现实中也会遇到——Nelson Elhage 有一个很棒的博客,名为 不经意的二次方,其中有关于不经意以二次时间算法运行代码导致性能问题的故事。

二次时间算法可能会“偷袭”你

关于二次时间算法的奇怪之处在于,当你在少量元素(如 1000)上运行它们时,它看起来并没有那么糟糕!没那么慢!但是如果你给它 1,000,000 个元素,它真的会花费几个小时去运行。

所以我认为它还是值得深入了解的,这样你就可以避免无意中使用二次时间算法,特别是当有一种简单的方法来编写线性时间算法(例如使用 hashmap)时。

总是让我感到一丝神奇的 hashmap

hashmap 当然不是魔法(你可以学习一下为什么 hashmap 查找是即时的!真的很酷!),但它总是让人 感觉 有点神奇,每次我在程序中使用 hashmap 来加速,都会使我感到开心 :)


via: https://jvns.ca/blog/2021/09/10/hashmaps-make-things-fast/

作者:Julia Evans 选题:lujun9972 译者:unigeorge 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出