Java 8的惰性序列实现

我刚刚在GitHub上发布了LazySeq库,这是我最近进行的Java 8实验的结果。 我希望你会喜欢它。 即使您觉得它不是很有用,它仍然是Java 8(以及一般而言)中的函数式编程的重要课程。 而且它可能是第一个针对Java 8的社区库!

介绍

惰性序列是仅在实际需要其元素时才计算的数据结构。 对延迟序列的所有操作map()例如map()filter()也都是延迟的,从而将调用延迟到真正必要的时候。 总是从一开始就使用非常便宜的first / rest遍历惰性序列
分解( head()tail() )。 惰性序列的一个重要属性是它们可以表示无限的数据流,例如,随时间推移的所有自然数或温度测量值。

惰性序列会记住已计算的值,因此,如果您访问第N个元素,则也会计算并缓存从1N-1所有元素。 尽管如此, LazySeq (处于许多功能语言和算法的核心)是不可变的且线程安全的。

基本原理

该库在很大程度上受到scala.collection.immutable.Stream启发,旨在提供不可变,线程安全且易于使用的惰性序列实现(可能是无限的)。 有关某些用例,请参见Scala和Clojure中的惰性序列

Stream类名称是用Java 8已被使用,因此LazySeq被选择,类似于lazy-seq Clojure中 。 说到Stream ,一开始它看起来像是一个开箱即用的惰性序列实现。 但是,引用Javadoc:

流不是数据结构

和:

一旦在流上执行了某个操作,该操作将被视为已消耗并且不再可用于其他操作。

换句话说, java.util.stream.Stream只是现有集合的一个瘦包装,适合一次使用。 更类似于Iterator ,而不是Stream于斯卡拉。 该库试图填补这一空白。

当然,在Java 8之前可以实现惰性序列数据结构,但是缺少lambda使得使用这种数据结构变得乏味且过于冗长。

入门

在10分钟内构建并处理惰性序列。

所有自然数的无限序列

为了创建一个惰性序列,您可以使用LazySeq.cons()工厂方法,该方法接受第一个元素( head )和一个以后可能用于计算rest( tail )的函数。 例如,为了产生具有给定起始元素的自然数的惰性序列,您只需说:

private LazySeq<Integer> naturals(int from) {return LazySeq.cons(from, () -> naturals(from + 1));
}

这里真的没有递归。 如果存在,则调用naturals()会很快导致StackOverflowError因为它会在没有停止条件的情况下自行调用。 但是() -> naturals(from + 1)表达式定义了一个函数,函数返回此数据结构将调用的LazySeq<Integer> (准确地说是Supplier ),但仅在需要时才返回。 查看下面的代码,您认为几次调用了naturals()函数(第一行除外)?

final LazySeq<Integer> ints = naturals(2);final LazySeq<String> strings = ints.map(n -> n + 10).filter(n -> n % 2 == 0).take(10).flatMap(n -> Arrays.asList(0x10000 + n, n)).distinct().map(Integer::toHexString);

的第一次调用naturals(2)返回来自起始懒惰序列2但休息( 345 ,...)还没有被计算。 稍后,我们将map()到该序列上,对其进行filter()take()前10个元素,删除重复项,等等。所有这些操作都不会评估序列,并且尽可能懒惰。 例如take(10)不会急切地求出前10个元素以返回它们。 而是返回新的惰性序列,该序列记住它应该在第10个元素处截断原始序列。

同样适用于distinct() 。 它不会评估提取所有唯一值的整个序列(否则,上面的代码将Swift爆炸,遍历无限量的自然数)。 而是返回仅包含第一个元素的新序列。 如果您要第二个唯一元素,它将懒惰地评估尾巴,但只会尽可能多。 查看
toString()输出:

System.out.println(strings);
//[1000c, ?]

问号( ? )说: “该集合中可能还有其他内容,但我还不知道” 。 您了解1000c来自何处吗? 仔细地看:

  1. 2开始的无限自然数流开始
  2. 向每个元素添加10 (因此第一个元素变为12或十六进制的C
  3. filter()输出奇数( 12为偶数,因此保持不变)
  4. take()到目前为止序列中的前10元素
  5. 每个元素都被两个元素替换:该元素加0x1000和元素本身( flatMap() )。 这不会产生成对的序列,但是会产生两倍长的整数序列
  6. 我们确保只返回distinct()元素
  7. 最后,我们将整数转换为十六进制字符串。

如您所见,这些操作都不是真正需要评估整个流的。 唯一的头正在转变,这就是我们最终看到的。 那么,何时实际评估此数据结构? 在绝对必要时(例如在副作用遍历期间):

strings.force();//or
strings.forEach(System.out::println);//or
final List<String> list = strings.toList();//or
for (String s : strings) {System.out.println(s);
}

仅上述所有语句将强制评估整个延迟序列。 如果我们的序列是无限的,不是很聪明,但是
strings仅限于前10个元素,因此不会无限运行。 如果只想强制执行序列的一部分,只需调用strings.take(5).force() 。 顺便说一句,您是否注意到我们可以使用标准Java 5 for-each语法遍历LazySeq strings ? 这是因为LazySeq实现了List接口,因此可以与Java Collections Framework生态系统很好地配合使用:

import java.util.AbstractList;public abstract class LazySeq<E> extends AbstractList<E>

请记住,一旦对惰性序列进行了评估(计算),它将对它们进行缓存( 记住 )以备后用。 这使得惰性序列非常适合表示无限或非常长的数据流,这些数据计算起来很昂贵。

迭代()

建立无限的惰性序列通常可以归结为提供一个初始元素和一个功能,该功能可以根据前一个元素生成下一个元素。 换句话说,第二元素是第一个的函数,第三元素是第二个的函数,依此类推。 为这种情况提供了便利的LazySeq.iterate()函数。 现在, ints定义可以如下所示:

final LazySeq<Integer> ints = LazySeq.iterate(2, n -> n + 1);

我们从2开始,每个后续元素表示为前一个元素+ 1。

更多示例:斐波那契数列和Collat​​z猜想

没有斐波那契数字,就不会留下关于懒惰数据结构的文章,例如:

private static LazySeq<Integer> lastTwoFib(int first, int second) {return LazySeq.cons(first,() -> lastTwoFib(second, first + second));
}

斐波那契数列也是无限的,但我们可以通过多种方式自由变换它:

System.out.println(fib.drop(5).take(10).toList()
);
//[5, 8, 13, 21, 34, 55, 89, 144, 233, 377]final int firstAbove1000 = fib.filter(n -> (n > 1000)).head();fib.get(45);

看到无限的数字流是多么容易和自然吗? drop(5).take(10)跳过前5个元素,并显示下一个10。在这一点上,已经计算了前15个数字,以后再也不会计算了。

查找高于1000(可能是1597 )的第一个斐波那契数非常简单。 head()始终由filter()预先计算,因此不需要进一步评估。 最后但并非最不重要的一点是,我们只需索取第45个斐波那契数 (从0开始)并获得
1134903170 。 如果您尝试访问该编号之前的任何斐波那契数,它们将被预先计算并可以快速检索。

有限序列(Collat​​z猜想)

Collat​​z猜想也是一个很有趣的问题。 对于每个正整数n我们使用以下算法计算下一个整数:

  • 如果n为偶数则为n n/2
  • 如果n为奇数,则为3n + 1

例如,从10序列开始的外观如下:10、5、16、8、4、2、1。该序列在达到1时结束。数学家认为,从任何整数开始,我们最终都将达到1,但尚未证明。

让我们创建一个惰性序列,该序列为给定的n生成Collat​​z级数,但仅根据需要生成。 如上所述,这次我们的序列将是有限的:

private LazySeq<Long> collatz(long from) {if (from > 1) {final long next = from % 2 == 0 ? from / 2 : from * 3 + 1;return LazySeq.cons(from, () -> collatz(next));} else {return LazySeq.of(1L);}
}

该实现由定义直接驱动。 对于每个大于1数字,返回该数字+惰性计算的流的其余部分( () -> collatz(next) )。 如您所见,如果给定1 ,我们将使用特殊的of()工厂方法返回单元素惰性序列。 让我们用上述10测试它:

final LazySeq<Long> collatz = collatz(10);collatz.filter(n -> (n > 10)).head();
collatz.size();

filter()允许我们找到大于10的序列中的第一个数字。 请记住,惰性序列将必须遍历内容(进行自我评估),但只能遍历找到第一个匹配元素的位置。 然后停止,确保其计算量尽可能少。

但是,为了计算元素总数, size()必须遍历整个序列。 当然,这仅适用于有限的惰性序列,在无限序列上调用size()最终效果不佳。

如果您对此序列稍作练习,您将很快意识到,不同数字的序列共享相同的后缀 (总是以相同的数字序列结尾)。 这请求进行一些缓存/结构共享。 有关详细信息,请参见CollatzConjectureTest

现实生活?

无限的数字序列很棒,但在现实生活中并不十分实用。 也许还有更多脚踏实地的例子? 假设您有一个收藏,您需要从该收藏中随机挑选一些物品。 代替集合,我将使用一个返回随机拉丁字符的函数:

private char randomChar() {return (char) ('A' + (int) (Math.random() * ('Z' - 'A' + 1)));
}

但是有一个转折。 您需要N个(N <26,拉丁字符个数)唯一值。 仅仅几次调用randomChar()并不能保证唯一性。 解决这个问题的方法很少,使用LazySeq非常简单:

LazySeq<Character> charStream = LazySeq.<Character>continually(this::randomChar);
LazySeq<Character> uniqueCharStream = charStream.distinct();

当需要时, continually()只需为每个元素调用给定函数。 因此, charStream将是无限的随机字符流。 当然,它们不能唯一。 但是uniqueCharStream保证其输出是唯一的。 它通过检查底层charStream下一个元素并拒绝已出现的项目来实现。 现在我们可以说uniqueCharStream.take(4)并确保不会出现重复项。

再次注意, continually(this::randomChar).distinct().take(4)实际上只调用randomChar()一次! 只要您不消耗此序列,它就会保持延迟并尽可能延迟评估。

另一个示例涉及从数据库加载数据的批次(页面)。 使用ResultSetIterator很麻烦,但是将整个数据集加载到内存中通常不可行。 另一种选择是急于加载第一批数据,然后提供加载下一批数据的功能。 仅当确实需要数据时才加载数据,而我们不会遇到性能或可伸缩性问题。

首先,让我们定义抽象的API,以从数据库中加载批量数据:

public List<Record> loadPage(int offset, int max) {//load records from offset to offset + max
}

我完全从技术中抽象出来,但是您明白了。 假设我们现在定义LazySeq<Record> ,它从第0行开始,仅在需要时才加载下一页:

public static final int PAGE_SIZE = 5;private LazySeq<Record> records(int from) {return LazySeq.concat(loadPage(from, PAGE_SIZE),() -> records(from + PAGE_SIZE));
}

通过调用records(0)创建新的LazySeq<Record>实例时,将加载5个元素的第一页。 这意味着已经计算出前5个序列元素。 如果您尝试访问6th或更高版本,sequence将自动加载所有丢失的记录并对其进行缓存。 换句话说,您永远不会两次计算相同的元素。

使用序列时,更有用的工具是grouped()sliding()方法。 首先将输入序列分成大小相等的组。 以这个为例,还证明这些方法总是很懒:

final LazySeq<Character> chars = LazySeq.of('A', 'B', 'C', 'D', 'E', 'F', 'G');chars.grouped(3);
//[[A, B, C], ?]chars.grouped(3).force();       //force evaluation
//[[A, B, C], [D, E, F], [G]]

同样适用于sliding()

chars.sliding(3);
//[[A, B, C], ?]chars.sliding(3).force();       //force evaluation
//[[A, B, C], [B, C, D], [C, D, E], [D, E, F], [E, F, G]]

这两种方法非常有用。 您可以通过滑动窗口查看数据(例如,计算移动平均值 )或将其划分为等长的存储桶。

您可能会发现有用的最后一个有用的实用程序方法是scan() ,它会(当然是延迟地scan()迭代输入流,并通过在输入的前一个和当前元素上应用函数来构造输出的每个元素。 代码段值一千个字:

LazySeq<Integer> list = LazySeq.numbers(1).scan(0, (a, x) -> a + x);list.take(10).force();  //[0, 1, 3, 6, 10, 15, 21, 28, 36, 45]

LazySeq.numbers(1)是自然数(1、2、3…)的序列。 scan()创建一个新的序列,从
0并为输入的每个元素(自然数)将其添加到自身的最后一个元素。 因此我们得到:[ 0 0+1 0+1+2 0+1+2+3 0+1+2+3+4 0+1+2+3+4+5 …]。 如果您想要一系列增长的字符串,只需替换几种类型:

LazySeq.continually("*").scan("", (s, c) -> s + c).map(s -> "|" + s + "\\").take(10).forEach(System.out::println);

并享受这个美丽的三角形:

|\
|*\
|**\
|***\
|****\
|*****\
|******\
|*******\
|********\
|*********\

或者(相同的输出):

LazySeq.iterate("", s -> s + "*").map(s -> "|" + s + "\\").take(10).forEach(System.out::println);

Java Collections框架的互操作性

LazySeq实现java.util.List接口,因此可以在许多地方使用。 此外,它还对集合(即流和集合)实现了Java 8增强:

lazySeq.stream().map(n -> n + 1).flatMap(n -> asList(0, n - 1).stream()).filter(n -> n != 0).substream(4, 18).limit(10).sorted().distinct().collect(Collectors.toList());

但是,Java 8中的流是为了解决作为LazySeq (延迟评估)基础的功能而创建的。 上面的示例将所有中间步骤推迟到调用collect()为止。 使用LazySeq您可以安全地跳过.stream()并直接处理序列:

lazySeq.map(n -> n + 1).flatMap(n -> asList(0, n - 1)).filter(n -> n != 0).slice(4, 18).limit(10).sorted().distinct();

此外, LazySeq提供了特殊用途的收集器(请参阅: LazySeq.toLazySeq() ),即使与collect()一起使用也可以避免评估,这通常会强制进行完整的收集计算。

实施细节

每个懒惰序列都是围绕急切计算的和懒洋洋地表示为函数的尾巴的思想构建的。 这与经典的单链列表递归定义非常相似:

class List<T> {private final T head;private final List<T> tail;//...
}

但是,在延迟序列的情况下, 尾部是函数而不是值。 该功能的调用会尽可能推迟:

class Cons<E> extends LazySeq<E> {private final E head;private LazySeq<E> tailOrNull;private final Supplier<LazySeq<E>> tailFun;@Overridepublic LazySeq<E> tail() {if (tailOrNull == null) {tailOrNull = tailFun.get();}return tailOrNull;}

有关完整的实现,请参见在创建时知道tail时使用的Cons.javaFixedCons.java (例如LazySeq.of(1, 2)LazySeq.cons(1, () -> someTailFun()相对LazySeq.of(1, 2) ,例如LazySeq.of(1, 2) )。

陷阱和常见危险

下面介绍常见问题和误解。

评估太多

使用无限序列的最大危险之一就是试图对其进行完全评估,这显然会导致无限计算。 无限序列背后的思想不是整体评估它,而是在不引入人为限制和意外复杂性的情况下,尽可能多地获取它(请参见数据库加载示例)。

但是,评估整个序列太容易遗漏了。 例如,调用LazySeq.size() 必须评估整个序列,并且将无限运行,最终填满堆栈或堆(实现细节)。 还有其他方法需要完全遍历才能正常运行。 例如allMatch()确保所有元素都匹配给定谓词。 有些方法甚至更危险,因为它们是否完成取决于序列中的数据。 例如,如果head匹配谓词–或从不, anyMatch()可能会立即返回。

有时,我们可以使用更具确定性的方法来轻松避免昂贵的操作。 例如:

seq.size() <= 10        //BAD

如果seq是无限的,则可能无法正常工作或非常慢。 但是,我们可以通过(更多)可预测的方式实现相同的目标:

seq.drop(10).isEmpty()

请记住,惰性序列是不可变的(因此,我们实际上并不对seq突变), drop(n)通常为O(n)isEmpty()O(1)

如有疑问,请查阅源代码或JavaDoc以确保您的操作不会过于急切地评估您的序列。 使用LazySeq ,也要非常小心,需要使用java.util.Collection或java.util.List 。

持有不必要的头参考

惰性序列应定义为记住已计算的元素。 您必须意识到这一点,否则您的序列(尤其是无限序列)将Swift填满可用内存。 但是,由于LazySeq只是一个奇特的链接列表,因此,如果您不再保留对head的引用(而仅保留中间的某个元素),则可以进行垃圾回收。 例如:

//LazySeq<Char> first = seq.take(10);
seq = seq.drop(10);

前十个元素被删除,我们假设没有任何东西引用过seq以前的内容。 这使前十个元素有资格进行垃圾回收。 但是,如果我们取消注释第一行,并保持参照老headfirst ,JVM不会释放任何内存。 让我们对此进行透视。 下面的代码最终将抛出OutOfMemoryError因为infinite引用将保持序列的开头,因此,到目前为止创建的所有元素:

LazySeq<Big> infinite = LazySeq.<Big>continually(Big::new);
for (Big arr : infinite) {//
}

但是,通过内联对continually()调用或将其提取到方法中,此代码可以完美运行(嗯,仍然可以永远运行,但几乎不使用内存):

private LazySeq<Big> getContinually() {return LazySeq.<Big>continually(Big::new);
}for (Big arr : getContinually()) {//
}

有什么不同? 每个循环都在下面使用迭代器。 LazySeqIterator下面的LazySeqIterator不会保留对旧head()的引用,因此,如果没有其他引用该head的对象,则可以进行垃圾回收,当使用for-each时,请参见true javac输出:

for (Iterator<Big> cur = getContinually().iterator(); cur.hasNext(); ) {final Big arr = cur.next();//...
}

TL; DR

您的序列在遍历时会增加。 如果在另一端成长时保持一端,则最终会炸毁。 就像您在Hibernate中的一级缓存一样,如果您在一个事务中加载过多。 仅根据需要使用。

转换为纯Java集合

转换很简单,但是很危险。 这是以上几点的结果。 您可以通过调用toList()将惰性序列转换为java.util.List

LazySeq<Integer> even = LazySeq.numbers(0, 2);
even.take(5).toList();  //[0, 2, 4, 6, 8]

或使用具有更丰富API的Java 8中的Collector

even.stream().limit(5).collect(Collectors.toSet())   //[4, 6, 0, 2, 8]

但是请记住,Java集合在定义上是有限的,因此请避免将惰性序列明确转换为集合。 注意
LazySeq已经是List ,因此是IterableCollection 。 它还具有高效的LazySeq.iterator() 。 如果可以,只需直接传递LazySeq实例即可。

性能,时间和空间复杂度

每个序列(空除外head()head()总是很容易计算,因此对其进行快速O(1)访问。 计算tail()可能占用从O(1) (如果已经计算过)到无限时间的所有内容。 以这个有效的流为例:

import static com.blogspot.nurkiewicz.lazyseq.LazySeq.cons;
import static com.blogspot.nurkiewicz.lazyseq.LazySeq.continually;LazySeq<Integer> oneAndZeros = cons(1,() -> continually(0)
).
filter(x -> (x > 0));

它代表1后跟无穷多个0 s。 通过过滤所有正数( x > 0 ),我们得到了一个具有相同头部的序列,但是尾部的过滤被延迟了(延迟)。 但是,如果现在我们不小心调用oneAndZeros.tail()LazySeq将继续计算该无限序列的越来越多,但是由于在初始1之后没有正元素,因此该操作将永远运行,最终抛出StackOverflowErrorOutOfMemoryError (这是一个实施细节)。

但是,如果您达到此状态,则可能是编程错误或滥用该库。 通常tail()将接近O(1) 。 另一方面,如果您已经“堆叠”了很多操作,则调用tail()会Swift一次又一次触发它们,因此tail()运行时间在很大程度上取决于您的数据结构。

LazySeq上的大多数操作都是O(1)因为它们是惰性的。 一些操作,例如get(n)drop(n)都是O(n)n表示参数,而不是序列长度)。 一般而言,运行时间将类似于正常的链表。

因为LazySeq记住单个链接列表中所有已经计算出的值,所以内存消耗始终为O(n) ,其中n n是已经计算出的元素数。

故障排除

错误invalid target release: 1.8在Maven构建期间invalid target release: 1.8

如果在maven生成过程中看到此错误消息:

[INFO] BUILD FAILURE
...
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project lazyseq:
Fatal error compiling: invalid target release: 1.8 -> [Help 1]

这意味着您不使用Java 8进行编译。 下载具有lambda支持的JDK 8,并让maven使用它:

$ export JAVA_HOME=/path/to/jdk8

我收到StackOverflowError或程序无限期挂起

使用LazySeq ,有时会出现StackOverflowErrorOutOfMemoryError

java.lang.StackOverflowErrorat sun.misc.Unsafe.allocateInstance(Native Method)at java.lang.invoke.DirectMethodHandle.allocateInstance(DirectMethodHandle.java:426)at com.blogspot.nurkiewicz.lazyseq.LazySeq.iterate(LazySeq.java:118)at com.blogspot.nurkiewicz.lazyseq.LazySeq.lambda$0(LazySeq.java:118)at com.blogspot.nurkiewicz.lazyseq.LazySeq$$Lambda$2.get(Unknown Source)at com.blogspot.nurkiewicz.lazyseq.Cons.tail(Cons.java:32)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)at com.blogspot.nurkiewicz.lazyseq.LazySeq.size(LazySeq.java:325)

当使用可能无限的数据结构时,必须小心。 避免调用必须size()allMatch()minBy()forEach()reduce() ,…)或可以filter()distinct() ,…)遍历整个序列以便给出正确值的操作结果。 有关更多示例和避免方法,请参阅陷阱

到期

质量

该项目是作为练习开始的,尚未经过战斗验证。 但是,健康的300多个单元测试套件 (测试代码/生产代码比率为3:1)可以保护质量和功能正确性。 我还通过LazySeq尾部函数并验证它们被尽可能少地调用来确保LazySeq尽可能地懒惰。

贡献和错误报告

如果发现错误或缺少功能,请随时打开新票证或开始拉取请求 。 我也很LazySeq在野外看到LazySeq更多有趣用法。

可能的改进

  • 就像在FixedCons知道尾巴的情况下使用FixedCons一样,请考虑将IterableCons包装在一个节点中而不是建立FixedCons层次结构的Iterable 。 这可以用于所有concat方法。
  • 并行处理支持(实现分离器?)

执照

该项目是在Apache许可证的 2.0版下发布的 。

参考:来自Java和社区博客的JCG合作伙伴 Tomasz Nurkiewicz 提供的Java 8的惰性序列实现 。

翻译自: https://www.javacodegeeks.com/2013/05/lazy-sequences-implementation-for-java-8.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/368388.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字符串、对象、数组操作方法、json方法

1.字符串操作方法 1.charAt * 作用 * 通过索引找字符 * 语法 * 字符串.charAt(index) * 参数 * 索引值 * 取值范围&#xff1a;0-(length-1) * 如果不写参数&#xff0c;那默认为0 * 如果index超出了范围后&…

禁用mysql的sleep函数_MySQL的sleep函数的特殊特现象

MySQL中的系统函数sleep&#xff0c;实际应用的场景不多&#xff0c;一般用来做实验测试&#xff0c;昨天在测试的时候&#xff0c;意外发现sleep函数的一个特殊现象。如果在查询语句中使用sleep函数&#xff0c;那么休眠的时间跟返回的记录有关。如下测试所示&#xff1a;mysq…

ADO.NET 事务控制

在ADO.NET 中&#xff0c;可以使用Connection 和Transaction 对象来控制事务。若要执行事务&#xff0c;请执行下列操作&#xff1a; 1.调用Connection 对象的BeginTransaction 方法来标记事务的开始。 2.将Transaction 对象分配给要执行的Command的Transaction 属性。…

sublime text 快捷键的使用大全

多行选择后按下ctrl/ 选择类 CtrlD 选中光标所占的文本&#xff0c;继续操作则会选中下一个相同的文本。 AltF3 选中文本按下快捷键&#xff0c;即可一次性选择全部的相同文本进行同时编辑。举个栗子&#xff1a;快速选中并更改所有相同的变量名、函数名等。 CtrlL 选中整行&am…

amoeba mysql读写分离_Mysql 实现读写分离的详细教程(amoeba)

Mysql 实现读写分离的详细教程(amoeba)发布时间&#xff1a;2018-08-17作者&#xff1a;laosun阅读(2220)继上篇文章&#xff0c;mysql实现主从配置之后的读写分离实现方式&#xff0c;文章地址。amoeba是盛大架构师陈思儒独立完成&#xff0c;除此之外还有很多中间件&#xff…

z-index的学习整理转述

前言&#xff1a;这是笔者第一次写博客&#xff0c;主要是学习之后自己的理解。如果有错误或者疑问的地方&#xff0c;请大家指正&#xff0c;我会持续更新&#xff01; z-index属性描述元素的堆叠顺序&#xff08;层级&#xff09;&#xff0c;意思是A元素可以覆盖B元素&…

十道海量数据处理面试题与十个方法大总结

1. 给定a、b两个文件&#xff0c;各存放50亿个url&#xff0c;每个url各占64字节&#xff0c;内存限制是4G&#xff0c;让你找出a、b文件共同的url&#xff1f; 方案1&#xff1a;可以估计每个文件安的大小为50G64320G&#xff0c;远远大于内存限制的4G。所以不可能将其完全加载…

Spring集成文件轮询和测试

我最近实施了一个小项目&#xff0c;在该项目中&#xff0c;我们必须轮询文件夹中的新文件&#xff0c;然后在文件内容上触发服务流。 Spring Integration非常适合此要求&#xff0c;因为它带有一个通道适配器 &#xff0c;该适配器可以扫描文件夹中的新文件&#xff0c;然后通…

Spark参数配置总结

转载于:https://www.cnblogs.com/lz3018/p/8128017.html

eclipse mysql生成实体类_Eclipse实现数据库反向生成实体类(pojo)-------(插件安装和实现步骤的说明)...

一、插件安装1.下载插件&#xff1a;http://jaist.dl.sourceforge.net/sourceforge/jboss/HibernateTools-3.2.4.Beta1-R200810311334.zip2.解压压缩包分别将其中的features和plugins放到Eclipse安装目录下对应的这2个文件里&#xff0c;重启Eclipse使其生效3.随便建个project&…

一些小技巧-重构

用box-shadow制造浮雕效果用box-shadow做简单的背景修饰长页面背景图不够用...... 01 用box-shadow制造浮雕效果 demo示例&#xff1a; src"http://demo.zhangruojun.com/static/demo/demo001/" frameborder"0" width"414" height"650&qu…

VM虚拟机显示不能铺满问题

关于使用虚拟机&#xff08;VMware&#xff09;时桌面显示不能铺满整个窗口时的设置操作&#xff1a; 步骤&#xff1a;虚拟机菜单下的&#xff1a;编辑-->首选项-->显示&#xff08;如下图&#xff09; 可以根据自己需求设置全屏下面的三个选项&#xff0c;确定后如果不…

Spring Data MongoDB中的审核实体

Spring Data MongoDB 1.2.0静默引入了新功能&#xff1a;支持基本审核 。 因为您不会在本文的官方参考中找到太多有关它的信息&#xff0c;所以我将展示它带来的好处&#xff0c;如何配置Spring以进行审计以及如何对文档进行注释以使其可审计。 审核使您可以声明性地告诉Spring…

Weex系列-项目工程

转载于:https://www.cnblogs.com/hacjy/p/8136460.html

MySQL索引效率对比_mysql下普通索引和唯一索引的效率对比

今天在我的虚拟机中布置了环境&#xff0c;测试抓图如下&#xff1a;抓的这几个都是第一次执行的&#xff0c;刷了几次后&#xff0c;取平均值&#xff0c;效率大致相同&#xff0c;而且如果在一个列上同时建唯一索引和普通索引的话&#xff0c;mysql会自动选择唯一索引。谷歌一…

css中的三种基本定位机制

css中的三种基本定位机制 a.普通文档流 b.定位&#xff1a;相对定位 绝对定位 固定定位 c.浮动 1.普通流中&#xff0c;元素位置由文档顺序和元素性质决定&#xff0c;块级元素从上到下依次排列&#xff0c;框之间的垂直距离由框的垂直margin值计算得到&#xff0c;行内元素在…

Mac再建管理员帐户

在Mac上添加用户没有成功, 把原来的管理员的用户群组修改后, 账户类型成为普通用户. 百度如何重新添加管理员: Mac上修改用户名后&#xff0c;无法用管理员账号和密码登录. 按照超过方法下去, 出现问题: 1, Command S之后, 命令行谈话一线转到登录. 重复几次后居然登录后进入命…

spring以及json,fastjson和jackson

&#xff08;一&#xff09; RestController 以及 RequestBody spring的这两个注解都需要使用对应的 message-converters 实现 pojo到字符串的转换&#xff0c; 需要配置实现了 GenericHttpMessageConverter 接口的实现类GenericHttpMessageConverter 父接口为HttpMessageConve…

JPA 2 | 动态查询与命名查询

JPA有自己的查询语言&#xff0c;称为JPQL。 JPQL与SQL非常相似&#xff0c;主要区别在于JPQL与应用程序中定义的实体一起使用&#xff0c;而SQL与数据库中定义的表和列名称一起使用。 在定义将对定义的Entity类执行CRUD操作的JPA查询时&#xff0c;JPA为我们提供了多种选择。 …

turtle 函数 方法_学python第十一节:turtle深入 了解

学python第十一节&#xff1a;深入分析turtleTurtle是一个直观有趣的图形绘制函数。这节课对turtle的以下几点进行补充&#xff1a;在蟒蛇绘制代码中提到过import 库引用保留字的函数&#xff0c;是补充python程序功能的方式&#xff0c;使用2种编写格式&#xff1a; 第一种引用…