什么是索引以及如何在Java中使用它
索引是一种便于在数据中进行搜索的结构。是一个非常通用的术语,不属于某些语言或数据库。为了解释它,让我们创建一种方法来搜索用户的特定条件,如姓名,ID,出生日期等。
在数百万个用户条目中快速搜索
假设我们有 100 万用户。作为一项要求,我们必须通过特定条件向搜索用户公开 API。显然,简单的迭代会花费太多时间。因此,让我们使用索引(或者只是通过预先生成一些在API调用期间加速查找的数据)来解决这个问题。因此,首先让我们定义我们的用户类和搜索界面:
仅使用默认 Java 集合
在我们的服务中,我们只使用默认的Java集合。如果您不熟悉它们并且不知道它们使用什么数据结构,那么阅读起来会有点困难。其中一个主要思想是仅使用经典的Java集合来快速创建索引。同时,创建自己的索引并不困难。
按 ID 搜索唯一用户
作为第一个实现的API,我们创建了索引,以便快速访问唯一用户。最简单的方法是将每个用户ID保留为键,并将用户保留为HashMap中的值。
平均而言,这样的解决方案将提供恒定的复杂性,这实际上是一个完美的案例。
按非唯一名称搜索用户
现在情况更加复杂。用户名不是唯一的,我们希望返回特定名称的用户列表。解决方案将是相同的 - 具有用户名的哈希映射和具有相同名称的用户列表。复杂性仍然相同 - 恒定
按年龄搜索用户(小于指定值)
现在,当请求的期限未知时,我们创建一个索引,并且我们无法为每个键创建一个列表(这将花费大量内存,例如1M列表)。因此,经典的搜索解决方案是基于树的索引。因此,对于我们的情况,我们使用 TreeSet 集合。树搜索过程将比恒定的慢,但仍然是可以接受的。
树集隐藏在二叉树中。此树构建树在传递给构造函数的比较器中定义。基本上,比较器只需要比较两个节点。如果比较器返回 0,则表示将删除其中一个节点。在我们的例子中,我们保留所有用户,所以实现将如下所示:
在我们获取年龄小于指定的所有用户之前,我们必须再次查看此树是如何构建的:
- 我们使用 age 来决定节点位置(如果比较器返回负数,则返回左数,如果为正数 - 右,如果为 0 则不添加节点)
- 在我们的例子中,所有用户都必须留在树中。因此,当年龄相同时,比较器应返回0并跳过一个用户。而不是它,我们通过id(这是唯一的)进行比较
为了澄清内部结构,让我们画一个可能的树:
因此,现在如果我们获取用户年龄小于14的用户,我们必须找到最“正确”的节点。从图像中,您可以意识到最“正确”是具有最大id(即整数.MAX)的那个。现在意识到这一点,我们可以使用TreeSet中的方法:headset
此方法将返回年龄小于 maxAge 且日志复杂性的所有用户。
按年龄搜索用户(日期之间)
几乎在前面显示的方式中,我们可以使用 TreeSet 来构建基于年龄的索引。在这种情况下,唯一的区别是如何获取日期之间的子集。与前面的示例一样,我们还需要使用整数最小值和最大值以及子集方法来选择大多数“左”和“右”节点:
更多示例
到目前为止,所展示的内容看起来非常明显。让我们想象一个更复杂的情况,当客户端需要获取名称以3个指定字符开头的所有用户时。当您开始在Google中输入时,您是否已经看到了这样的功能,并且它几乎可以立即向您显示结果?
如何实施?以同样的方式准备索引。
所以很快的要求是:
- 客户端请求 3 个字符,API 返回名称以它开头的所有用户
- API响应应该很快(好吧,没有固定的时间要求,而是意味着复杂性必须是恒定的,或者至少是对数的)
同样,使用HashMap,我们可以轻松创建此类索引。我们唯一需要的是使用每个名称的前3个字符并保存它们:
此示例与 getUsersByName 几乎完全相同,但我用它来揭示具有自动完成功能的已知功能是如何工作的。
没有Java集合的自定义索引值得吗?
简短的答案 - 不。默认集合是众所周知且可靠的。它们为您提供了恒定的和记录的复杂性。这绰绰有余,但在某些情况下,特别是对于实时应用程序,您可以编写自己的索引。
索引 缺点
索引带来性能,但您必须为此付费。在所有缺点中,主要有两个:
在我们的示例中,我们假设不会添加或删除任何用户。实际上,这样的数据更改可能代价高昂(并非所有索引,例如树或基于链接的结构在每次更新时都不会消耗太多时间和内存)。因此,您必须记住,在重新计算索引时,您的API将被搁置(或提供过时的数据)。