首页 > 爪哇 > java教程 > 正文

持久且不可变的 Java LinkedList

PHPz
发布: 2024-07-24 11:44:21
原创
436 人浏览过

Persistent and Immutable Java LinkedList

In diesem Artikel werden wir eine persistente und unveränderliche Variante der LinkedList in Java implementieren mit
teilweise strukturelle gemeinsame Nutzung für Zeit- und Raumeffizienzgewinne.

Einführung

Was ist eine LinkedList?

Eine verknüpfte Liste ist eine Datenstruktur, die aus einer Sammlung von Knoten besteht, wobei jeder Knoten einen Wert und einen Verweis auf den nächsten Knoten in der Sequenz enthält. Operationen wie das Hinzufügen eines Elements zum Kopf der Liste oder das Entfernen eines Elements aus dem Kopf sind O(1)-Operationen. Allerdings sind Operationen wie das Hinzufügen eines Elements am Ende der Liste oder das Entfernen eines Elements vom Ende O(n)-Operationen, wobei n die Anzahl der Elemente in der Liste ist.

Warum brauchen wir eine unveränderliche LinkedList?

In der funktionalen Programmierung ist Unveränderlichkeit ein Schlüsselkonzept. Unveränderlichkeit bedeutet, dass, sobald eine Datenstruktur erstellt wurde, sie
kann nicht geändert werden. Stattdessen wird mit den Änderungen eine neue Datenstruktur erstellt und die ursprüngliche bleibt unverändert.

Diese Immobilie bietet uns mehrere Vorteile:

  1. Thread-Sicherheit: Da die Datenstruktur unveränderlich ist, kann sie von mehreren Threads gemeinsam genutzt werden, ohne dass eine Synchronisierung erforderlich ist.
  2. Vorhersagbarkeit: Da die Datenstruktur unveränderlich ist, können wir zu jedem Zeitpunkt Rückschlüsse auf den Zustand der Datenstruktur ziehen.
  3. Rückgängigmachen: Da die Datenstruktur unveränderlich ist, können wir jederzeit zu einem früheren Zustand zurückkehren, indem wir die vorherige Version der Datenstruktur verwenden.
  4. Debugging: Unveränderlichkeit erleichtert das Debuggen, da die Datenstruktur nicht geändert werden kann.

Java-Sammlungen sind jedoch standardmäßig veränderbar, da der Schwerpunkt dieses Artikels auf LinkedList liegt. Die Gründe könnten vielfältig sein
Das reicht von einem nicht nachträglichen Einfall bei der Gestaltung der Kollektionen bis hin zu Leistungsgründen, die inhärenten
unveränderliche Datenstrukturen.

Unterschied zwischen nicht veränderbaren JDK-Sammlungen und dieser LinkedList

Das JDK stellt unveränderbare Sammlungen bereit, die die Originalsammlungen umhüllen. Sie unterstützen Unveränderlichkeit, sind jedoch weder dauerhaft noch bieten sie eine wirklich typsichere Möglichkeit. Sie sind Hüllen um die Originalkollektionen und sie
löst eine Ausnahme aus, wenn eine Änderungsoperation aufgerufen wird. Dies ist nicht dasselbe wie Unveränderlichkeit, bei der die Datenstruktur überhaupt nicht geändert werden kann und gleichzeitig sichergestellt wird, dass es schwierig ist, zur Laufzeit eine UnsupportedOperationException zu erhalten.

Persistent vs. unveränderlich

Obwohl die Begriffe „beständig“ und „unveränderlich“ oft synonym verwendet werden, haben sie unterschiedliche Bedeutungen. Während Unveränderlichkeit keine Änderung der Datenstruktur zulässt, ermöglicht Persistenz die gemeinsame Nutzung der Datenstruktur, wenn diese geändert wird. Das bedeutet, dass bei der Änderung einer Datenstruktur, d. h. bei der Erstellung einer neuen Version, Teile der alten Datenstruktur mit der neuen gemeinsam genutzt werden können, was zu Zeit- und Platzeffizienzgewinnen führt. Diese Technik wird Strukturelles Teilen

genannt

Es gibt mehrere Möglichkeiten, Persistenz in Datenstrukturen zu erreichen. Die Datenstrukturen reichen von einfach bis komplex, wie die Verwendung ausgeglichener Bäume wie AVL- oder Rot-Schwarz-Bäume, bis hin zu komplexeren wie Fingerbäumen und Radix-basierten ausgeglichenen Bäumen.

In diesem Artikel werden wir eine einfachere Version einer dauerhaften und unveränderlichen LinkedList mit teilweiser struktureller Freigabe implementieren. Der Grund dafür ist, dass LinkedList eine einfache Datenstruktur ist und uns hilft, die Konzepte der Unveränderlichkeit und Persistenz besser zu verstehen. Normalerweise ist die Implementierung komplexerer Datenstrukturen von Natur aus eine herausfordernde Aufgabe.

Implementierung

Im Folgenden werden wir Schritt für Schritt eine persistente und unveränderliche Single LinkedList in Java implementieren.

Für eine vollständige Implementierung und zusätzliche Monaden und Dienstprogramme, die Sie bei Ihrer funktionalen Programmiertour nach Java unterstützen, können Sie sich diese tolle kleine Bibliothek FunctionalUtils ansehen.

Der Name, den wir unserer LinkedList geben werden, ist SeqList und es wird eine generische Klasse sein.

Zunächst müssen wir über die Operationen nachdenken, die wir in unserer Liste unterstützen werden.

  1. Ergänzung zum Kopf der Liste, die eine O(1)-Operation sein wird.
  2. Entfernen eines Elements aus der Liste, was im schlimmsten Fall eine O(n)-Operation ist, wenn sich das Element am Ende befindet.
  3. Hinzufügung an einer beliebigen Position in der Liste.
  4. Filteroperation zum Ein-/Ausfiltern von Elementen mit einem Prädikat.
  5. Map- und FlatMap-Operationen, um unsere Liste zur einfacheren Funktionskomposition in eine Monade umzuwandeln.

Wir können uns eine LinkedList als eine Struktur vorstellen, die aus Knoten besteht, wobei jeder Knoten Folgendes umfasst:

  1. head 持有一个值。
  2. tail 保存列表的其余部分,而列表又是一个由头和尾组成的 LinkedList,直到列表末尾。
  3. 列表的末尾由一个空的 LinkedList 表示,这意味着头和尾都为空。

完整的实现可以在这里找到

鉴于列表的最后一个元素是一个空的 LinkedList,并且每个元素都是一个有头和尾的节点,我们可以将我们的 LinkedList 表示为由两个类组成的递归数据结构:

雷雷

其中 Cons 是一个名为 Construct 的函数式编程术语,其历史可以追溯到 Lisp 编程语言。

鉴于上述,我们可以实现 SeqList 接口如下:

雷雷

让我们分解一下上面写的内容:

  1. 我们创建了一个密封接口 SeqList,它将成为我们 LinkedList 的接口。
  2. empty() 方法创建一个空列表,它是 Empty 类的实例。
  3. 方法 add() 将一个元素添加到列表中。此方法的复杂度为 O(1),因为我们只是使用给定元素和当前列表创建一个新节点。此方法使用结构共享,因为新列表共享当前列表的尾部。
  4. of() 方法使用给定元素创建一个新列表。此方法的复杂度为 O(n),其中 n 是元素的数量。很明显,我们从最后一个元素开始并将其添加到列表中。这是因为我们想保留元素的顺序。

我们需要实施剩余的操作。让我们从删除操作开始:

雷雷

另外在我们的子类中实现 tail() 方法和其他一些有用的方法:

雷雷

我们可以从删除方法的实现中检查,我们正在使用递归调用来删除元素
列表。这是函数式编程中的典型模式,我们使用递归来遍历列表并
删除该元素。应注意避免在无限列表的情况下堆栈溢出。未来的改进可能是使用 Java 不支持的尾递归优化,但可以使用蹦床来实现。

最后,让我们实现map和flatMap操作,将我们的List变成Monad:

雷雷

从 map 和 flatMap 方法的实现中可以看出,我们使用递归调用来遍历列表并将函数应用于每个元素。 flatMap 方法有点复杂,因为它需要函数返回一个新列表,我们需要将其与列表的其余部分连接起来。由于其众所周知的难度和使用高级数据结构的重要性,这两种方法都不使用结构共享。未来的改进将在以后的文章中进行探讨。

使用示例

让我们看看 SeqList 的一些使用示例。

  • 想象我们有一个整数列表,我们想要过滤掉偶数,然后将它们乘以 2 的幂,但具有不变性和持久性。
雷雷
  • 想象我们有一个字符串列表,我们想用前缀和后缀将它们连接起来。
雷雷
  • 想象我们有一个列表列表,我们想要将它们展平。
雷雷
  • 另一个例子是使用 JDK 21 开关表达式并利用编译​​器检查的模式匹配。
雷雷

缺点

  1. 性能:如果列表主要用于从列表头部获取元素的前置元素,那么性能很好。在所有其他情况下,此实现至少需要 O(n)。
  2. 复杂性:持久且不可变的 LinkedList 的实现比其可变的对应物更复杂。
  3. 内存:由于为每个操作创建新列表,持久且不可变的 LinkedList 的实现比其可变的对应项需要更多的内存。通过结构共享,这种情况会得到缓解,但不会消除。

结论

在本文中,我们用 Java 实现了一个具有部分结构共享的持久且不可变的 LinkedList。我们演示了不变性和持久性的好处以及如何在 Java 中实现它们。我们还展示了如何将 LinkedList 转换为 Monad 以便更轻松地进行函数组合。我们讨论了持久性和不可变数据结构的优点和缺点以及如何在实践中使用它们。

以上是持久且不可变的 Java LinkedList的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!