一、Objects方法
1.equals
当一个对象中的字段可以为null时,实现Object.equals方法会很痛苦,因为不得不分别对它们进行null检查。使用Objects.equal帮助你执行null敏感的equals判断,从而避免抛出NullPointerException。例如:
Objects.equal("a", "a"); // returns true
Objects.equal(null, "a"); // returns false
Objects.equal("a", null); // returns false
Objects.equal(null, null); // returns true
ps:JDK7引入的Objects类提供了一样的方法Objects.equals。
2.hashCode
Guava的Objects.hashCode(Object...)会对传入的字段序列计算出合理的、顺序敏感的散列值。你可以使用Objects.hashCode(field1, field2, …, fieldn)来代替手动计算散列值。
Objects.hashCode("abc", 11, new User());
ps:JDK7引入的Objects类提供了一样的方法Objects.equals。
3.toString
使用 Objects.toStringHelper可以轻松编写有用的toString方法。例如:
// Returns "ClassName{x=1}"
Objects.toStringHelper(this).add("x", 1).toString();
// Returns "User{id=1, name=a}"
Objects.toStringHelper("User").add("id", 1).add("name", "a").toString();
4.compare/compareTo
实现一个比较器[Comparator],或者直接实现Comparable接口有时也伤不起。考虑一下这种情况:
class Person implements Comparable<Person> {
private String lastName;
private String firstName;
private int zipCode;
public int compareTo(Person other) {
int cmp = lastName.compareTo(other.lastName);
if (cmp != 0) {
return cmp;
}
cmp = firstName.compareTo(other.firstName);
if (cmp != 0) {
return cmp;
}
return Integer.compare(zipCode, other.zipCode);
}
}
使用Guava提供的 ComparisonChain,可以将compareTo方法改写为:
public int compareTo(Person that) {
return ComparisonChain.start()
.compare(this.lastName, that.lastName)
.compare(this.firstName, that.firstName)
.compare(this.zipCode, that.zipCode, Ordering.natural().nullsLast())
.result();
}
Ordering.natural().nullsLast() 表示根据数字字符串自然排序(从小到大),但把null值排到最后面。
二、集合类
1.不可变集合:ImmutableSet
为什么使用不可变集合:
1)当对象被不可信的库调用时,不可变形式是安全的;
2)不可变对象被多个线程调用时,不存在竞态条件问题
3)不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率(分析和测试细节);
4)不可变对象因为有固定不变,可以作为常量来安全使用。
不可变集合的创建:
1)copyOf方法
ImmutableSet.copyOf(bars)
2)of方法
ImmutableSet.of(“a”, “b”, “c”)
3)Builder工具
public static final ImmutableSet<Color> GOOGLE_COLORS =
ImmutableSet.<Color>builder()
.addAll(WEBSAFE_COLORS)
.add(new Color(0, 191, 255))
.build();
ps:对有序不可变集合 ImmutableSortedSet 来说,排序是在构造集合的时候完成的,如下,顺序为a, b, c, d
ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");
2. 带计数功能的集合:Multiset
Multiset以Map<T,Count>为存储结构,一种元素并没有被存多分,而且巧妙的利用iterator指针来模拟多份数据。可以用两种方式看待Multiset:
1)没有元素顺序限制的ArrayList<E>
2)Map<E, Integer>,键为元素,值为计数
统计一个词在文档中出现了多少次,传统的做法是这样的:
Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
Integer count = counts.get(word);
if (count == null) {
counts.put(word, 1);
} else {
counts.put(word, count + 1);
}
}
如果使用Multset就变成:
HashMultiset<String> multiSet = HashMultiset.create();
multiSet.addAll(words);
//count word “the”
Integer count = multiSet.count(“the”);
使用方法有:
方法 | 描述 |
count(E) | 给定元素在Multiset中的计数 |
elementSet() | Multiset中不重复元素的集合,类型为Set<E> |
entrySet() | 和Map的entrySet类似,返回Set<Multiset.Entry<E>>,其中包含的Entry支持getElement()和getCount()方法 |
add(E, int) | 增加给定元素在Multiset中的计数 |
remove(E, int) | 减少给定元素在Multiset中的计数 |
setCount(E, int) | 设置给定元素在Multiset中的计数,不可以为负数 |
size() | 返回集合元素的总个数(包括重复的元素) |
特点:
1)Multiset中的元素计数只能是正数。任何元素的计数都不能为负,也不能是0。elementSet()和entrySet()视图中也不会有这样的元素。
2)multiset.size()返回集合的大小,等同于所有元素计数的总和。对于不重复元素的个数,应使用elementSet().size()方法。(因此,add(E)把multiset.size()增加1)
3)multiset.iterator()会迭代重复元素,因此迭代长度等于multiset.size()。
4)Multiset支持直接增加、减少或设置元素的计数。setCount(elem, 0)等同于移除所有elem。
5)对multiset 中没有的元素,multiset.count(elem)始终返回0。
常用实现 Multiset 接口的类有:
1)HashMultiset: 元素存放于 HashMap
2)LinkedHashMultiset: 元素存放于 LinkedHashMap,即元素的排列顺序由第一次放入的顺序决定
3)TreeMultiset:元素被排序存放于TreeMap
4)EnumMultiset: 元素必须是 enum 类型
5)ImmutableMultiset: 不可修改的 Mutiset
3.键映射多个值:Multimap
可以用两种方式思考Multimap的概念:
1)键-单个值映射”的集合:a -> 1 a -> 2 a ->4 b -> 3 c -> 5
2)键-值集合映射”的映射:a -> [1, 2, 4] b -> 3 c -> 5
当需要 Map<K, Collection<V>>这样的结构时,如以下场景:
void putMyObject(String key, Object value) {
List<Object> myClassList = myClassListMap.get(key);
if(myClassList == null) {
myClassList = new ArrayList<object>();
myClassListMap.put(key,myClassList);
}
myClassList.add(value);
}
此时我们可以使用Multmap替换,如下面例子:
Multimap<String, String> myMultimap = ArrayListMultimap.create();
// Adding some key/value
myMultimap.put("Fruits", "Bannana");
myMultimap.put("Fruits", "Apple");
myMultimap.put("Fruits", "Pear");
myMultimap.put("Fruits", "Pear");
myMultimap.put("Vegetables", "Carrot");
// Getting the size
int size = myMultimap.size();
// Getting values
Collection<String> fruits = myMultimap.get("Fruits");
System.out.println(fruits); // [Bannana, Apple, Pear, Pear]
Multmap的接口实现:
@GwtCompatible
public interface Multimap<K, V> {
//返回Multimap集合的key、value pair的数量
int size();
//判断Multimap是否包含key、value pair
boolean isEmpty();
//判断Multimap中是否包含指定key的value值
boolean containsKey(@Nullable Object key);
//判断Multimap中是否包含指定value的key
boolean containsValue(@Nullable Object value);
//判断Multimap中是否包含指定的key-value pair的数据
boolean containsEntry(@Nullable Object key, @Nullable Object value);
//将数据加入到Multimap中
boolean put(@Nullable K key, @Nullable V value);
//删除Multimap中指定key-value pair
boolean remove(@Nullable Object key, @Nullable Object value);
//将指定的key-集合数据加入Multimap中
boolean putAll(@Nullable K key, Iterable<? extends V> values);
//将指定的Multimap和当前的Multimap合并
boolean putAll(Multimap<? extends K, ? extends V> multimap);
//替换指定key的value
Collection<V> replaceValues(@Nullable K key, Iterable<? extends V> values);
//删除Imultimap中的指定key数据
Collection<V> removeAll(@Nullable Object key);
//清空Imultimap中的数据
void clear();
//获取指定key的值
Collection<V> get(@Nullable K key);
//获取所有的key集合
Set<K> keySet();
Multiset<K> keys();
Collection<V> values();
Collection<Map.Entry<K, V>> entries();
Map<K, Collection<V>> asMap();
@Override
boolean equals(@Nullable Object obj);
@Override
int hashCode();
}
Multimap提供了多种形式的实现
实现 | 键行为类似 | 值行为类似 |
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap* | LinkedHashMap* | LinkedList* |
LinkedHashMultimap** | LinkedHashMap | LinkedHashMap |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
4.可反转map:BiMap
这个反转的map不是新的map对象,它实现了一种视图关联,这样你对于反转后的map的所有操作都会影响原先的map对象。
传统上,实现键值对的双向映射需要维护两个单独的map,并保持它们间的同步。但这种方式很容易出错,而且对于值已经在map中的情况,会变得非常混乱。例如:
Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
//如果"Bob"和42已经在map中了,会发生什么?
//如果我们忘了同步两个map,会有诡异的bug发生...
在BiMap中,可以使用 inverse()实现键值反转。如果你想把值反转成已经存在的键,会抛出IllegalArgumentException异常。如果想要强制覆盖该键的值,可以使用 BiMap.forcePut(key, value)。
例子:
public void BimapTest(){
BiMap<Integer,String> logfileMap = HashBiMap.create();
logfileMap.put(1,"a.log");
logfileMap.put(2,"b.log");
logfileMap.put(3,"c.log");
System.out.println("logfileMap:"+logfileMap);
BiMap<String,Integer> filelogMap = logfileMap.inverse();
System.out.println("filelogMap:"+filelogMap);
logfileMap.put(4,"d.log");
System.out.println("logfileMap:"+logfileMap);
System.out.println("filelogMap:"+filelogMap);
}
输出为:
logfileMap:{3=c.log, 2=b.log, 1=a.log}
filelogMap:{c.log=3, b.log=2, a.log=1}
logfileMap:{4=d.log, 3=c.log, 2=b.log, 1=a.log}
filelogMap:{d.log=4, c.log=3, b.log=2, a.log=1}
BiMap的各种实现:
键–值实现 | 值–键实现 | 对应的BiMap实现 |
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
三、集合工具类拓展
1.Lists
方法 | 描述 |
partition(List, int) | 把List按指定大小分割 |
reverse(List) | 返回给定List的反转视图。注: 如果List是不可变的,考虑改用ImmutableList.reverse()。 |
List countUp = Ints.asList(1, 2, 3, 4, 5);
List countDown = Lists.reverse(theList); // {5, 4, 3, 2, 1}
List<List> parts = Lists.partition(countUp, 2);//{{1,2}, {3,4}, {5}}
2.Sets
方法 |
union(Set, Set) |
intersection(Set, Set) |
difference(Set, Set) |
symmetricDifference(Set, Set) |
Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> intersection = Sets.intersection(primes,wordsWithPrimeLength);
// intersection包含"two", "three", "seven"
return intersection.immutableCopy();//可以使用交集,但不可变拷贝的读取效率更高
3.Maps
Maps.uniqueIndex(Iterable,Function)通常针对的场景是:有一组对象,它们在某个属性上分别有独一无二的值,而我们希望能够按照这个属性值查找对象,这个方法返回一个Map,键为Function返回的属性值,值为Iterable中相应的元素,因此我们可以反复用这个Map进行查找操作。
ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings,
new Function<String, Integer> () {
public Integer apply(String string) {
return string.length();
}
});
4.Multisets
方法 | 说明 | 和Collection方法的区别 |
containsOccurrences(Multiset sup, Multiset sub) | 对任意o,如果sub.count(o)<=super.count(o),返回true | Collection.containsAll忽略个数,而只关心sub的元素是否都在super中 |
removeOccurrences(Multiset removeFrom, Multiset toRemove) | 对toRemove中的重复元素,仅在removeFrom中删除相同个数。 | Collection.removeAll移除所有出现在toRemove的元素 |
retainOccurrences(Multiset removeFrom, Multiset toRetain) | 修改removeFrom,以保证任意o都符合removeFrom.count(o)<=toRetain.count(o) | Collection.retainAll保留所有出现在toRetain的元素 |
intersection(Multiset, Multiset) | 返回两个multiset的交集; | 没有类似方法 |
Multiset<String> multiset1 = HashMultiset.create();
multiset1.add("a", 2);
Multiset<String> multiset2 = HashMultiset.create();
multiset2.add("a", 5);
multiset1.containsAll(multiset2); //返回true;因为包含了所有不重复元素,
//虽然multiset1实际上包含2个"a",而multiset2包含5个"a"
Multisets.containsOccurrences(multiset1, multiset2); // returns false
multiset2.removeOccurrences(multiset1); // multiset2 现在包含3个"a"
multiset2.removeAll(multiset1);//multiset2移除所有"a",虽然multiset1只有2个"a"
multiset2.isEmpty(); // returns true
5.Multimaps
1)index
作为Maps.uniqueIndex的兄弟方法,Multimaps.index(Iterable, Function)通常针对的场景是:有一组对象,它们有共同的特定属性,我们希望按照这个属性的值查询对象,但属性值不一定是独一无二的。
比方说,我们想把字符串按长度分组。
ImmutableSet digits = ImmutableSet.of("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine");
Function<String, Integer> lengthFunction = new Function<String, Integer>() {
public Integer apply(String string) {
return string.length();
}
};
ImmutableListMultimap<Integer, String> digitsByLength= Multimaps.index(digits, lengthFunction);
/*
* digitsByLength maps:
* 3 => {"one", "two", "six"}
* 4 => {"zero", "four", "five", "nine"}
* 5 => {"three", "seven", "eight"}
*/
2)invertFrom
反转Multimap。
ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.putAll("b", Ints.asList(2, 4, 6));
multimap.putAll("a", Ints.asList(4, 2, 1));
multimap.putAll("c", Ints.asList(2, 5, 3));
TreeMultimap<Integer, String> inverse = Multimaps.invertFrom(multimap, TreeMultimap<String, Integer>.create());
//注意我们选择的实现,因为选了TreeMultimap,得到的反转结果是有序的
/*
* inverse maps:
* 1 => {"a"}
* 2 => {"a", "b", "c"}
* 3 => {"c"}
* 4 => {"a", "b"}
* 5 => {"c"}
* 6 => {"b"}
*/
3)forMap
把Map包装成SetMultimap,与Multimaps.invertFrom结合使用,可以把多对一的Map反转为一对多的Multimap。
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2);
SetMultimap<String, Integer> multimap = Multimaps.forMap(map);
// multimap:["a" => {1}, "b" => {1}, "c" => {2}]
Multimap<Integer, String> inverse = Multimaps.invertFrom(multimap, HashMultimap<Integer, String>.create());
// inverse:[1 => {"a","b"}, 2 => {"c"}]
四、缓存
适用条件:
Guava Cache适用于:
1)你愿意消耗一些内存空间来提升速度。
2)你预料到某些键会被查询一次以上。
3)缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
简单的例子:
public class GuavaCacheTest {
public static void main(String[] args) {
Cache<Integer, String> cache = CacheBuilder.newBuilder().build();
cache.put(1, "a");
System.out.println(cache.getIfPresent(1)); //输出 a
System.out.println(cache.getIfPresent(2)); //输出 null
}
}
如果没有缓存,计算值并缓存,
如果使用的 LoadingCache 需要在创建对象时重写 CacheLoader 方法;
如果使用 Cache 类则后续 get 方法中多传入一个 Callable 的对象并重写 call 方法,缓存不存在时用于计算并加到缓存,此方法更为灵活。
public class GuavaCacheTest {
public static void main(String[] args) {
LoadingCache<Integer, String> cache = CacheBuilder.newBuilder().build(
new CacheLoader<Integer, String>() {
@Override
public String load(Integer key) throws Exception {
return "key-" + key;
}
}
);
cache.put(1, "a");
System.out.println(cache.getIfPresent(1));
try {
System.out.println(cache.get(2));
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
=========================================================
或
public class GuavaCacheTest {
public static void main(String[] args) {
Cache<Integer, String> cache = CacheBuilder.newBuilder().build();
cache.put(1, "a");
try {
cache.get(2, new Callable<String>() {
@Override
public String call() throws Exception {
return "b";
}
});
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何释放内存,防止发生OOM:限量、定时、手动
1)限量:
CacheBuilder.maximumSize(long) 按个数来回收,
public class GuavaCacheTest {
public static void main(String[] args) {
Cache<Integer, String> cache = CacheBuilder.newBuilder().maximumSize(2).build();
cache.put(1, "a");
cache.put(2, "b");
cache.put(3, "c");
System.out.println(cache.asMap()); //输出{3=c, 2=b}
System.out.println(cache.getIfPresent(2)); //输出b,此处被使用,故为最新
cache.put(4, "d"); //输出{4=d, 2=b}
System.out.println(cache.asMap());
}
}
CacheBuilder.weigher(Weigher) 按权重来回收,CacheBuilder.maximumWeight(long)指定最大总权重。CacheBuilder.maximumSize(long),CacheBuilder.maximumWeight(long)是互斥的,只能二选一。
public class GuavaCacheTest {
public static void main(String[] args) {
Cache<Integer, Integer> cache = CacheBuilder.newBuilder().maximumWeight(100)
.weigher(new Weigher<Integer, Integer>() {
@Override
public int weigh(Integer key, Integer value) {
if (value % 2 == 0) {
return 20;
} else {
return 5;
}
}
}).build();
// 放偶数
for (int i = 0; i <= 20; i += 2) {
cache.put(i, i);
}
System.out.println(cache.asMap()); //输出{20=20, 18=18, 16=16, 14=14}
cache.invalidateAll(); //全部失效
for (int i = 1; i < 10; i += 1) {
cache.put(i, i);
}
System.out.println(cache.asMap()); //输出{6=6, 5=5, 8=8, 7=7, 2=2, 9=9, 3=3, 4=4}
}
}
2)定时
expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问 来回收
public class GuavaCacheTest {
public static void main(String[] args) {
Cache<Integer, Integer> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).build();
cache.put(1,1);
cache.put(2,2);
System.out.println(cache.asMap()); //输出{1=1, 2=2}
try {
Thread.sleep(1000);
cache.getIfPresent(2); //使用2,该值会重新计时
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(cache.asMap()); //输出{2=2}
}
}
3)限量+定时
Cache<Integer, Integer> cache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(3, TimeUnit.SECONDS).build();
4)手动
个别清除:Cache.invalidate(key)
批量清除:Cache.invalidateAll(keys)
清除所有缓存项:Cache.invalidateAll()
public class GuavaCacheTest {
public static void main(String[] args) {
Cache<Integer, Integer> cache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(3, TimeUnit.SECONDS).build();
cache.put(1, 1);
cache.put(2, 2);
cache.invalidateAll(Lists.newArrayList(1));
System.out.println(cache.asMap()); //{2=2}
cache.put(3, 3);
System.out.println(cache.asMap()); //{2=2, 3=3}
cache.invalidateAll();
System.out.println(cache.asMap()); //{}
}
}
监听器:可在加入缓存或者清除是做额外操作
public class GuavaCacheTest {
public static void main(String[] args) {
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
System.out.println("remove key[" + notification.getKey() + "],value[" + notification.getValue() + "],remove reason[" + notification.getCause() + "]");
}
}).recordStats().build(
new CacheLoader<Integer, Integer>() {
@Override
public Integer load(Integer key) throws Exception {
return 2;
}
}
);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.asMap()); //{2=2, 1=1}
cache.invalidateAll(); //remove key[1], value[1], remove reason[EXPLICIT]
//remove key[2], value[2], remove reason[EXPLICIT]
System.out.println(cache.asMap()); //{}
cache.put(3, 3);
try {
System.out.println(cache.getUnchecked(3)); //3
Thread.sleep(4000); //remove key[3], value[3], remove reason[EXPLICIT]
System.out.println(cache.getUnchecked(3)); //2,新生成的
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
刷新:
刷新表示为键加载新值,这个过程可以是异步的。在刷新操作进行时,缓存仍然可以向其他线程返回旧值.
而不像回收操作,读缓存的线程必须等待新值加载完成。
如果刷新过程抛出异常,缓存将保留旧值
public class GuavaCacheTest {
public static void main(String[] args) {
LoadingCache<Integer, Integer> cache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.SECONDS).removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
System.out.println("remove key[" + notification.getKey() + "],value[" + notification.getValue() + "],remove reason[" + notification.getCause() + "]");
}
}).recordStats().build(
new CacheLoader<Integer, Integer>() {
@Override
public Integer load(Integer key) throws Exception {
return 2;
}
}
);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.asMap());
cache.refresh(1);
System.out.println(cache.asMap());
}
}
统计:Guava Cache具有统计功能
1)CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后,Cache.stats()方法会返回对象以提供如下统计信息:
2)hitRate():缓存命中率;
3)averageLoadPenalty():加载新值的平均时间,单位为纳秒;
4)evictionCount():缓存项被回收的总数,不包括显式清除
5)统计信息对于调整缓存设置是至关重要的,在性能要求高的应用中我们建议密切关注这些数据。
五、参考
http://ifeve.com/google-guava/
https://blog.youkuaiyun.com/u014082714/article/details/52080647
https://blog.youkuaiyun.com/gongxinju/article/details/53634434
https://www.yiibai.com/guava/guava_bimap.html
https://www.cnblogs.com/peida/p/Guava_Bimap.html
https://www.jianshu.com/p/5299f5a11bd5