问题描述
如果我从头开始开发链表,我可以在我的业务实体类中存储一个指向 Node 对象的指针,并实现恒定的 O(1) remove 和 insertAfter操作。在 java 标准库实现中,它们是 O(n),在处理大型数据集时可能会有很大差异。
为什么他们不公开 Node 类并在其中封装一些细节仍然使类本身(可能通过接口)可访问?这将使 LinkedList 更加灵活。
我们在 Apache Commons 或 Guava 中是否有类似 FlexibleLinkedList 的东西?
解决方法
ListIterator
为什么他们不只是将 Node 类公开并在其中封装一些细节仍然使类本身(可能通过接口)可访问?
没有必要。
为什么他们不公开 Node 类并在其中封装一些细节仍然使类本身(可能通过接口)可访问?这将使 LinkedList 更加灵活。
那个已经存在了。
如果您想获得基于节点的操作的好处,例如:
- 根据当前节点给我下一项
- 删除我已有的节点,不定位它
- 在我已经拥有的节点之后插入一些内容
您只需要使用 list.listIterator() 返回的 ListIterator
。此方法由所有 List
提供。
该类封装了在迭代中知道当前节点的逻辑,并提供了直接使用Node
进行操作的有效方法,例如:
-
add
- 元素被插入到next()
返回的元素之前
-
set
- 用指定的元素替换next()
或previous()
返回的最后一个元素 -
remove
- 从列表中删除next()
或previous()
返回的最后一个元素
同时提供使用 next()
和 previous()
控制迭代的方法。
示例
例如,您可以每隔一个元素更改一次:
LinkedList<Integer> values = new LinkedList<>(List.of(1,2,3,4,5,6,7,8,9,10));
int i = 0;
ListIterator<Integer> iter = values.listIterator();
while (iter.hasNext()) {
iter.next();
if (i % 2 == 0) {
iter.set(100);
}
i++;
}
结果
[100,100,10]
并且此代码在 O(n)
中运行,它不需要每次都需要重新定位节点。与
for (int i = 0; i < list.size(); i++) {
if (i % 2 == 0) {
list.set(i,100);
}
}
出于您所述的原因,它在 O(n^2)
中运行。
隐藏 Node
的好处
一般来说,封装和隐藏您的私人内部运作要好得多。用户不应打扰LinkedList
如何在幕后工作。
此外,如果它会暴露 Node
,用户可以偷偷地编辑它们,整个列表就会变得疯狂。
例如用户可以然后做
Node a = list.getNodeAtIndex(3);
Node b = a.next;
Node c = b.next;
// Remove b
a.next = c;
c.previous = a;
不调整列表的 size
。所以 list.size()
现在会返回错误的数字,可能会导致迭代过程中崩溃。
或者你也可以引入一个危险的循环:
a.next = b;
b.next = a;
或者忘记设置previous
,导致向后迭代时不同的列表:
a.next = c;
c.previous = b;
ListIterator
确保此类事情不会发生,同时提供相同的功能。因此,它不是直接向用户公开节点,而是仅公开所需的功能,并以完全控制的方法的形式公开。