根据流的性质,除非您阅读它,否则您不知道 next 元素。因此,在处理当前元素时,无法直接获取 next 元素。但是,由于您正在阅读 current 元素,因此您显然知道之前阅读过的内容,因此要实现“访问 previous 元素”和“访问 next 元素”这样的目标> element”,您可以依赖已处理的元素的历史记录。
以下两种解决方案可以解决您的问题:
- 获取对以前读取的元素的访问权限。这样您就知道 当前 元素和定义的先前读取元素的数量
- 假设在流处理时您读取了 next 元素,而 current 元素是在之前的迭代中读取的。换句话说,您将先前读取的元素视为“当前”,将当前处理的元素视为 next(见下文)。
解决方案 1 - 实施
首先,我们需要一个数据结构,它允许跟踪流经流的数据。不错的选择可能是Queue 的实例,因为队列本质上允许数据流过它们。我们只需要将队列绑定到我们想知道的最后一个元素的数量(对于您的用例,这将是 3 个元素)。为此,我们创建了一个“有界”队列保持历史,如下所示:
public class StreamHistory<T> {
private final int numberOfElementsToRemember;
private LinkedList<T> queue = new LinkedList<T>(); // queue will store at most numberOfElementsToRemember
public StreamHistory(int numberOfElementsToRemember) {
this.numberOfElementsToRemember = numberOfElementsToRemember;
}
public StreamHistory save(T curElem) {
if (queue.size() == numberOfElementsToRemember) {
queue.pollLast(); // remove last to keep only requested number of elements
}
queue.offerFirst(curElem);
return this;
}
public LinkedList<T> getLastElements() {
return queue; // or return immutable copy or immutable view on the queue. Depends on what you want.
}
}
泛型参数 T 是流的实际元素的类型。 save 方法返回对当前 StreamHistory 实例的引用,以便更好地与 java Stream api 集成(见下文),它并不是真正需要的。
现在唯一要做的就是将元素流转换为 StreamHistory 实例流(其中流的每个下一个元素将保存通过流的实际对象的最后 n 个实例)。
public class StreamHistoryTest {
public static void main(String[] args) {
Stream<Character> charactersStream = IntStream.range(97, 123).mapToObj(code -> (char) code); // original stream
StreamHistory<Character> streamHistory = new StreamHistory<>(3); // instance of StreamHistory which will store last 3 elements
charactersStream.map(character -> streamHistory.save(character)).forEach(history -> {
history.getLastElements().forEach(System.out::print);
System.out.println();
});
}
}
在上面的例子中,我们首先创建了一个所有字母的流。然后我们创建 StreamHistory 的实例,该实例将被推送到原始流上的 map() 调用的每次迭代。通过调用 map(),我们转换为包含对 StreamHistory 实例的引用的流。
请注意,每次数据流经原始流时,对 streamHistory.save(character) 的调用都会更新 streamHistory 对象的内容以反映流的当前状态。
最后,在每次迭代中,我们打印最后 3 个保存的字符。该方法的输出如下:
a
ba
cba
dcb
edc
fed
gfe
hgf
ihg
jih
kji
lkj
mlk
nml
onm
pon
qpo
rqp
srq
tsr
uts
vut
wvu
xwv
yxw
zyx
解决方案 2 - 实施
虽然解决方案 1 在大多数情况下都可以完成这项工作并且相当容易遵循,但也有一些用例可以检查下一个元素和前一个元素确实很方便。在这种情况下,我们只对三个元素元组(前一个、当前、下一个)感兴趣,并且只有一个元素并不重要(例如,考虑以下谜语:“给定一个数字流返回一个由三个后续数字组成的元组,这给出了最高金额”)。为了解决此类用例,我们可能希望拥有比 StreamHistory 类更方便的 api。
对于这个场景,我们引入了 StreamHistory 类的新变体(我们称之为 StreamNeighbours)。该类将允许直接检查 previous 和 next 元素。处理将在时间“T-1”完成(即:当前处理的原始元素被视为next元素,之前处理的原始元素被视为current元素)。这样,在某种意义上,我们可以检查前面的一个元素。
修改后的类如下:
public class StreamNeighbours<T> {
private LinkedList<T> queue = new LinkedList(); // queue will store one element before current and one after
private boolean threeElementsRead; // at least three items were added - only if we have three items we can inspect "next" and "previous" element
/**
* Allows to handle situation when only one element was read, so technically this instance of StreamNeighbours is not
* yet ready to return next element
*/
public boolean isFirst() {
return queue.size() == 1;
}
/**
* Allows to read first element in case less than tree elements were read, so technically this instance of StreamNeighbours is
* not yet ready to return both next and previous element
* @return
*/
public T getFirst() {
if (isFirst()) {
return queue.getFirst();
} else if (isSecond()) {
return queue.get(1);
} else {
throw new IllegalStateException("Call to getFirst() only possible when one or two elements were added. Call to getCurrent() instead. To inspect the number of elements call to isFirst() or isSecond().");
}
}
/**
* Allows to handle situation when only two element were read, so technically this instance of StreamNeighbours is not
* yet ready to return next element (because we always need 3 elements to have previos and next element)
*/
public boolean isSecond() {
return queue.size() == 2;
}
public T getSecond() {
if (!isSecond()) {
throw new IllegalStateException("Call to getSecond() only possible when one two elements were added. Call to getFirst() or getCurrent() instead.");
}
return queue.getFirst();
}
/**
* Allows to check that this instance of StreamNeighbours is ready to return both next and previous element.
* @return
*/
public boolean areThreeElementsRead() {
return threeElementsRead;
}
public StreamNeighbours<T> addNext(T nextElem) {
if (queue.size() == 3) {
queue.pollLast(); // remove last to keep only three
}
queue.offerFirst(nextElem);
if (!areThreeElementsRead() && queue.size() == 3) {
threeElementsRead = true;
}
return this;
}
public T getCurrent() {
ensureReadyForReading();
return queue.get(1); // current element is always in the middle when three elements were read
}
public T getPrevious() {
if (!isFirst()) {
return queue.getLast();
} else {
throw new IllegalStateException("Unable to read previous element of first element. Call to isFirst() to know if it first element or not.");
}
}
public T getNext() {
ensureReadyForReading();
return queue.getFirst();
}
private void ensureReadyForReading() {
if (!areThreeElementsRead()) {
throw new IllegalStateException("Queue is not threeElementsRead for reading (less than two elements were added). Call to areThreeElementsRead() to know if it's ok to call to getCurrent()");
}
}
}
现在,假设已经读取了三个元素,我们可以直接访问current元素(也就是在时间T-1通过流的元素),我们可以访问next 元素(这是当前通过流的元素)和 previous(这是在时间 T-2 时通过流的元素):
public class StreamTest {
public static void main(String[] args) {
Stream<Character> charactersStream = IntStream.range(97, 123).mapToObj(code -> (char) code);
StreamNeighbours<Character> streamNeighbours = new StreamNeighbours<Character>();
charactersStream.map(character -> streamNeighbours.addNext(character)).forEach(neighbours -> {
// NOTE: if you want to have access the values before instance of StreamNeighbours is ready to serve three elements
// you can use belows methods like isFirst() -> getFirst(), isSecond() -> getSecond()
//
// if (curNeighbours.isFirst()) {
// Character currentChar = curNeighbours.getFirst();
// System.out.println("???" + " " + currentChar + " " + "???");
// } else if (curNeighbours.isSecond()) {
// Character currentChar = curNeighbours.getSecond();
// System.out.println(String.valueOf(curNeighbours.getFirst()) + " " + currentChar + " " + "???");
//
// }
//
// OTHERWISE: you are only interested in tupples consisting of three elements, so three elements needed to be read
if (neighbours.areThreeElementsRead()) {
System.out.println(neighbours.getPrevious() + " " + neighbours.getCurrent() + " " + neighbours.getNext());
}
});
}
}
输出如下:
a b c
b c d
c d e
d e f
e f g
f g h
g h i
h i j
i j k
j k l
k l m
l m n
m n o
n o p
o p q
p q r
q r s
r s t
s t u
t u v
u v w
v w x
w x y
x y z
通过 StreamNeighbours 类更容易跟踪前一个/下一个元素(因为我们有具有适当名称的方法),而在 StreamHistory 类中这更麻烦,因为我们需要手动“反转”队列的顺序来实现这一点.