Guava的基础功能与集合
Guava
文章目录
- Guava
- 一、 guava 是什么
- 二、 基础功能:更开心的使用Java语言
- 2.1 使用和避免null
- 2.2 先决条件:Preconditions
- (1)Preconditions 类
- (2)有条件的失败
- 2.3 排序:Ordering
- (1)常见的排序通过静态方法提供
- (2)将一个预先存在的Comparator变成Ordering
- (3)创建一个自定义的Ordering:直接扩展Ordering
- (4)比较器链
- (5)比较器链的调用:从右往左的调用
- 2.4 Objects中通用的对象方法:equals、hashCode、toString
- (1)`Objects.equal()`
- (2)`Objects.hashCode(filed1, filed2,...,filedn)`
- (3)`toString()` :`MoreObjects.toStringHelper()`
- (4)compare/compareTo:`ComparisonChain`
- 2.5 异常:Throwables
- (1)传播异常
- (2)异常的因果关系
- 三、集合:Collections
- 3.1 不可变集合:Immutable
- (1)不可变集合的好处
- (2)JDK的`Collections.unmodifiableXXX` 产生不可变集合存在的问题
- (3)不可变集合的使用场景(Guava的不可变集合中不允许null值)
- (4)创建不可变集合的几个方法
- (5)`copyOf` 非常的聪明:能在安全的情况下,避免复制数据结构。
- (6)`asList`
- (7)J可变集合接口与Guava中的不可变集合对应
- 3.2 新的集合类型
- (1)Multiset (接口)
- Multiset 的使用
- Multiset并不是一个Map
- Multiset的实现
- SortedMultiset
- (2)Multimap
- Multimap介绍
- 创建Multimap
- Multimap 的修改
- Multimap支持多种强大的视图
- Multimap并不是一个Map
- Multimap的实现
- (3)Bimap
- Bimap介绍
- Bimap的具体实现
- (4)Table
- Table的使用
- Table的实现
- (5)ClassToInstanceMap
- (6)RangeSet
- RangeSet 介绍
- RangeSet视图
- 查询操作
- (7)RangeMap
- RangeMap 介绍
- 视图
- 3.3 强大的集合工具
- (1)集合工具类(辅助类)与接口的对应
- (2)静态的方式构造集合对象
- (3)Iterables
- Iterables 的特点:惰性
- Iterables常用的静态方法
- 类集合操作
- (4)Lists
- (5)比较
- (6)Sets
- SetView
- 其它的操作
- 静态工厂
- (7)Maps
- `Maps.uniqueIndex(Iterable, Function) `
- `Maps.difference(Map, Map)`
- BiMap工具
- 静态工厂
- (8)Multisets
- (9)Multimaps
- index(Iterable, Function),返回一个不可变集合
- invertFrom(Multimap toInvert, Multimap dest)
- forMap(Map) 将一个Map转成SetMultimap
- 包装器:Multimaps提供了传统的包装方法
- (10)Tables
- customTable
- transpose 将行和列到转
- 包装Table
- 3.4 扩展工具
- (1)Forwarding 装饰器
- (2)PeekingIterator
- (3)AbstractIterator
- (4)AbstractSequentialIterator
一、 guava 是什么
guava是来自Google的Java核心类库。包含了新的集合类型(例如:复合map、复合set)、不可变集合,以及一些对于并发、I/O、hashing、缓存、原型、字符串等的通用功能。guava被广泛使用在Google的项目中,也被广泛的使用在其他公司里。
二、 基础功能:更开心的使用Java语言
2.1 使用和避免null
null 是一个模棱两可的概念,能引起让人困惑的错误。很多Guava的通用功能在遇到null的时候会拒绝并快速失败。而不是盲目的接受它们。
####(1)使用和避免null:Optional
粗心使用null可能造成令人震惊的各种错误。
另外,null是一个令人不愉快的模糊概念,很少能从返回的null值明确的推测出其代表的含义。例如Map.get(key),返回null,既可以代表在map中的值是null,也可以代表value不再map中。null即可以代表失败,也是代表成功,它能代表几乎任何东西。使用null以外的东西,可以让意思更加明确。
Guava提供了很多特征,不仅帮助你让在需要使用null的时候轻松使用null,也让你避免使用null。
Optional:
创建Optionnal,静态方法
Optional.of(T t)
当遇到null值当时候,就会快速失败;Optional.obsent()
创建某一个缺少某个值当类型当对象;Optional.fromNullable(T t)
将一个可能为非空的引用转换为Optional;
查询方法,非静态方法
optional.isPresent()
判断是否存在optional.get()
如果不存在将抛出异常optional.or(T t)
如果不存在返回定义的默认值optional.orNull()
如果不存在返回nulloptional.asSet()
返回一个不可变的集合,有一值,或者一个空集合
重点是什么?
除了通过null值一个名称增加了可读性,最大的优势的它的操作是白痴操作。如果你想让你的程序在任何时候都能编译通过,它强迫你去考虑值不存在的情况,因为你不得不积极的展开Optional并去处理这种情况。
它是极其相关的,当返回的值既可能是存在也可能不存在的时候。比起你要实现other.method方法的时候忘记a可能为null值,你更容器忘了
other.method(a, b)
的返回值是null。让返回值是Optional类型,就会避免这种情况的发生。便利的方法
MoreObjects.firstNonNull(T t, T t2)
无论什么时候你想让一个null值被一个默认值代替,使用MoreObjects.firstNonNull
。如果两个值都为空,将报空指针异常。如果使用Optional,则有一个更好的代替方案,
or(T t)
一些用于处理String类型null的方法,是在Strings中提供,具体来说,我们提供了如下的名称:
Strings.nullToEmpty(String)
Strings.isNullOrEmpty(String)
Strings.emptyToNull(String)
我们想要强调这些方法主要是为了面对令人不愉快的APIs交互,它们等同于null 字符串或空字符串。每次你要写代码合并null 字符串和空字符串的时候,Guava团队都在哭泣。(如果将null字符串和空字符串作为不同的东西对待,那是好多,如果将它们作为相同的东西对待就是会有令人不安的代码味道。)
2.2 先决条件:Preconditions
让方法更容易测试先决条件。
(1)Preconditions 类
Guava提供了很多先决条件的检查工具在Preconditions类中。强烈推荐通过静态导入的方式使用它们。
每一个方法都有三个变种:
没有额外的参数,任何异常将会抛出,不带任何错误信息;
带有一个额外的Object参数,任何异常将会抛出,带有一个object.toString的错误消息;
带有一个额外的String类型的参数,并带有任意数量的额外的Object参数。这种行为有时候更像printf,但是它为了GWT的兼容性和高效性,仅仅允许
%s
标识符。注意:
checkNotNull
、checkArgument
、checkState
又很多的重载方法,获取原始类型和对象参数,而不是一个可变数组。在绝大多数情况下,这种调用可以避免上述调用时进行原始类型的装箱和可变数组的分配。
第三种变体的示例代码:
import static com.google.common.base.Preconditions.*;
public class UsingAndAvoidNull {public void preconditionTest(int i, int j) {checkArgument(i>0, "Argument was %s but expected nonnegative", i);checkArgument(i>j, "Expected i > j, but %s >= %s", i, j);}
}
方法签名(不包含额外的参数) | 描述 | 在失败的时候抛出异常 |
---|---|---|
checkArgument(boolean) | 检查boolean是true,用于验证方法的参数。 |
IllegalArgumentException
|
checkNotNull(T) |
检查值不为null,如果在行内使用checkNotNull(value) ,将直接返回value值。
|
NullPointerException |
checkState(boolean) | 检查对象的状态,不依赖方法的参数。例如,一个Iterator可以使用这个方法检查next有值能够调用,在调用remove之前 | IllegalStateException |
checkElementIndex(int index, int size) | 检查索引是有效的,对于list、String、或者指定大小的数组。元素的索引从0(包含)到size(不包含)。不能直接传递list、string、或数组。仅传递它的大小,返回索引值。 | IndexOutOfBoundsException |
checkPositionIndex(int index, int size) | 检查是有一个位置,对于list、string、或指定大小的数组。位置的值从0(包含)到size(不包含)。不能直接传递list、string、数组,只需要传递它们的大小。最后返回index。 | IndexOutOfBoundsException |
checkPositionIndexes(int start,int end, int index) | 检查index是否在[start, end]范围内,其中end值要大于等于start。 | IndexOutOfBoundsException |
可变参数“printf-style”风格的异常消息。这个优势是我们推荐使用checkNotNull,而不是Objects.requireNotNull的原因。
建议将先决条件分成不同的行,它能帮助你在调试期间定位出哪个先决条件失败了。此外,你应该提供有用的错误信息。
(2)有条件的失败
一个有条件的失败或者运行时检查,是任何一段代码当且仅当一个boolean条件成立的时候抛出异常。在好的软件设计中这种代码很常见。
一个非常容易的处理,可以用相同的方法:if(!condition) {throw new RunTimeException();}
。但是如果你花一点时间去考虑你正在执行的检查性质,然后用更合适的方式处理它,就能让你的代码更容易理解,错误也更容易诊断。
这里有一些重要的运行时检查:
先觉条件的检查。
确认调用的公共方法是服从方法规范要求的。例如,一个sqrt函数,仅接受非负参数。
IllegalArgumentException、IllegalStateException
传统的断言检查。
仅应该在以某种方式类本身被破坏的时候失败。
assert、AssertionError
确认检查。
当你对一个要使用的API严重缺乏信心的时候,要对它进行检查。
VerifyException
测试断言。仅仅在测试类中被发现,确保测试代码是符合规范要求的。
注意,这类断言的代码和生产代码的断言几乎没有共同之处。
assertThat、assertEquals、AssertionError
不可能条件检查。它是不可能失败,除非我们代码被修改了,或者严重违反了底层平台的行为。
AssertionError
一个异常的结果。方法没有提供预期的结果
2.3 排序:Ordering
Guava有强大的Comparator类。
从本质上讲,Ordering 无非是一个Comparator实现。Ordering 依赖于Collector的方法(例如,Collections.max)。此外Ordering通过链式方法来增强和调整现有的比较器。
(1)常见的排序通过静态方法提供
Ordering.natural()
对可比较类型进行自然排序Ordering.usingToString()
根据toString()返回的字符串表示形式的字典顺序比较对象。
(2)将一个预先存在的Comparator变成Ordering
Ordering.from(Comparator)
(3)创建一个自定义的Ordering:直接扩展Ordering
Ordering byLenOrdering = new Ordering<String>() {@Overridepublic int compare(String s1, String s2) {return Integer.compare(s1.length(), s2.length());}};
(4)比较器链
通过包装Ordering获取派生的顺序。一些常用的变体包含如下:
reverse()
逆序nullsFirst()
null值放到最前面,非空值按照Ordering定义的排序进行排序;nullsLast()
null值放在最后,非空值按照Ordering定义的排序进行排序;compound()
组合两个比较器,当第一个比较器无法比较大小时,采用第二个比较器进行比较;// 按照字符串的长度进行比较,如果长度相同按照字母表顺序进行比较 Collections.sort(names, byLenOrdering.compound(Ordering.usingToString()));
lexicographical()
对Iterator列表进行排序,类比Ordering.usingToString()
,这里每个可对比的单元是Iterator中的一个元素。List<List<String>> groups = new ArrayList<>();groups.add(new ArrayList<>(Arrays.asList("aa", "ff")));groups.add(new ArrayList<>(Arrays.asList("aa", "bb", "cc")));System.out.println(groups); // [[aa, ff], [aa, bb, cc]]Collections.sort(groups, Ordering.usingToString().lexicographical());System.out.println(groups); // [[aa, bb, cc], [aa, ff]]
onResultOf(Function)
把比较器的元素使用Function函数转化成一个值result,再对这个值应用Ordering的比较方法。result的排序顺序就是最后的排序顺序。
例如,你要对Foo列表进行排序:
class Foo {String sortedBy;int notSortedBy;
}Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf((Foo foo)->foo.sortedBy);
(5)比较器链的调用:从右往左的调用
Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf((Foo foo)->foo.sortedBy);
比如上面的调用器链,先查找Foo的sortedBy字段,然后将所有null元素移动到最前面,再对剩下的顺序进行自然排序。
之所以,出现这个从右往左的调用方式,是因为每个调用链都是将前面一个Ordering包装成一个新的Ordering。
注意:compound()
是调用链里的例外,它是从左往右调用。
调用链太长,不容易理解。推荐把调用链控制在三个以内。
####(6)应用
greatestOf(Iterable iterable, int k)
对元素按照从大到小排序,并返回前k个元素List<Integer> result = Ordering.natural().greatestOf(new ArrayList<>(Arrays.asList(3, 5, 1)), 2);
leastOf(Iterable iterable, int k)
对元素按照从小到大排序,并返回前k个元素isOrdered(Iterable iterable)
测试是否为按照递增的顺序排序boolean ordered = Ordering.natural().isOrdered(result);
isStrictlyOrdered(Iterable iterable)
测试是否按照递减的顺序排序sortedCopy(Iterable iterable)
返回一个新的已经排序的列表,原来的列表顺序不会变List<Integer> nums = new ArrayList<>(Arrays.asList(4, 1, 3));List<Integer> resultCopy = Ordering.natural().sortedCopy(nums);System.out.println(resultCopy); // [1, 3, 4]System.out.println(nums); // [4, 1, 3]
immutableSortedCopy(Iterable iterable)
类似sortedCopy,只不过返回一个不可变的副本min(E e1, E e2)
返回最小的值,如果两个值相等,返回第一个元素。Integer min = Ordering.natural().min(5, 1);
max(E e1, E e2)
返回最大值,如果两个值相等,返回第一个元素。min(E, E, E...)
对比多个值,返回最小值,如果有多个最小值,返回第一个max(E, E, E...)
对比多个值,返回最大值,如果有多个最大值,返回第一个min(Iterable)
、max(Iterable)
返回列表中的最小值或最大值,如果列表为空,抛出异常。
2.4 Objects中通用的对象方法:equals、hashCode、toString
简化了实现对象的方法,例如hashcode()、toString()。
(1)Objects.equal()
可以避免在比较时由于null值带来麻烦。在JDK7中也引入的Objects,里面的equals() 方法是等价的。
Objects.equal("a", "a"); // returns true
Objects.equal(null, "a"); // returns false
Objects.equal("a", null); // returns false
Objects.equal(null, null); // returns true
(2)Objects.hashCode(filed1, filed2,...,filedn)
对一系列顺序的字段进行hash。
在JDK7中引入的Objects中,里面的hash()
方法和上面是等价的。
(3)toString()
:MoreObjects.toStringHelper()
toString() 方法对于调试代码没有用,但很难写,可以使用MoreObjects.toStringHelper()
方法来创建一个有用的toString方法。
// MyObject{x=1, y=cc}
MoreObjects.toStringHelper("MyObject").add("x", 1).add("y", "cc").toString();
(4)compare/compareTo:ComparisonChain
在实现Comparator 或 Comparable的时候非常痛苦,可以使用ComparisonChain来进化工作:
@Overridepublic int compareTo(Product o) {return ComparisonChain.start().compare(this.name, o.name).compare(this.num, o.name).compare(this.typeEnum, o.typeEnum, Ordering.natural()).result();}
ComparisonChain 采用懒工作模式:它执行比较,直到找到一个非零的结果,然后忽略进一步的输入。
2.5 异常:Throwables
Guava中的Throwables能够简化异常的处理。
(1)传播异常
Throwables.propagateIfPossible(Throwable, Class<X extends Throwable>) throws X;
只有当它是RuntimeException、Error、或X的时候,它才会按照原样抛出异常Throwables.throwIfInstanceOf(Throwable, Class<x extends Exeption>) throws X;
当且仅当它是X类型的异常时,按照原样传播可抛出异常Throwables.throwIfUnchecked(Throwable)
仅当它是RuntimeExeption或Error的时候,才按照原样抛出异常
(2)异常的因果关系
Throwables.getRootCause(Throwable)
获取根部异常Throwables.getCausalChain(Throwable)
获取异常链Throwables.getStackTraceAsString(Throwable)
将异常转化成字符串
Throwable throwable = new IllegalArgumentException(new ArrayStoreException());Throwable rootCause = Throwables.getRootCause(throwable); // java.lang.ArrayStoreExceptionSystem.out.println(rootCause);// [java.lang.IllegalArgumentException: java.lang.ArrayStoreException, java.lang.ArrayStoreException]System.out.println(Throwables.getCausalChain(throwable));/*java.lang.IllegalArgumentException: java.lang.ArrayStoreExceptionat com.hef.guava.baseutilities.ThrowDemo.main(ThrowDemo.java:16)
Caused by: java.lang.ArrayStoreException... 1 more*/System.out.println(Throwables.getStackTraceAsString(throwable));
三、集合:Collections
Guava扩展了Java的集合生态系统,这是Guava中非常成熟和流程的一部分。
3.1 不可变集合:Immutable
(1)不可变集合的好处
- 被不信任的类库使用是安全的
- 线程安全:能够被多个线程实现,而没有共享问题;
- 节省内存和时间
- 可以作为常量使用
为了集合对象创建不可变的副本是一个好的防御性的编程策略。Guava为每一个Collection类型(包括Guava自己的Collection类)都提供了简单易用的不可变集合版本。
(2)JDK的Collections.unmodifiableXXX
产生不可变集合存在的问题
- 笨重、冗长
- 不安全:只有当没有人持有原始集合引用的时候,它返回的结合才真正是不可变的;
- 低效:数据结构仍然保留着可变集合的开销,包括并发修改检查、在hash表中额外的空间等。
(3)不可变集合的使用场景(Guava的不可变集合中不允许null值)
当你不期望集合被修改,或者期望集合一致不变,一个好的策略是采用防御性拷贝,让其变成不可变集合。
重要:在Guava的不可变集合中,不允许有null值。如果你想使用带有null的不可变集合,可以使用Collections.unmodifiableXXX
。
(4)创建不可变集合的几个方法
通过
copyOf
方法,例如:ImmutableSet.copyOf(set)
使用
of
方法,例如:ImmutableSet.of
ImmutableMap.of("name", "world", "where", "beijing"); ImmutableSet.of("aa", "bb")
使用builder方法:
ImmutableSet<String> result = ImmutableSet.<String>builder().add("aaa").add("ccc").build();
除了有顺序的集合,顺序是根据存入数据结构的先后顺序。
(5)copyOf
非常的聪明:能在安全的情况下,避免复制数据结构。
ImmutableXXX.copyOf
避免线性时间的复制:
- 在恒定的时间,使用底层的数据结构。例如,
ImmutableSet.copyOf(ImutableList)
, 不能再固定时间完成。 - 它不会导致内存泄露。例如,如果有一个
ImmutableList<String> hugeList
,使用ImmutableList.copyOf(hugeList.subList(0, 10))
,将会执行一个明确的拷贝。从而避免持有对hugeList不必要的引用; - 不会改变语义。例如,
ImmutableSet.copyOf(myImmutableSortedSet)
将执行明确的拷贝,因为hashCode和equals在ImmutableSet和ImmutableSorted中的语义是不一样的。
这将能为防御性编码风格带来最小的性能负担。
(6)asList
便可变集合通过asList
方法就能得到一个ImmutableList
。
(7)J可变集合接口与Guava中的不可变集合对应
JDK中的接口 | JDK 还是 Guava | Guava中的不可变集合版本 |
---|---|---|
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
SortedSet/NavigableSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
SortedMap | JDK | Immutable |
MultiSet | Guava | ImmutableMultiSet |
SortedMultiSet | Guava | ImmutableSortedMultiSet |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
ClassToInstanceMap | Guava | ImmutableClassToInstanceMap |
Table | Guava | ImmutableTable |
3.2 新的集合类型
Guava中定义的很多新的集合,它们被广泛的使用。这些新的集合遵循JDK的集合接口约定。
(1)Multiset (接口)
Multiset 的使用
一个场景:统计单词在文章中出现的次数。传统的做法如下:
Map<String, Integer> result = new HashMap<>();for (String word : words) {if (result.containsKey(word)) {result.put(word,result.get(word)+1);}else {result.put(word, 1);}}
这样做容易出错,还不利于统计出一些有用的信息,例如,单词的总数。
使用Multiset就可以很简单的这样写:
Multiset<String> set = HashMultiset.create();set.addAll(words);
- 当被当作了一个普通的Collection的时候,Multiset的行为类似一个无序的ArrayList;
- 调用
add(E)
方法,添加一个指定的元素; iterator()
方法,遍历每个元素每次出现(元素每出现一次就会被遍历一次);size()
方法,获取所有元素出现的总次数;addAll(Collection)
添加多个元素添加进去;
- 调用
- 额外的查询操作,已经查询性能,就跟
Map<String, Integer>
一样:count(Object)
返回某个元素出现的总次数。对于HashMultiset,count的时间复杂度为O(1);对于TreeMultiset,count的时间复杂度是O(logn);entrySet()
返回Set<Multiset.entry<E>>
,工作机制类似于Map的entrySet;elementSet()
返回不同元素的集合列表,类似于Map的keySet();- Multiset 实现的内存消耗是和不同元素的数量是成线性相关的;
setCount(E, int)
设置某个元素,出现的次数remove(E, int)
将某个元素减少指定次数;
Multiset并不是一个Map
注意,Multiset并不是一个Map<E,Integer>
,尽管它是Multiset的一部分。Multiset真的是Collection类型。其它值得注意的有:
- Multiset仅仅拥有正数的count。没有元素的count值是负数。并且计数为0的元素,是不在Multiset中,它不会出现在elementSet() 和entrySet() 中;
multiset.size()
返回的是集合的大小。每次执行add操作,size都会加1;multiset.iterator()
迭代的是每个元素的所有次数。也就是迭代的次数等于size;- Multiset 支持添加元素、移除元素,直接设置元素的个数。setCount(element, 0) 等于把一个元素出现的所有次数移除;
multiset.count(E)
对于不在Multiset中的元素,总是返回0;
Multiset的实现
map | Multiset | 是否支持null |
---|---|---|
HashMap | HashMultiset | YES |
TreeMap | TreeMultiset | YES |
LinkedHashMap | LinkedHashMultiset | YES |
ConcurrentHashMap | ConcurrentHashMultiset | NO |
ImmutableMap | ImmutableMultiset | NO |
SortedMultiset
SortedMultiset 接口是Multiset的变体。它能有效的获取在特定范围的子集。例如:
SortedMultiset<Comparable<Integer>> result = set.subMultiset(3, BoundType.OPEN, 9, BoundType.CLOSED);
TreeMultiset 实现了 SortedMultiset接口。
(2)Multimap
Multimap介绍
Guava的Multimap很容见处理key到多个value的映射。
有两种思路理解Multimap概念:
- 它是一个集合:里面是单个key到单个value到映射;
- 作为key到多个value到映射;
一帮情况下,我们最好把Multimap接口作为第一视图,也可以用另一种视角,通过调用asMap()
方法得到Map<K, Collection<V>>
。
非常重要的是,在Multimap中没有key会对应一个空集合。也就是说,一个key至少对应一个值,或者key不存在Multimap中。
很少直接使用Multimap接口,经常使用ListMultimap或SetMultimap,它们的key分别对应List和Set。
创建Multimap
创建Multimap最简单直接的方式是通过MultimapBuilder来创建:
ListMultimap<String, Integer> listMultimap =MultimapBuilder.hashKeys().arrayListValues().build();
也可以直接在实现类上使用create() 方法:
ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
Multimap 的修改
Multimap.get(key)
通过这个方法返回指定key关联的values;
修改Multimap底层的内容:
ListMultimap<String, Integer> listMultimap =MultimapBuilder.hashKeys().arrayListValues().build();List<Integer> nums = listMultimap.get("name");System.out.println(nums); // []nums.clear();nums.add(23);nums.add(21);System.out.println(listMultimap.get("name")); // [23, 21]
其它修改Multimap底层的方法:
put(K, V)
添加一个K 及相关的V;等价于multimap.get(K).add(V)
putAll(K, Iterable<V>)
添加K到每一个V的联系;等价于Iterables.addAll(multimap.get(K), Iterable<V>)
remove(K, V)
移除一个K到V的关系,如果Multimap改变了,返回true;等价于multimap.get(K).remove(V)
removeAll(K)
移除所有与K有关的关系,并返回合适的集合类型;等价于multimap.get(K).clear()
replaceValues(K, Iterable<V>)
清空现有的key相关的关系,并建立新的关系,返回原来与Key相关联的value集合;等价于multimap.get(K).clear(); Iterables.addAll(multimap.get(K), Iterable<V>)
Multimap支持多种强大的视图
asMap
将Multimap作为Map<K, Collection<V>>
。返回的Map支持remove,也支持改变写入的集合。但是map不支持put或putAll。entries
返回Collection<Map.Entry<K, V>>
所有Multimap的关系keySet
返回所有的key集合;keys
返回Multiset。可以移除元素,但是不能添加;values
返回所有的values。类似于Iterables.concat(listMultimap.asMap().values())
Multimap并不是一个Map
一个Multimap<K, V>
并不是一个Map<K, Collection<V>>
,尽管这样的Map能够用Multimap来实现。值得注意的不同如下:
Multimap.get(K)
总是返回一个非null,可能为空的集合。返回的集合允许你添加与key有关系的value;- 如果你想让K不在Multimap中的时候,调用get方法返回null。那么先使用asMap() 方法得到
Map<K, Collection<V>>
视图。也可以使用Multimaps
的静态方法asMap
; Multimap.containsKey(K)
只有当存在K的关系,才会返回true。Multimap.entries()
返回所有key与value的关系对;如果要获取key-collection,使用Multimap.asMap().entrySet()
Multimap.size()
将返回所有Key的关系对数量,而不是不同key对个数。使用Multimap.asMap().size()
可以获取不同key对个数;
Multimap的实现
Multimap提供了很多实现,首先创建Multimap实现的方式是通过MultimapBuilder。
实现 | Key的行为像 | value的行为像 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap | LinkedHashMap | LinkedList |
LinkedHashMultimap | LinkedHashMap | LinkedHashSet |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
这些实现中,除了不可变集合,其它的key和value都可以为空。
可以自定义自己的实现:Multimaps.newMultimap(Map, Supplier<Collection>)
(3)Bimap
Bimap介绍
把values转成keys的传统方式是维护两个独立的map,并同步更新它们。但是这是易于出错的。当map中已经存在value的时候会造成混乱,例如:
Map<String, Integer> nameToId = Maps.newHashMap();Map<Integer, String> idToName = Maps.newHashMap();nameToId.put("Bob", 42);idToName.put(42, "Bob");// 如果 Bob 或 42 已经存在了,会怎么样?// 诡异的bug将会出现,如果我们忘了同步更新
一个Bimap<K, V>
是一个Map<K, V>
:
- 允许我们将
Bimap<K, V>
反转,通过调用inverse()
方法; - 确保values是唯一的,让
values()
作为一个set集合;
如果你试图通过Bimap.put(key, value)
映射一个key到一个已经存在的value值上,会抛出异常:
BiMap<String, Integer> biMap = HashBiMap.create();biMap.put("Bob", 42);biMap.put("Job", 42);
/*
Exception in thread "main" java.lang.IllegalArgumentException: value already present: 42at com.google.common.collect.HashBiMap.put(HashBiMap.java:310)at com.google.common.collect.HashBiMap.put(HashBiMap.java:290)at com.hef.guava.collection.GuavaCollectionDemo.biMapTest(GuavaCollectionDemo.java:32)at com.hef.guava.collection.GuavaCollectionDemo.main(GuavaCollectionDemo.java:19)
*/
如果你想删除预先已经存在的值,可以通过bimap.forcePut(key, value)
:
BiMap<String, Integer> biMap = HashBiMap.create();biMap.put("Bob", 42);biMap.forcePut("Job", 42);System.out.println(biMap); // {Job=42}
Bimap的具体实现
key-value map | 双向map |
---|---|
HashMap | HashBimap |
ImmutableMap | ImmutableBimap |
EnumMap | EnumBimap |
EnumMap | EnumHashBimap |
(4)Table
Table的使用
类似一个二维表,由“行、列、值”三元素组成。这里的行和列,可以是任意类型。
通常,当你想要一次索引多个keys时,你可能会想起使用Map<FirstName,Map<LastName,Person>>
。Guava提供了一种新的集合类型Table
,它支持使用任何的行列,以展示不同的视图:
rowMap()
对于Table<R, C, V>
,将返回Map<R, Map<C, V>>
。- 同样的
rowKeySet()
将返回Set<R>
; row(r)
返回非null的Map<C, V>
,向这个返回值的Map中写数据,值将写入底层的Table中;- 类似的类方法被提供:
columnMap()
、columnKeySet()
、column(c)
cellSet()
将返回Set<Table.Cell<R, C, V>>
, 类似于Map.Entry;
Table的实现
HashBasedTable
, 它本质上是基于HashMap<R, HashMap<C, V>>
;TreeBasedTable
, 它本质上是基于TreeMap<R, TreeMap<C, V>>
;ImmutableTable
ArrayTable
需要在创建的时候指定行和列。能够提升速度和内存使用率;
(5)ClassToInstanceMap
有些使用想将不同的类型作为key,该类型的值作为value。就可以使用ClassToInstanceMap。从技术上说ClassToInstanceMap实现了Map<Class<? extends B>, B>
,换句话说,它是一个B类型到B类型值的映射。
ClassToInstanceMap有一个带有一个参数的构造,通过这个参数可以确定ClassToInstanceMap中B的上界类型。
除了扩展Map接口,ClassToInstanceMap还提供了两个方法:
T getInstance(Class<T>)
获取某个类型的值;putInstance(Class<T>, T)
设置某个类型的值;
Guava提供了两个有用的ClassToInstanceMap实现:
MutableClassToInstanceMap
ImmutableClassToInstanceMap
(6)RangeSet
RangeSet 介绍
RangeSet描述了一个没有关联的非空的范围。当添加一个范围到可变集合RangeSet到时候,任何有联系的范围将合并在一块,空的范围将被忽略。例如:
TreeRangeSet<Integer> rangeSet = TreeRangeSet.create();rangeSet.add(Range.closed(1, 10));System.out.println(rangeSet); // [[1..10]]rangeSet.add(Range.closedOpen(11, 15));System.out.println(rangeSet); // [[1..10], [11..15)]rangeSet.add(Range.closedOpen(15, 20));System.out.println(rangeSet);// [[1..10], [11..20)]rangeSet.add(Range.openClosed(0, 0));System.out.println(rangeSet); // [[1..10], [11..20)]rangeSet.remove(Range.open(5, 10));System.out.println(rangeSet); // [[1..5], [10..10], [11..20)]
RangeSet视图
complement()
展示rangeSet的补集TreeRangeSet<Integer> rangeSet = TreeRangeSet.create();rangeSet.add(Range.openClosed(2, 5));RangeSet<Integer> complement = rangeSet.complement();System.out.println(rangeSet); // [(2..5]]System.out.println(complement); // [(-∞..2], (5..+∞)]
subRangeSet(Range)
获取特定范围内的子集RangeSet<Integer> subRangeSet = rangeSet.subRangeSet(Range.closed(4, 5));
asRanges()
返回一个Set<Range>
,用于迭代所有子集asSet()
仅用于不可变集合。
查询操作
contains(C)
查询一个元素是否在RangSet的范围中rangeContaining(C)
返回包含某一个元素的范围encloses(Range)
测试一个范围是否在RangeSet中span()
返回包含RangeSet所有范围的最小范围
(7)RangeMap
RangeMap 介绍
范围到值的映射。RangeMap 不会合并两个相同的范围,即便两个相邻的范围映射到同一个值。
TreeRangeMap<Integer, String> rangeMap = TreeRangeMap.create();rangeMap.put(Range.closed(1, 10), "foo");System.out.println(rangeMap); // [[1..10]=foo]rangeMap.put(Range.open(3, 6), "bar");System.out.println(rangeMap); // [[1..3]=foo, (3..6)=bar, [6..10]=foo]rangeMap.put(Range.open(10, 20), "foo");System.out.println(rangeMap); // [[1..3]=foo, (3..6)=bar, [6..10]=foo, (10..20)=foo]rangeMap.remove(Range.closed(5, 11));System.out.println(rangeMap); // [[1..3]=foo, (3..5)=bar, (11..20)=foo]
视图
asMapOfRanges
返回Map<Range, V>
用于遍历RangeMapsubRangeMap(Range)
返回子的RangeMap
3.3 强大的集合工具
Guava提供了很多工具。
(1)集合工具类(辅助类)与接口的对应
接口 | JDK还是Guava | 对应Guava的工具类 |
---|---|---|
Collection | JDK | Collections2 |
List | JDK | Lists |
Set | JDK | Sets |
SortedSet | JDK | Sets |
Map | JDK | Maps |
SortedMap | JDK | Maps |
Queue | JDK | Queues |
Multiset | Guava | Multisets |
Multimap | Guava | Multimaps |
BiMap | Guava | Maps |
Table | Guava | Tables |
(2)静态的方式构造集合对象
在JDK7之前,构建一个泛型集合需要写令人不愉快的代码:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();
JDK7 的菱形运算符减少了这方面的麻烦:
List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();
Guava 提供了静态方法,通过泛形推断右侧的类型:
ArrayList<TypeThatsTooLongForItsOwnGood> list02 = Lists.newArrayList();
在创建集合的时候,可以非常方便的初始化集合元素:
ArrayList<String> list03 = Lists.newArrayList("aa", "bb", "cc");
通过静态工厂方法,初始化集合的大小:
// 明确值
ArrayList<String> list04 = Lists.newArrayListWithCapacity(10);
// 估计值
ArrayList<String> list05 = Lists.newArrayListWithExpectedSize(10);
HashSet<Integer> set = Sets.newHashSetWithExpectedSize(10);
(3)Iterables
Iterables 的特点:惰性
只要有可能,Guava更喜欢提供接受Iterable而不是Collection的程序。在Google里,一个集合实际上并没有存在主存中的现象是不稀奇的,这个集合来自数据库或来自其他的数据中心。因为没有抓取到所有元素,所以并不支持size() 操作。
因此,你希望看到的所有集合操作都能在Iterables中找到。Iterables的大多方法都支持接受一个Iterator的版本。
在Iterables类中它的绝大多数方法都是懒惰的:只有当绝对需要迭代的时候,迭代操作发生。返回Iterables的方法,返回的是一个懒惰的计算视图,而不是构建一个明确的集合。
Iterables常用的静态方法
concat(Iterable...)
连接多个Iterables,返回一个惰性视图;frequency
返回元素在集合中出现的频次对比
Collections.frequency(Collection, o)
。Multisetpartition(Iterable, int)
返回一个不可修改的Iterable,里面是分成指定大小的块。paddedPartition(Iterable, int)
分成指定大小的块,如果某一块的元素个数不足,用null补充。getFirst(Iterable, T default)
返回第一个元素,没有Iterable为空,返回默认值;getLast(Iterable)
返回最后一个元素,如果为空抛出异常getLast(Iterable, T default)
返回最后一个元素,如果没有返回默认值elementsEqual(Iterable, Iterable)
如果两个Iterable有相同的元素,则返回trueunmodifiableIterable(Iterable)
返回一个不可修改的视图limit(Iterable, int)
返回集合的前几个元素getOnlyElement(Iterable)
返回Iterable中仅有的一个元素。如果Iterable中有多个元素,将会报错getOnlyElement(Iterable, T default)
返回Iterable中仅有的一个元素,如果Iterable为空,返回默认值
类集合操作
一些方法在Iterable中不支持,但在Collection中支持。当向Iterables中下面的这些方法传递Collection的时候,实际的操作会调用Collection接口的方法:
addAll(Collection, Iterable)
contains(Iterable, o)
removeAll(Iterable, Collection)
retainAll(Iterable removeFrom, Collection retain)
size(Iterable)
toArray(Iterable, Class<T>)
isEmpty(Iterable)
get(Iterable, int)
类似List.get(i)toString(Iterable)
(4)Lists
partition(List, int)
将list切分成若干个大小为size的小块。reverse(List)
反转list中的元素
静态工厂方法:
Lists.newLinkedList()
Lists.newArrayList()
(5)比较
查找最小值、最大值。一个看似很简单,却因为考虑最小的分配、装箱、API的灵活性而变得复杂。
要对比的元素 | 刚好两个元素 | 超过两个元素 |
---|---|---|
不需要包装箱的基本数据类型 | Math(a, b) | Ints.max(a, b, c); |
Comparable类型的实例 | Comparators.max(a, b) | Collections.max(Arrays.asList(a, b, c)) |
使用自定义的Comparator | Comparators.max(a, b, comparator) | Collections.max(Arrays.asList(a, b, c), comparator) |
Comparators.max("aa", "bb", Comparator.comparingInt(String::length));Collections.max(Arrays.asList("aa", "bb"),Comparator.comparingInt(String::length));
(6)Sets
SetView
union(set, set)
获取两个set的并集intersection(set, set)
获取两个set的交集difference(Set, Set)
返回前一个集合与后一个集合不同的元素symmetricDifference(Set, Set)
差集,除去两个集合共有的元素
HashSet<String> set01 = Sets.newHashSet("aa", "bb", "cc");HashSet<String> set02 = Sets.newHashSet("ee", "bb", "ff");Sets.SetView<String> result = Sets.union(set01, set02);System.out.println(result);Sets.SetView<String> intersection = Sets.intersection(set01, set02);System.out.println(intersection);Sets.SetView<String> difference = Sets.difference(set01, set02);System.out.println(difference);Sets.SetView<String> difference1 = Sets.symmetricDifference(set01, set02);System.out.println(difference1);
这些操作返回的是SetView
:
- 可以直接把SetView当作Set使用,因为它实现了Set接口;
copyInto(Set)
将SetView中的元素拷贝到一个Set中;immutableCopy()
拷贝成一个不可变集合
其它的操作
cartesianProduct(Set...)
多个集合进行组合,形成笛卡尔积Sets.powerSet(Set)
获取元素的子集
HashSet<String> set01 = Sets.newHashSet("aa", "bb", "cc");
Set<Set<String>> sets = Sets.powerSet(set01);
// {{}, {"aa"}, {"bb"}, {"cc"}, {"aa", "bb"}, {"aa", "cc"}, {"bb", "cc"}, {"aa", "bb", "cc"}}
静态工厂
newHashSet()
newTreeSet()
newLinkedHashSet()
(7)Maps
Maps中有很多酷的方法,值得分别解释:
Maps.uniqueIndex(Iterable, Function)
解决有一堆对象(Iterable),这一些对象都有唯一的属性。想要根据属性查到对象。
// 假设这些字符串的长度是唯一的,想要根据长度查找相应的字符串
// 一旦这个唯一的属性不唯一就会报错
ArrayList<String> list = Lists.newArrayList("a", "bb", "ccc", "dddd", "eeeee", "ffffff");
ImmutableMap<Integer, String> lenMap = Maps.uniqueIndex(list, String::length);
String item = lenMap.get(5);
System.out.println(item);
Maps.difference(Map, Map)
根据MapDifference
的方法:
entriesInCommon()
key和value都匹配的项目entriesDiffering()
key相同,value不相同的项。返回值是Map<K, MapDifference.ValueDifference<V>>
通过MapDifference.ValueDifference能看到左右两边的值;entriesOnlyOnLeft()
返回key只在左边出现的元素对;entriesOnlyOnRight()
返回key只在右边出现的元素对;
Map<String, Integer> map01 = ImmutableMap.of("aa", 1, "bb", 2, "cc", 3);Map<String, Integer> map02 = ImmutableMap.of("aa", 2, "d", 2, "cc", 3);MapDifference<String, Integer> difference = Maps.difference(map01, map02);Map<String, Integer> entriesInCommon = difference.entriesInCommon();System.out.println(entriesInCommon); // {cc=3}Map<String, MapDifference.ValueDifference<Integer>> entriesDiffering = difference.entriesDiffering();System.out.println(entriesDiffering); // {aa=(1, 2)}Map<String, Integer> entriesOnlyOnLeft = difference.entriesOnlyOnLeft();System.out.println(entriesOnlyOnLeft); // {bb=2}Map<String, Integer> entriesOnlyOnRight = difference.entriesOnlyOnRight();System.out.println(entriesOnlyOnRight); // {d=2}
BiMap工具
因为BiMap也是一个Map,所以它的工具方法也在Maps中:
Maps.synchronizedBiMap(BiMap)
等价的方法Collections.synchronizedMap(Map);
Maps.unmodifiableBiMap(BiMap)
等价的方法Collections.unmodifiableMap(Map);
静态工厂
Maps.newHashMap()
Maps.newLinkedHashMap()
Maps.newTreeMap()
Maps.newEnumMap()
Maps.newConcurrentMap()
Maps.newIdentityHashMap()
(8)Multisets
标准的Collection操作,例如containsAll,忽略了元素在Multiset的个数,仅关心元素是否在Multiset中。Multisets提供了很多方法考虑多重性操作:
containsOccurrences(superMultiset, subMultiset)
如果subMultiset中的所有元素都有subMultiset.count(o)<=superMultiset(o)
,则返回true。removeOccurrences(superMultiset, subMultiset)
根据subMultiset中数量移除superMultise的元素。retainOccurrences(superMultiset, subMultiset)
根据subMultiset中的元素个数,保留superMultiset中的元素,其余的都移除intersection(superMultiset, subMultiset)
根据元素个数获取交集
HashMultiset<String> multiset = HashMultiset.create();HashMultiset<String> multiset02 = HashMultiset.create();multiset.addAll(Arrays.asList("aa", "bb", "aa", "bb", "cc", "cc"));multiset02.addAll(Arrays.asList("aa", "bb", "aa", "bb", "cc", "dd"));Multiset<String> intersection = Multisets.intersection(multiset, multiset02);System.out.println(intersection); // [aa x 2, bb x 2, cc]
Multisets中的其他工具方法:
copyHighestCountFirst(multiset)
返回一个不可变的Multiset,并且迭代的时候元素频次最高的在前面;unmodifiableMultiset(multiset)
返回一个不可修改的MultisetunmodifiableSortedMultiset(SortedMultiset)
返回一个有序的不可修改的Multiset
(9)Multimaps
Multimap提供了很多常用的方法,下面分开解释:
index(Iterable, Function),返回一个不可变集合
和Maps.uniqueIndex
功能类似。Multimaps.index(Iterable, Function)
是把一批对象,属性相同的分成一组。可以根据属性找到一批对象。
ImmutableListMultimap<Integer, String> multimap1 = Multimaps.index(Lists.newArrayList("aa", "bb", "cc", "dd", "world"), String::length);System.out.println(multimap1);// {2=[aa, bb, cc, dd], 5=[world]}
invertFrom(Multimap toInvert, Multimap dest)
因为多个key可以对应一个value,多个value可以对应一个key
ListMultimap<String, Integer> multimap = MultimapBuilder.hashKeys().arrayListValues().build();multimap.put("name", 23);multimap.put("name", 21);multimap.put("value", 21);System.out.println(multimap); // {name=[23, 21], value=[21]}ListMultimap<Integer, String> multimap2 = Multimaps.invertFrom(multimap, MultimapBuilder.hashKeys().arrayListValues().build());System.out.println(multimap2); // {21=[name, value], 23=[name]}
对于ImmutableMultimap,可以直接调用invert() 方法。
forMap(Map) 将一个Map转成SetMultimap
Map<String, Integer> map = ImmutableMap.of("aa", 1, "bb", 1, "cc", 3);System.out.println(map); // {aa=1, bb=1, cc=3}SetMultimap<String, Integer> setMultimap = Multimaps.forMap(map);System.out.println(setMultimap); // {aa=[1], bb=[1], cc=[3]}HashMultimap<Integer, String> multimap3 = Multimaps.invertFrom(setMultimap, HashMultimap.<Integer, String>create());System.out.println(multimap3);// {1=[aa, bb], 3=[cc]}
包装器:Multimaps提供了传统的包装方法
Multimap类型 | 不可修改类型 | 同步类型 | 自定义类型 |
---|---|---|---|
Multimap | unmodifiableMultimap | synchronizedMultimap | newMultimap |
ListMultimap | unmodifiableListMultimap | synchronizedListMultimap | newListMultimap |
SetMultimap | unmodifiableSetMultimap | synchronizedSetMultimap | newSetMultimap |
SortedSetMultimap | unmodifiableSortedSetMultimap | synchronizedSortedSetMultimap | newSortedSetMultimap |
自定义类型,允许指定一个实现,能够在返回的Multimap中使用。
自定义类型需要提供一个Supplier方法,用于创建新的集合。
Multimap<String, Integer> multimap4 = Multimaps.newMultimap(new HashMap<String, Collection<Integer>>(), Lists::newArrayList);
(10)Tables
customTable
对比newXXXMultimap(Map, Supplier)
。customTable 可以指定row、col的数据结构。
Table<String, String, Integer> table = Tables.<String, String, Integer>newCustomTable(new HashMap<String, Map<String, Integer>>(), LinkedHashMap::new);
transpose 将行和列到转
Table<String, String, Integer> table =Tables.<String, String, Integer>newCustomTable(new HashMap<String, Map<String, Integer>>(),LinkedHashMap::new);table.put("name", "value", 2);System.out.println(table);Table<String, String, Integer> transpose =Tables.transpose(table);System.out.println(transpose);
包装Table
unmodifiableTable
转成不可修改的TableTable<String, String, Integer> unmodifiableTable = Tables.unmodifiableTable(table);
unmodifiableRowSortedTable
转成row排序的TableRowSortedTable<String, String, Integer> treeBasedTable = TreeBasedTable.create();treeBasedTable.put("name1", "name2", 23);RowSortedTable<String, String, Integer> unmodifiableRowSortedTable = Tables.unmodifiableRowSortedTable(treeBasedTable);
3.4 扩展工具
有些时候你需要写自己的集合扩展。或者当元素添加到集合汇总时,你想添加特定的行为;或者你想编写一个数据库支持的Iterable。Guava提供了很多工具让你做这些事情更容易。
(1)Forwarding 装饰器
对于各种集合接口。Guava提供了Forwarding抽象类简化了装饰器模式的使用。
Forwarding类定义了一个抽象方法delegate()
,你应该复写它并返回被装饰的对象。其他的方法就简单的委托给装饰者。例如,ForwardingList.get(i)
是简单的实现了delegate().get(i)
(2)PeekingIterator
通过Iterators.peekingIterator(Iteraotr) 得到PeekingIterator。其中的peek() 能够得到next() 元素
List<String> result = Lists.newArrayList("a", "b", "c");PeekingIterator<String> peekingIterator = Iterators.peekingIterator(result.iterator());// 能够打印5次 afor (int i = 0; i < 5; i++) {System.out.println(peekingIterator.peek());}
(3)AbstractIterator
通过AbstractIterator
可以很方便的实现自己的Iterator:
private Iterator<String> skipNullIterator(final Iterator<String> in){return new AbstractIterator<String>() {@CheckForNull@Overrideprotected String computeNext() {while (in.hasNext()) {String s = in.next();if (s!=null) {return s;}}return endOfData();}};}
(4)AbstractSequentialIterator
private Iterator<Integer> sequentialIterator() {return new AbstractSequentialIterator<Integer>(2) {@CheckForNull@Overrideprotected Integer computeNext(Integer previous) {return previous>100?null:previous*3;}};}
- 必须要有一个初始值;
- null作为迭代的结束;
- AbstractSequentialIterator不能用于迭代元素包含null的实现;
Guava的基础功能与集合相关推荐
- 服务器开发系列(三)——Linux与Windows操作系统基础功能对比
系列文章目录 服务器开发系列(一)--计算机硬件 服务器开发系列(二)--Jetson Xavier NX 文章目录 系列文章目录 前言 一.操作系统概述 二.Linux和Windows的应用场景 三 ...
- PySpark数据分析基础:PySpark基础功能及DataFrame操作基础语法详解
目录 前言 一.PySpark基础功能 1.Spark SQL 和DataFrame 2.Pandas API on Spark 3.Streaming 4.MLBase/MLlib 5.Spark ...
- Guava RateLimter 基础学习
Guava RateLimter 基础学习 平滑突发限流 平滑预热限流 原理分析--以平滑突发限流为例 缺点 结合Redis实现分布式 思路 平滑突发限流 public static void mai ...
- java核心技术 基础知识<集合并发part1>
文章目录 java核心技术 基础知识<集合&并发part1> 9 泛型程序设计 9.5 算法 9.6 遗留的集合 14 并发 14.2 中断线程 14.3 线程状态 14.4 线程 ...
- 完爆Facebook/GraphQL,APIJSON全方位对比解析(一)-基础功能
相关阅读: 完爆Facebook/GraphQL,APIJSON全方位对比解析(二)-权限控制 完爆Facebook/GraphQL,APIJSON全方位对比解析(三)-表关联查询 自APIJSON发 ...
- java实现用户登录注册功能(用集合框架来实现)
需求:实现用户登录注册功能(用集合框架来实现) 分析: A:需求的类和接口 1.用户类 UserBean 2.用户操作方法接口和实现类 UserDao UserDaoImpl 3.测试类 UserTe ...
- php实现文件夹管理器,php实现文件管理与基础功能操作
文件的基本操作 先来看一下PHP文件基础操作,请看强大注释 var_dump(filetype("./img/11.png")); //判断返回得是文件还是目录,返回sile为文件 ...
- 『Python基础-11』集合 (set)
# 『Python基础-11』集合 (set) 目录: 集合的基本知识 集合的创建 访问集合里的值 向集合set增加元素 移除集合中的元素 集合set的运算 1. 集合的基本知识 集合(set)是一个 ...
- 以服务的方式提供站点基础功能支持
Web站点除了提供内容展示,业务逻辑处理外,还有很多看不到的操作,如:错误日志,后台管理,权限分配,访问统计等.如果只有一个站点,完全可以根据需要逐步添加,但实际的情况是往往有多个子站点,这时这些重复 ...
最新文章
- java实现线程同步的方法_Java实现线程同步方法及原理详解
- 20-思科防火墙:Network Static NAT:网络静态NAT
- 发送请求获取响应内容(c#)
- java tif格式图片_java给tif格式图片加文字水印?
- 基于VSM的命名实体识别、歧义消解和指代消解
- python 进行一元线性回归并输出相关结果_Python实现一元线性回归实战
- 近日的思绪(外三首)
- 使用WebView监控网页加载状况,PerformanceMonitor,WebViewClient生命周期
- [转] 如何快速掌握一门新技术/语言/框架
- ubuntu18安装tim
- 不加群提取群成员_快速提取PPT上的文字!
- 阿里巴巴连接池mysql_阿里巴巴连接池(Druid)
- fstream 头文件作用
- 「Don‘t Make Me Think」 读后感
- Android中修改ScrollBar默认样式
- 深度解读DeepMind新作:史上最强GAN图像生成器—BigGAN
- 达梦归档校验工具dmrachk
- HNU OJ10320 穿越火线 简单模拟
- 网络安全 kali虚拟机中的渗透实验
- 黑客是怎么攻击网站的,管理员必知