Guava知识点总结--集合

全文链接

前置条件
在这里插入图片描述
*索引值常用来查找列表、字符串或数组中的元素,如List.get(int), String.charAt(int)

*位置值和位置范围常用来截取列表、字符串或数组,如List.subList(int,int), String.substring(int)

常见Object方法

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

hashCode

用对象的所有字段作散列[hash]运算应当更简单。Guava的Objects.hashCode(Object…)会对传入的字段序列计算出合理的、顺序敏感的散列值。你可以使用Objects.hashCode(field1, field2, …, fieldn)来代替手动计算散列值。
toString

好的toString方法在调试时是无价之宝,但是编写toString方法有时候却很痛苦。使用 Objects.toStringHelper可以轻松编写有用的toString方法。例如:

// Returns "ClassName{x=1}"
Objects.toStringHelper(this).add("x", 1).toString();
// Returns "MyObject{x=1}"
Objects.toStringHelper("MyObject").add("x", 1).toString();
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。

ComparisonChain执行一种懒比较:它执行比较操作直至发现非零的结果,在那之后的比较输入将被忽略。

1
public int compareTo(Foo that) {
2
    return ComparisonChain.start()
3
            .compare(this.aString, that.aString)
4
            .compare(this.anInt, that.anInt)
5
            .compare(this.anEnum, that.anEnum, Ordering.natural().nullsLast())
6
            .result();
7
}

Throwables:简化异常和错误的传播与检查

不可变集合

        ImmutableSet<Integer> set = ImmutableSet.of(1,3);
        ImmutableSet<Integer> set1 = ImmutableSet.copyOf(set);
        ImmutableSet<Integer> set2 = ImmutableSet.<Integer>builder().add(1).addAll(set).build();
        ImmutableList<Integer> list =  set.asList();

不可变对象有很多优点,包括:

当对象被不可信的库调用时,不可变形式是安全的;
不可变对象被多个线程调用时,不存在竞态条件问题
不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可变形式有更好的内存利用率.
不可变对象因为有固定不变,可以作为常量来安全使用。
在这里插入图片描述
新集合类型
Guava提供了一个新集合类型 Multiset,Multiset元素的顺序是无关紧要的:Multiset {a, a, b}和{a, b, a}是相等的”。
可以用两种方式看待Multiset:

没有元素顺序限制的ArrayList
Map<E, Integer>,键为元素,值为计数
Guava的Multiset API也结合考虑了这两种方式:
当把Multiset看成普通的Collection时,它表现得就像无序的ArrayList:

add(E)添加单个给定元素
iterator()返回一个迭代器,包含Multiset的所有元素(包括重复的元素)
size()返回所有元素的总个数(包括重复的元素)

当把Multiset看作Map<E, Integer>时,它也提供了符合性能期望的查询操作:

count(Object)返回给定元素的计数。HashMultiset.count的复杂度为O(1),TreeMultiset.count的复杂度为O(log n)。
entrySet()返回Set<Multiset.Entry>,和Map的entrySet类似。
elementSet()返回所有不重复元素的Set,和Map的keySet()类似。
所有Multiset实现的内存消耗随着不重复元素的个数线性增长。
在这里插入图片描述

HashMultiset<Integer> set = HashMultiset.create();
        set.add(1);
        set.add(2);
        set.add(1);
        System.out.println(set.size());  //3
        System.out.println(set.count(1)); //2
        Set<Integer> s = set.elementSet();
        System.out.println(JsonHelper.toJson(s));  //[1,2]

在这里插入图片描述
Multimap
每个有经验的Java程序员都在某处实现过Map<K, List>或Map<K, Set>,并且要忍受这个结构的笨拙。例如,Map<K, Set>通常用来表示非标定有向图。Guava的 Multimap可以很容易地把一个键映射到多个值。换句话说,Multimap是把键映射到任意多个值的一般方式。

可以用两种方式思考Multimap的概念:”键-单个值映射”的集合:

a -> 1 a -> 2 a ->4 b -> 3 c -> 5

或者”键-值集合映射”的映射:

a -> [1, 2, 4] b -> 3 c -> 5

一般来说,Multimap接口应该用第一种方式看待,但asMap()视图返回Map<K, Collection>,让你可以按另一种方式看待Multimap。重要的是,不会有任何键映射到空集合:一个键要么至少到一个值,要么根本就不在Multimap中。
在这里插入图片描述
Multimap的视图
Multimap还支持若干强大的视图:

asMap为Multimap<K, V>提供Map<K,Collection>形式的视图。返回的Map支持remove操作,并且会反映到底层的Multimap,但它不支持put或putAll操作。更重要的是,如果你想为Multimap中没有的键返回null,而不是一个新的、可写的空集合,你就可以使用asMap().get(key)。(你可以并且应当把asMap.get(key)返回的结果转化为适当的集合类型——如SetMultimap.asMap.get(key)的结果转为Set,ListMultimap.asMap.get(key)的结果转为List——Java类型系统不允许ListMultimap直接为asMap.get(key)返回List——译者注:也可以用Multimaps中的asMap静态方法帮你完成类型转换)
entries用Collection<Map.Entry<K, V>>返回Multimap中所有”键-单个值映射”——包括重复键。(对SetMultimap,返回的是Set)
keySet用Set表示Multimap中所有不同的键。
keys用Multiset表示Multimap中的所有键,每个键重复出现的次数等于它映射的值的个数。可以从这个Multiset中移除元素,但不能做添加操作;移除操作会反映到底层的Multimap。
values()用一个”扁平”的Collection包含Multimap中的所有值。这有一点类似于Iterables.concat(multimap.asMap().values()),但它直接返回了单个Collection,而不像multimap.asMap().values()那样是按键区分开的Collection。

在这里插入图片描述

ArrayListMultimap<String,Integer> map = ArrayListMultimap.create();
map.put("key1",2);
map.put("key1",2);
map.put("key1",3);
map.put("key2",2);
System.out.println(map.get("key1")); //[2, 2, 3]
Map<String, Collection<Integer>> m = map.asMap();

BiMap
传统上,实现键值对的双向映射需要维护两个单独的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发生...

在这里插入图片描述
在这里插入图片描述
Table

Table<Vertex, Vertex, Double> weightedGraph = HashBasedTable.create();
weightedGraph.put(v1, v2, 4);
weightedGraph.put(v1, v3, 20);
weightedGraph.put(v2, v3, 5);

weightedGraph.row(v1); // returns a Map mapping v2 to 4, v3 to 20
weightedGraph.column(v3); // returns a Map mapping v1 to 20, v2 to 5

通常来说,当你想使用多个键做索引的时候,你可能会用类似Map<FirstName, Map<LastName, Person>>的实现,这种方式很丑陋,使用上也不友好。Guava为此提供了新集合类型Table,它有两个支持所有类型的键:”行”和”列”。Table提供多种视图,以便你从各种角度使用它:

rowMap():用Map<R, Map<C, V>>表现Table<R, C, V>。同样的, rowKeySet()返回”行”的集合Set。
row® :用Map<C, V>返回给定”行”的所有列,对这个map进行的写操作也将写入Table中。
类似的列访问方法:columnMap()、columnKeySet()、column©。(基于列的访问会比基于的行访问稍微低效点)
cellSet():用元素类型为Table.Cell<R, C, V>的Set表现Table<R, C, V>。Cell类似于Map.Entry,但它是用行和列两个键区分的。
Table有如下几种实现:

HashBasedTable:本质上用HashMap<R, HashMap<C, V>>实现;
TreeBasedTable:本质上用TreeMap<R, TreeMap<C,V>>实现;
ImmutableTable:本质上用ImmutableMap<R, ImmutableMap<C, V>>实现;注:ImmutableTable对稀疏或密集的数据集都有优化。
ArrayTable:要求在构造时就指定行和列的大小,本质上由一个二维数组实现,以提升访问速度和密集Table的内存利用率。ArrayTable与其他Table的工作原理有点不同,请参见Javadoc了解详情。

在这里插入图片描述

强大的集合工具类:java.util.Collections中未包含的集合工具

在这里插入图片描述
静态工厂方法
在JDK 7之前,构造新的范型集合时要讨厌地重复声明范型:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();

我想我们都认为这很讨厌。因此Guava提供了能够推断范型的静态工厂方法:

List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList();
Map<KeyType, LongishValueType> map = Maps.newLinkedHashMap();

可以肯定的是,JDK7版本的钻石操作符(<>)没有这样的麻烦:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();

但Guava的静态工厂方法远不止这么简单。用工厂方法模式,我们可以方便地在初始化时就指定起始元素。

Set<Type> copySet = Sets.newHashSet(elements);
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");

此外,通过为工厂方法命名(Effective Java第一条),我们可以提高集合初始化大小的可读性:

List<Type> exactly100 = Lists.newArrayListWithCapacity(100);
List<Type> approx100 = Lists.newArrayListWithExpectedSize(100);
Set<Type> approx100Set = Sets.newHashSetWithExpectedSize(100);

确切的静态工厂方法和相应的工具类一起罗列在下面的章节。

注意:Guava引入的新集合类型没有暴露原始构造器,也没有在工具类中提供初始化方法。而是直接在集合类中提供了静态工厂方法,例如:

Multiset<String> multiset = HashMultiset.create();

Iterables
在可能的情况下,Guava提供的工具方法更偏向于接受Iterable而不是Collection类型。在Google,对于不存放在主存的集合——比如从数据库或其他数据中心收集的结果集,因为实际上还没有攫取全部数据,这类结果集都不能支持类似size()的操作 ——通常都不会用Collection类型来表示。

因此,很多你期望的支持所有集合的操作都在Iterables类中。大多数Iterables方法有一个在Iterators类中的对应版本,用来处理Iterator。

截至Guava 1.2版本,Iterables使用FluentIterable类进行了补充,它包装了一个Iterable实例,并对许多操作提供了”fluent”(链式调用)语法。
在这里插入图片描述

 Iterable<Integer> concatenated = Iterables.concat(
                Ints.asList(1, 2, 3),
                Ints.asList(1, 5, 6));
        List<Integer> list = new ArrayList<>();

      int count = Iterables.frequency(concatenated,1);
        System.out.println(count);  // 2
       int first = Iterables.getFirst(concatenated,-1);
       System.out.println(first);  //1

在这里插入图片描述
Sets
在这里插入图片描述

 Set<Integer> set1 = Sets.newHashSet(1,2,3,5);
        Set<Integer> set2 = Sets.newHashSet(2,3,4);
        Set<Integer> union = Sets.union(set1,set2);
        Set<Integer> intersection = Sets.intersection(set1,set2);
        Sets.SetView<Integer> setView = Sets.difference(set1,set2);  ///set1中比set2中多的
        Set<Integer> difference = new HashSet<>();
        setView.copyInto(difference);

        System.out.println(union);  //[1, 2, 3, 5, 4]
        System.out.println(difference);  //[1, 5]
        System.out.println(intersection); // [2, 3]

Maps
uniqueIndex
Maps.uniqueIndex(Iterable,Function)通常针对的场景是:有一组对象,它们在某个属性上分别有独一无二的值,而我们希望能够按照这个属性值查找对象——译者注:这个方法返回一个Map,键为Function返回的属性值,值为Iterable中相应的元素,因此我们可以反复用这个Map进行查找操作。

    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("ab");
    list.add("abc");
    ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(list,new Function<String,Integer>(){

        @Override
        public Integer apply(String input) {
            return input.length();
        }
    });
  String s= stringsByIndex.get(2);
  System.out.println(s);  //ab

源码:

  public static <K, V> ImmutableMap<K, V> uniqueIndex(
      Iterable<V> values, Function<? super V, K> keyFunction) {
    // TODO(lowasser): consider presizing the builder if values is a Collection
    return uniqueIndex(values.iterator(), keyFunction);
  }

difference
在这里插入图片描述

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "f", 3);
Map<String, Integer> right = ImmutableMap.of("a", 1, "e", 3, "c", 4);
MapDifference<String, Integer> diff = Maps.difference(left, right);
Map<String, Integer> common = diff.entriesInCommon(); //{a=1}
Map<String, MapDifference.ValueDifference<Integer>> Differing = diff.entriesDiffering();
Map<String, Integer> OnlyOnLeft = diff.entriesOnlyOnLeft(); //{b=2, f=3}
Map<String, Integer> OnlyOnRight = diff.entriesOnlyOnRight(); //{e=3, c=4}

字符串处理:分割,连接,填充
连接器[Joiner]
用分隔符把字符串序列连接起来也可能会遇上不必要的麻烦。如果字符串序列中含有null,那连接操作会更难。Fluent风格的Joiner让连接字符串更简单。

Joiner joiner = Joiner.on("; ").skipNulls();
return joiner.join("Harry", null, "Ron", "Hermione");
Joiner.on(",").join(Arrays.asList(1, 5, 7)); // returns "1,5,7"

读guavaCache源码并记录
Guava Cache 原理分析与最佳实践

public class GuavaTest {
    public static void main(String[] args) throws ExecutionException {

        LoadingCache<String, String> graphs = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build(
                        new CacheLoader<String, String>() {
                            public String load(String key)  {
                                return get(key);
                            }
                        });
        String r1 = graphs.get("wang");
        String r2 = graphs.get("wang");
    }

    public static String get(String key){
       System.out.println("啦啦啦啦"+key);
       return "嘻嘻"+key;
    }
}
  V get(K key, CacheLoader<? super K, V> loader) throws ExecutionException {
    int hash = hash(checkNotNull(key));
    return segmentFor(hash).get(key, hash, loader);
  }
  /** The segments, each of which is a specialized hash table. */
  final Segment<K, V>[] segments;

static class Segment<K, V> extends ReentrantLock {
volatile @Nullable AtomicReferenceArray<ReferenceEntry<K, V>> table;
}

在这里插入图片描述

  V lockedGetOrLoad(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
      ReferenceEntry<K, V> e;
      ValueReference<K, V> valueReference = null;
      LoadingValueReference<K, V> loadingValueReference = null;
      boolean createNewEntry = true;

      lock();
      try {
       
        AtomicReferenceArray<ReferenceEntry<K, V>> table = this.table;
        int index = hash & (table.length() - 1);   //根据key的hash值,找到table的index
        ReferenceEntry<K, V> first = table.get(index);  //获取第一个entry

        for (e = first; e != null; e = e.getNext()) {
          K entryKey = e.getKey();
          if (e.getHash() == hash
              && entryKey != null
              && map.keyEquivalence.equivalent(key, entryKey)) {
            valueReference = e.getValueReference();
            if (valueReference.isLoading()) {
              createNewEntry = false;
            } else {
              V value = valueReference.get();
              if (value == null) {
                enqueueNotification(
                    entryKey, hash, value, valueReference.getWeight(), RemovalCause.COLLECTED);
              } else if (map.isExpired(e, now)) {
                // This is a duplicate check, as preWriteCleanup already purged expired
                // entries, but let's accommodate an incorrect expiration queue.
                enqueueNotification(
                    entryKey, hash, value, valueReference.getWeight(), RemovalCause.EXPIRED);
              } else {
                recordLockedRead(e, now);
                statsCounter.recordHits(1);
                // we were concurrent with loading; don't consider refresh
                return value;
              }

              // immediately reuse invalid entries
              writeQueue.remove(e);
              accessQueue.remove(e);
              this.count = newCount; // write-volatile
            }
            break;
          }
        }

        if (createNewEntry) { //key不存在,跳过上面的循环
          loadingValueReference = new LoadingValueReference<>();

          if (e == null) {
            e = newEntry(key, hash, first);  //创建entry
            e.setValueReference(loadingValueReference);
            table.set(index, e);
          } else {
            e.setValueReference(loadingValueReference);
          }
        }
      } finally {
        unlock();
        postWriteCleanup();
      }

      if (createNewEntry) {
        try {
           synchronized (e) {
            return loadSync(key, hash, loadingValueReference, loader);   //真正去获取value,
            填充loadingValueReference
          }
        } finally {
          statsCounter.recordMisses(1);
        }
      } else {
        return waitForLoadingValue(e, key, valueReference);
      }
    }

创建entry时,将segment锁住,并将新创建的放在头上,最前面

ReferenceEntry<K, V> newEntry(K key, int hash, @Nullable ReferenceEntry<K, V> next) {
    LocalCache.Segment<K, V> segment = this.segmentFor(hash);
    segment.lock();

    ReferenceEntry var5;
    try {
        var5 = segment.newEntry(key, hash, next);
    } finally {
        segment.unlock();
    }

    return var5;
}



    V loadSync(
    K key,
    int hash,
    LoadingValueReference<K, V> loadingValueReference,
    CacheLoader<? super K, V> loader)
    throws ExecutionException {
  ListenableFuture<V> loadingFuture = loadingValueReference.loadFuture(key, loader); 
  //如果值不存在的话,调自己写的load方法加载
  return getAndRecordStats(key, hash, loadingValueReference, loadingFuture);
}

在存储value时,有一个判断是否扩容的动作
if (newCount > this.threshold) { //大于阈值就扩容
expand();
newCount = this.count + 1;
}

void expand() {
      AtomicReferenceArray<ReferenceEntry<K, V>> oldTable = table;
      int oldCapacity = oldTable.length();
      if (oldCapacity >= MAXIMUM_CAPACITY) {  //如果已大于最大容量,则不在扩容
        return;
      }

      int newCount = count;
      AtomicReferenceArray<ReferenceEntry<K, V>> newTable = newEntryArray(oldCapacity << 1); //扩容两倍
      threshold = newTable.length() * 3 / 4;     //阈值为总长度的0.75倍
      int newMask = newTable.length() - 1;
      for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) {
       
        ReferenceEntry<K, V> head = oldTable.get(oldIndex);

        if (head != null) {      //对table[i]中entry,,进行移动的
          ReferenceEntry<K, V> next = head.getNext();
          int headIndex = head.getHash() & newMask;     //将老表数据rehash到新表新的位置

  
          if (next == null) {
            newTable.set(headIndex, head);
          } else {
            
            ReferenceEntry<K, V> tail = head;
            int tailIndex = headIndex;
            for (ReferenceEntry<K, V> e = next; e != null; e = e.getNext()) {
              int newIndex = e.getHash() & newMask;
              if (newIndex != tailIndex) {
          
                tailIndex = newIndex;
                tail = e;
              }
            }
            newTable.set(tailIndex, tail);


            for (ReferenceEntry<K, V> e = head; e != tail; e = e.getNext()) {
              int newIndex = e.getHash() & newMask;
              ReferenceEntry<K, V> newNext = newTable.get(newIndex);
              ReferenceEntry<K, V> newFirst = copyEntry(e, newNext);
              if (newFirst != null) {
                newTable.set(newIndex, newFirst);
              } else {
                removeCollectedEntry(e);
                newCount--;
              }
            }
          }
        }
      }
      table = newTable;
      this.count = newCount;
    }
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Guava类库中的Table是一个双键的Map,可以理解为一个行列式的数据结构。 Table可以用来表示一个映射,其中每个键都有一个与之关联的值,而且这个键需要由两个参数来确定。在Table中,第一个键称为"行键",第二个键称为"列键",而对应的值称为"值"。 Table的实现方式可以看作是一个Map<RowKey, Map<ColumnKey, Value>>的嵌套结构,其中RowKey和ColumnKey分别表示行键和列键,Value表示对应的值。 Table提供了多种视图,包括行视图、列视图、单元格视图等,这些视图可以方便地进行表格的操作和查询。 下面是一个简单的示例代码: ```java Table<String, String, Integer> table = HashBasedTable.create(); table.put("row1", "col1", 1); table.put("row1", "col2", 2); table.put("row2", "col1", 3); table.put("row2", "col2", 4); System.out.println(table.get("row1", "col1")); // 输出1 System.out.println(table.row("row1")); // 输出{col1=1, col2=2} System.out.println(table.column("col1")); // 输出{row1=1, row2=3} System.out.println(table.cellSet()); // 输出[(row1,col1)=1, (row1,col2)=2, (row2,col1)=3, (row2,col2)=4] ``` 在上面的示例中,我们创建了一个Table对象,并往其中添加了四个元素。然后,我们分别通过get方法、row方法、column方法和cellSet方法获取了对应的视图,并输出了它们的内容。 需要注意的是,Table中的行键、列键和值都可以为null,但是在使用时需要特别注意空指针异常的问题。此外,Table中的行键和列键必须实现equals和hashCode方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值