Google-Guava 使用笔记
Google Java类库Guava使用笔记
google/guava 官网
https://github.com/google/guava
官方User Guide
https://github.com/google/guava/wiki
官方API文档
https://google.github.io/guava/releases/snapshot-jre/api/docs/
Guava RateLimiter
Google 的开源项目 guava 提供了 RateLimiter
类,实现了单点的令牌桶限流。
create() 创建限流器
public static RateLimiter create(double permitsPerSecond);
创建一个稳定输出令牌的 RateLimiter,保证了平均每秒不超过 permitsPerSecond 个请求
当请求到来的速度超过了 permitsPerSecond,保证每秒只处理 permitsPerSecond 个请求
当这个 RateLimiter 使用不足(即请求到来速度小于 permitsPerSecond),会囤积最多 permitsPerSecond 个请求
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit);
根据指定的稳定吞吐率和预热期来创建 RateLimiter,这里的吞吐率是指每秒多少许可数(通常是指QPS,每秒多少个请求量),在这段预热时间内,RateLimiter 每秒分配的许可数会平稳地增长直到预热期结束时达到其最大速率。(只要存在足够请求数来使其饱和)
同样地,如果 RateLimiter 在 warmupPeriod 时间内闲置不用,它将会逐步地返回冷却状态。也就是说,它会像它第一次被创建般经历同样的预热期。返回的 RateLimiter 主要用于那些需要预热期的资源,而不是在稳定(最大)的速率下可以立即被访问的资源。返回的 RateLimiter 在冷却状态下启动(即预热期将会紧跟着发生),并且如果被长期闲置不用,它将回到冷却状态。
Guava有两种限流模式:
一种为稳定模式(SmoothBursty:令牌生成速度恒定)
一种为渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值)
在调用create接口时,实际实例化的为SmoothBursty类
public static RateLimiter create(double permitsPerSecond) {
return create(permitsPerSecond, SleepingStopwatch.createFromSystemTimer());
}
static RateLimiter create(double permitsPerSecond, SleepingStopwatch stopwatch) {
RateLimiter rateLimiter = new SmoothBursty(stopwatch, 1.0 /* maxBurstSeconds */);
rateLimiter.setRate(permitsPerSecond);
return rateLimiter;
}
关键变量:maxPermits
最大许可数storedPermits
当前令牌数,数量不能超过最大许可数stableIntervalMicros
添加令牌时间间隔 ,毫秒, 等于 1/permitsPerSecondnextFreeTicketMicros
表示下一次允许补充许可的时间(时刻)。
acquire() 获取令牌
public double acquire();
public double acquire(int permits);
acquire() 会阻塞当前线程直到许可证可用后获取该许可证
提供了两个获取令牌的方法,不带参数表示获取一个令牌。如果没有令牌则一直等待,返回等待的时间(单位为秒),没有被限流则直接返回0.0
注意:RateLimiter 并不提供公平性的保证。
tryAcquire() 尝试获取令牌
尝试获取令牌,分为待超时时间和不带超时时间两种:
//尝试获取一个令牌,立即返回
public boolean tryAcquire();
public boolean tryAcquire(int permits);
//尝试获取permits个令牌,带超时时间,在超时时间内能获取到则返回true,否则返回false
public boolean tryAcquire(long timeout, TimeUnit unit);
public boolean tryAcquire(int permits, long timeout, TimeUnit unit);
惰性计算(延迟计算)
RateLimiter 中采取的是 惰性计算方式:在每次请求进来的时候先去计算上次请求和本次请求之间应该生成多少个令牌。
具体实现是通过 resync()
方法来做的
该函数会在每次获取令牌之前调用,其实现思路为,若当前时间晚于 nextFreeTicketMicros ,则计算该段时间内可以生成多少令牌,将生成的令牌加入令牌桶中并更新数据。这样一来,只需要在获取令牌时计算一次即可。
void resync(long nowMicros) {
// if nextFreeTicket is in the past, resync to now
if (nowMicros > nextFreeTicketMicros) {
double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
storedPermits = min(maxPermits, storedPermits + newPermits);
nextFreeTicketMicros = nowMicros;
}
}
如果当前时间(不是时刻,而是自创建起所流经的时间,下同)超过了上一次所设定的nextFreeTicketMicros时间,则会重新进行同步:
通过计算上一次设定nextFreeTicketMicros到当前时刻的时间差获取新增的可用许可数;
计算可用的许可数:如果新增的许可数+原有的许可数小于最大许可数,则存储的许可数增加新增的数量,否则同步为最大许可数;
同步下一次允许补充许可时间为当前时间
Guava RateLimiter源码解析
https://segmentfault.com/a/1190000012875897
热身
RateLimiter 的 SmoothWarmingUp
是带有预热期的平滑限流,它启动后会有一段预热期,逐步将分发频率提升到配置的速率。
比如RateLimiter r = RateLimiter.create(2, 3, TimeUnit.SECONDS);
创建一个平均分发令牌速率为2,预热期为3秒钟的限流器。
由于设置了预热时间是3秒,令牌桶一开始并不会0.5秒发一个令牌,而是形成一个平滑线性下降的坡度,频率越来越高,在3秒钟之内达到原本设置的频率,以后就以固定的频率输出。这种功能适合系统刚启动需要一点时间来“热身”的场景。
* ^ throttling
* |
* cold + /
* interval | /.
* | / .
* | / . ← "warmup period" is the area of the trapezoid between
* | / . thresholdPermits and maxPermits
* | / .
* | / .
* | / .
* stable +----------/ WARM .
* interval | . UP .
* | . PERIOD.
* | . .
* 0 +----------+-------+--------------→ storedPermits
* 0 thresholdPermits maxPermits
上图中横坐标是当前令牌桶中的令牌 storedPermits, SmoothWarmingUp 将 storedPermits 分为两个区间:[0, thresholdPermits) 和 [thresholdPermits, maxPermits]。
纵坐标是请求的间隔时间,stableInterval 就是 1 / QPS,例如设置的 QPS 为5,则 stableInterval 就是200ms,coldInterval = stableInterval * coldFactor,这里的 coldFactor “hard-code”写死的是3。
SmoothWarmingUp 实现预热缓冲的关键在于其分发令牌的速率会随时间和令牌数而改变,速率会先慢后快。
令牌刷新的时间间隔由长逐渐变短。等存储令牌数从maxPermits到达thresholdPermits时,发放令牌的时间价格也由coldInterval降低到了正常的stableInterval。
当系统进入 cold 阶段时,图像会向右移,直到 storedPermits 等于 maxPermits;
当系统请求增多,图像会像左移动,直到 storedPermits 为0。
超详细的Guava RateLimiter限流原理解析
https://zhuanlan.zhihu.com/p/60979444
Guava官方文档-RateLimiter类
http://ifeve.com/guava-ratelimiter/
Guava Cache
maximumSize()设置最大缓存数
Guava Cache 可以在构建缓存对象时指定缓存所能够存储的最大记录数量。当 Cache 中的记录数量达到最大值后再调用put方法向其中添加对象,Guava会先从当前缓存的对象记录中选择一条删除掉,腾出空间后再将新的对象存储到Cache中。
expireAfterAccess()设置过期时间
在构建 Cache 对象时,可以通过 CacheBuilder 类的 expireAfterAccess
和 expireAfterWrite
两个方法为缓存中的对象指定过期时间,过期的对象将会被缓存自动删除。其中,expireAfterWrite
方法指定对象被写入到缓存后多久过期,expireAfterAccess
指定对象多久没有被访问后过期。
get()方法传入Callable缓存生成方法
Cache的 get 方法有两个参数,第一个参数是要从 Cache 中获取记录的 key,第二个记录是一个 Callable
对象。当缓存中已经存在key对应的记录时,get方法直接返回key对应的记录。如果缓存中不包含key对应的记录,Guava会启动一个线程执行Callable对象中的call方法,call方法的返回值会作为key对应的值被存储到缓存中,并且被get方法返回。
Guava缓存的get方法如何解决并发回源的击穿问题?
在高并发场景下,被动更新(先从缓存获取,没有则回源取,再放回缓存)的回源是要格外小心的,如果有太多请求在同一时间回源,可能导致后端服务无法支撑这么高并发而宕机,这就是所谓的缓存击穿问题。
Guava Cache 里的 CacheLoader 在回源的 load 方法上加了控制,对于同一个key,只让一个请求回源load,其他线程阻塞等待结果。
recordStats()开启命中率等统计信息
可以对Cache的命中率、加载数据时间等信息进行统计。在构建Cache对象时,可以通过 CacheBuilder 的 recordStats()
方法开启统计信息的开关。开关开启后Cache会自动对缓存的各种操作进行统计,调用Cache的stats方法可以查看统计后的信息。
@Autowired
private NameService nameService;
private Cache<String, String> nameCache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(3, TimeUnit.MINUTES).build();
// 根据 code 查 name
public String getNameByCode(String code) {
try {
return nameCache.get(code, () -> nameService.GetByCode(code));
} catch (Exception e) {
return null;
}
}
public LoadingCache<String, Long> cache =
CacheBuilder.newBuilder()
.refreshAfterWrite(30, TimeUnit.MINUTES)
.maximumSize(100)
.build(new CacheLoader<String, Long>() {
@Override
/** 当本地缓存命没有中时,调用load方法获取结果并将结果缓存
*/
public Long load(String key) throws ExecutionException {
return service.count();
}
});
Guava Cache用法介绍
https://segmentfault.com/a/1190000011105644
集中式内存缓存Guava Cache
https://www.jianshu.com/p/64b0df87e51b
Preconditions 入参检查
package com.google.common.base;
@GwtCompatible
public final class Preconditions {
public static void checkArgument(boolean expression, @Nullable Object errorMessage) {
if (!expression) {
throw new IllegalArgumentException(String.valueOf(errorMessage));
}
}
public static void checkState(boolean expression, @Nullable Object errorMessage) {
if (!expression) {
throw new IllegalStateException(String.valueOf(errorMessage));
}
}
}
由于Preconditions中所有方法都是static的,使用的时候一般静态导入即可,比如:
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Preconditions.checkArgument;
private void method(long userId, String orderId, Request request) {
checkArgument(userId > 0, "Required long param userId is invalid");
checkState(StringUtils.isNotBlank(orderId), "Required String param orderId is not present");
checkState(request != null, "Required param request is not present");
... ...
}
CharMatcher
去掉字符串前后的双引号
CharMatcher.is(CharPool.DOUBLE_QUOTES).trimFrom(“"quoted string"“);
Splitter
package com.google.common.base;
@GwtCompatible(emulated = true)
public final class Splitter {
}
on() 指定分隔符
public static Splitter on(char separator)
public static Splitter on(String separator)
split() 分割字符串
public Iterable<String> split(CharSequence sequence)
例如
Splitter.on(',').split("foo,bar,qux")
splitToList()
public List<String> splitToList(CharSequence sequence)
splitToStream()
public Stream<String> splitToStream(CharSequence sequence)
trimResults() 去掉前后空白
public Splitter trimResults()
omitEmptyStrings() 去掉空串
public Splitter omitEmptyStrings()
Splitter.on(‘,’).omitEmptyStrings().split(“foo,bar,qux”)
withKeyValueSeparator()
public Splitter.MapSplitter withKeyValueSeparator(String separator)
返回一个 Splitter.MapSplitter
,基于此 Splitter 以 k/v 分隔符 separator 分隔
逗号分隔字符串转List
String userIds = "11,22,aa,bb"
List<Long> userIdList = Splitter.on(',').splitToList(userIds).stream().filter(StringUtils::isNumeric).map(Long::parseLong).collect(Collectors.toList());
Splitter.on(‘,’).split(“ foo,,, bar ,”) 结果为: [“ foo”, “”, “”, “ bar “, “”]
其中包含了空的字符串,这可能不是我们需要的,可以通过Splitter类中的 trimResults()
方法去掉子串的空格,List<String> result = Splitter.on(",").trimResults().splitToList(str);
以及 omitEmptyStrings()
方法去掉空的子串。运行之后的结果为[“foo”, “bar”]。
Splitter.on(‘,’).limit(3).omitEmptyStrings().split(“a,,,b,,,c,d”) 结果为 [“a”, “b”, “c,d”]
Splitter类还提供了limit(int limit)方法,当分割的子字符串达到了limit个时,则停止分割,如下:
Splitter.on(‘,’).limit(3).split(“a,b,c,d”) 结果为[“a”, “b”, “c,d”]
Splitter解析url查询参数
MapSplitter
对url中的查询字符串”id=123&name=green”进行分割
利用Guava的MapSplitter的代码如下:
final Map<String, String> join = Splitter.on("&").withKeyValueSeparator("=").split("id=123&name=green");
这里同样利用on传入字符串的第一分隔符,withKeyValueSeparator传入项的分隔符,产生map的key/value项,其结果是一个{id=123, name=green}的Map对象。
fixedLength() 字符串拆分为固定长度
List
Joiner
如果不指定 skipNulls()
或 useForNull(String)
, 当要连接的元素有 null 值时会抛出空指针异常。
useForNull
public Joiner useForNull(final String nullText)
对于 null 元素,使用 nullText 串代替
Joiner.on("|").useForNull(" ").join(stringList);
对于MapJoinner和MapSplitter的最好案例就是url的param编码。
Joiner拼接url查询参数
MapJoinner
生产一个查询id: 123, name: green的学生信息的url。
利用Guava的MapJoinner的代码如下:
Joiner.on("&").withKeyValueSeparator("=").join(ImmutableMap.of("id", "123", "name", "green"));
这里采用了on传入map item之间分隔符,以及withKeyValueSeparator传递map项key/value之间的分隔符。所以能够很简单的实现,不用我们在去实现一个的for循环代码。
String baseUrl = "/api/v1/users/%s/hello";
Map<String, String> body = Maps.newHashMap();
body.put("city_id", "11010000");
body.put("name", RandomStringUtils.randomAlphabetic(10));
body.put("mobile", "98762000970");
Long userId = 1001;
String url = String.format(baseUrl, userId);
doPOST(url, Joiner.on('&').withKeyValueSeparator("=").join(body))
.andExpect(MockMvcResultMatchers.status().isOk())
guava之Joiner 和 Splitter
https://www.cnblogs.com/whitewolf/p/4214749.html
List转逗号分隔字符串
public static String joinByGuava(List stringList, String delimiter) {
return Joiner.on(delimiter).skipNulls().join(stringList);
}
当然我们也可以用common-lang来很简单的完成:StringUtils.join(stringList, delimiter).但是个人推荐尽量使用Guava替代common-lange,因为Guava还有更多的有用方法,后续会陆续介绍,还有就是Guava的API相对更有语意一点。
Maps
package com.google.common.collect;
@GwtCompatible( emulated = true )
public final class Maps
GwtCompatible 表示该类型兼容Google Web Toolkit
newHashMap() 创建HashMap
public static <K,V> HashMap<K,V> newHashMap()
例如:
Map<String, Map<String, String>> mobileMessageMap = Maps.newHashMap();
Map<String, String> messageMap = Maps.newHashMap();
messageMap.put("messageKey", "messageBody");
mobileMessageMap.put("18688888888", messageMap);
uniqueIndex()
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();
}
});
根据User集合构建userId <-> User 映射
List<User> users = userMapper.queryUsersByIds(userIds);
Map<Long, User> userIdMap = Maps.uniqueIndex(users, User::getId);
如果索引值不是独一无二的,请参见下面的Multimaps.index方法。
[Google Guava] 2.3-强大的集合工具类:java.util.Collections中未包含的集合工具
http://ifeve.com/google-guava-collectionutilities/
Multimaps
index
通常针对的场景是:有一组对象,它们有共同的特定属性,我们希望按照这个属性的值查询对象,但属性值不一定是独一无二的。
比方说,我们想把字符串按长度分组:
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"}
*/
根据用户标签集合(一个用户可能多个标签)构建 userId <-> 标签集合 映射
List<UserTag> userTags = userMap.queryUserTagsByUserIds(userIds);
// UserId <--> UserTag Map
Multimap<Long, UserTag> userIdMultimap = Multimaps.index(userTags, UserTag::getUserId());
Map<Long, Collection<UserTag>> userIdMap = userIdMultimap.asMap();
用户收货地址,一个用户可能有多个收货地址:
// 批量查询地址信息
List<Long> userIds = Lists.newArrayList(12L, 23L, 34L);
List<UserAddress> userAddresses = userDao.queryUserAddressesByUserId(userIds);
Map<Long, Collection<UserAddress>> userIdAddressMap = Multimaps.index(userAddresses, UserAddress::getUserId).asMap();
[Google Guava] 2.3-强大的集合工具类:java.util.Collections中未包含的集合工具
http://ifeve.com/google-guava-collectionutilities/
ImmutableMap 不可变map
Guava 提供了每个 java.util 的不可变版本。
使用 ImmutableMap 映射 。每当我们尝试修改它时,它都会抛出 UnsupportedOperationException。
builder()
快速构造一个 map
private static final Map<String, String> level2NumberMap = ImmutableMap.<String,String>builder()
.put("high", "3")
.put("mid", "2")
.put("low", "1").build();
of(k, v)
Map<String, String> map = ImmutableMap.of("key", "value");
快速构造并初始化可变Map
如果想构造的 Map 可操作,外部再用 Maps.newHashMap() 包一层:
Maps.newHashMap(ImmutableMap.of(
"color", "银色",
"timestamp", System.currentTimeMillis()
))
Sets
package com.google.common.collect;
@GwtCompatible( emulated = true )
public final class Sets {
...
}
newHashSet()创建HashSet
public static <E> HashSet<E> newHashSet()
例如:
Set<String> emails = Sets.newHashSet();
emails.add("aa@aaa.com");
emails.add("bbb@bb.com");
Set<String> emails = Sets.newHashSet("aa@aaa.com","bbb@bb.com");
intersection()求交集
public static <E> Sets.SetView<E> intersection(Set<E> set1, Set<?> set2)
返回 set1 和 set2 中共同存在的元素
difference()求差集
public static <E> Sets.SetView<E> difference(Set<E> set1, Set<?> set2)
返回在 set1 中但不在 set2 中的元素,只在 set2 中不在 set1 中的元素会被忽略。
union()求并集
public static <E> Sets.SetView<E> union(Set<? extends E> set1, Set<? extends E> set2)
返回 set1 和 set2 的并集。
symmetricDifference()对称差
public static <E> Sets.SetView<E> symmetricDifference(Set<? extends E> set1, Set<? extends E> set2)
返回 在set1或set2中,但不在两者交集中的元素。
等于 set1 和 set2 的并集减去交集。
求集合的交集/差集/并集
package com.uwo9.test08;
import java.util.Set;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
/**
* 集合的操作:交集、差集、并集
* Sets.intersection()交集
* Sets.difference()差集
* Sets.union()并集
*/
public class Test04 {
public static void main(String[] args) {
Set<Integer> sets = Sets.newHashSet(1, 2, 3, 4, 5, 6);
Set<Integer> sets2 = Sets.newHashSet(3, 4, 5, 6, 7, 8, 9);
// 交集
System.out.println("交集为:");
SetView<Integer> intersection = Sets.intersection(sets, sets2);
for (Integer temp : intersection) {
System.out.println(temp);
}
// 差集
System.out.println("差集为:");
SetView<Integer> diff = Sets.difference(sets, sets2);
for (Integer temp : diff) {
System.out.println(temp);
}
// 并集
System.out.println("并集为:");
SetView<Integer> union = Sets.union(sets, sets2);
for (Integer temp : union) {
System.out.println(temp);
}
}
}
java guava 集合的操作:交集、差集、并集
https://blog.csdn.net/huanyinghanlang/article/details/78774652
求List的差集(先转Set求差集再转回List)
List<Long> userIds1 = Lists.newArrayList(12L, 23L, 34L);
List<Long> userIds2 = Lists.newArrayList(12L, 23L);
// 差集
List<Long> userIdsDifference = Sets.difference(Sets.newHashSet(userIds1), Sets.newHashSet(userIds2)).stream()
.collect(Collectors.toList());
Iterables
getLast() 集合最后一个元素
等价于 Streams.findLast(stream).get()
Iterables.getLast(Lists.newArrayList(1,2,3));
Lists
package com.google.common.collect;
@GwtCompatible( emulated = true )
public final class Lists {
...
}
partition() 分割 List
public static <T> List<List<T>> partition(List<T> list, int size)
将入参 list 分割为 n 个 list, 每个长度为 size, 最后一个长度可能小于 size, 返回一个 list 集合。
List<Long> ids = Lists.newArrayList(1L, 2L, 3L, 4L, 5L);
List<List<Long>> partition = Lists.partition(ids, 2);
例如,把一个较大的user ids列表为分100个一组,分别对100个调用服务
Lists.partition(userIds, 100).forEach(userIdList -> getSomeInfos(userIdList, resultMap));
Guava EventBus 事件驱动的观察者模式
单机环境下优雅地使用事件驱动进行代码解耦
https://javadoop.com/post/guava-eventbus
@AllowConcurrentEvents 并发消费
注意只有订阅者方法上加上 @AllowConcurrentEvents
才是并发消费,否则所有消息都是串行消费的,即使使用的是 AsyncEventBus
异步bus也没用,AsyncEventBus
只是说消息分发是异步的,生产者post后可立即返回,但消费还是串行的。
https://github.com/bennidi/eventbus-performance/issues/2 这个issue里说的很清楚
AsyncEventBus -> Async Dispatch
EventBus -> Sync Dispatch
@subscribe @AllowConcurrentEvents -> Allow Concurrent Subscribe method
@subscribe -> Disallow Concurrent Subscribe method(Synchronize using synchronized block)
EventBus是同步分发,即生产者需要等消费者消费完才能返回。
AsyncEventBus只是异步分发(Async Dispatch),就是生产者post后立即返回。
订阅方法上只注解 @subscribe 不允许并发消费(有synchronized锁)。
订阅方法上注解了 @subscribe @AllowConcurrentEvents 才允许并发消费。
虽然创建AsyncEventBus时需要指定线程池,并且消费者也是在这个线程池中执行的,但线程池中的所有消费者线程必须争抢synchronized锁后才能执行,所以还是串行的。
@Subscribe
@AllowConcurrentEvents
public void processImageEvent(InfoMessage infoMessage) {
...
}
没有 @AllowConcurrentEvents
时,队列里的所有消息是串行消费的,被加了 synchronized
锁
@Override
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
synchronized (this) {
super.invokeSubscriberMethod(event);
}
}
加上 @AllowConcurrentEvents
后才是并行消费,使用创建 AsyncEventBus 时传入的线程池进行并行消费。
final void dispatchEvent(final Object event) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
bus.handleSubscriberException(e.getCause(), context(event));
}
}
});
}
【Guava学习】EventBus
https://www.jianshu.com/p/5bb2f08f2721
BloomFilter 布隆过滤器
Hashing
murmur3_32_fixed() 返回32位MurMur3哈希函数
CaseFormat
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "camel_case"); // returns camelCase
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, "CAMEL_CASE"); // returns CamelCase
CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, "camelCase"); // returns CAMEL_CASE
CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_HYPHEN, "CamelCase"); // returns camel-case
但是只能从固定的一个已知格式转换为目标格式,无法实现在不知道原字符串是啥格式的情况下转换为目标格式。
Range
open(a, b) 开区间
closed(a, b) 闭区间
openClosed(a, b) 左开右闭区间
closedOpen(a, b) 左闭右开区间
Range<Double> ra = Range.closedOpen(0.00, 0.9999);
Assertions.assertTrue(ra.contains(0.1234));
Predicates
Predicates 是 guava 中与 Predicate 配套使用的工具类,返回 Predicate 实例。
Predicates.or(predicate1, predicate2)
返回一个 predicate3,当 predicate1 与 predicate2 中任意一个为 true 时 predicate3 为 true,对 predicate1 和 predicate2 的评估是从前往后、短路立即结束的。
Predicates.and(predicate1, predicate2)
返回一个 predicate3,当 predicate1 与 predicate2 中全部为 true 时 predicate3 为 true,对 predicate1 和 predicate2 的评估是从前往后、短路立即结束的。
上一篇 Postman使用笔记
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: