Google Guava

文章目录

  • Guava
    • 一.Utility
      • 1.1 Joiner
          • 1、wxx-18,mjp-20
          • 2、将结果写入文件log.txt,内容为1#2#3#0
          • 3、补充:正常的Join功能等效Java8流中的Collectors类中的Joining方法
      • 1.2 Splitter
          • 1、"2023051411:59:59" =>[2023, 0514, 11:59]
          • 2、"2023-0514-11:59:59 "=> [0514]
          • 3、“mjp-18,wxx-20”转为map
      • 1.3 Predictions
      • 1.4 Strings
          • 1、两个字符串公共前缀
          • 2、填充字符
      • 1.5 StringUtils
          • 1、is判断类型
          • 2、截取字符串
          • strip 去掉字符串两端的空白符 == trime
          • 3、 转换
          • 4、countMatches 计算字符串在另一个字符串中出现的次数
          • 5、填充字符串
          • 6、交、补、并等关系
      • 1.6 Charmatcher
          • 1、移除|保留 数字和空格
          • 2、判断字符是否为数字||字符
          • 3、替换
          • 4、去掉转义字符(\t,\n,\b...)
          • 5、统计数字、字符、空格出现的此时
          • 6、空格:截取掉字符串头、尾的空格,并将中间连续的空格合并替换成指定char
    • 二.things
      • 2.1StopWatch
      • Spring-StopWatch
      • 2.3 ServiceLoad中的spi
          • 1、简介
          • 2、JDBC举例
            • 2.1、引入mysql依赖jar
            • 2、前置了解:
            • 2.3、数据库连接过程(这里重点分析一中的spi)
            • 2.4、分析过程
          • 打破双亲委派机制:
          • 3、自定义操作
          • 4、作用
    • 三、Collections
      • 3.1 Lists
          • 1.笛卡尔积
      • 3.2 Sets
          • 1. 子集
      • 3.3 Maps
          • 1. 创建
          • 2. 对map的val操作,产生新的map
          • 3. fromProperties转为不可变map
          • 4. key对应多个val
      • 3.4 Table
          • 1. HashBasedTable
      • 3.5 Range
          • >=2
          • >2
          • <=5
          • < 5
          • (1,5)
          • [1,5)
          • (1,5]
          • [1,5]
          • (-,+)
          • TreeRangeMap
      • 3.6 Immutable
      • 3.7 Ordering -可对null排序
    • 四.Cache
      • 4.1 LRU
          • 4.1.1 LinkedHashMap实现
          • 4.1.2 缺点&改进
      • 4.2 Cache
    • 五.EventBus
      • 5.1 EventBus
          • 1. create、post、register、@Subscribe
          • 2.Exception handle
      • 5.2 AsyncEventBus
      • 5.3 场景
          • 1、进程级别
          • 2、并发不高
    • 六. File
      • 6.1 Files
          • 1. 将文件A,copy到文件B
          • 2. 将文件A,move到文件B
          • 3. 将文件A的内容,读取为字符串
      • 6.2 charSource: 一行一行读取文件并解析
          • 1.将文件A的内容,读取为字符串
          • 2. 一行一行读取文件A的内容,并在一行一行读取过程中,使用行解析器自定义处理每行内容
      • 6.3 Charsink:将字符串写入文件
          • 1.覆盖写
          • 2.追加写
      • 6.4 文件|文件夹操作
      • 6.5 closer-关闭IO
          • 1.try-catch-final
          • 等效:try-with
          • 3.closer
      • 6.6 BaseEncoding
          • 3.closer
      • 6.6 BaseEncoding

Guava

一.Utility

1.1 Joiner

1、wxx-18,mjp-20
    public void test() {
        Map<String, Integer> map = ImmutableMap.of("wxx", 18, "mjp", 20);
        String res = Joiner.on(",").withKeyValueSeparator("-").join(map);
        System.out.println(res);//"wxx-18,mjp-20"
    }
  • ImmutableMap: key-val不像HashMap那样允许为null
  • withKeyValueSeparator: k,v之间使用"-"连接,即k-v
  • on: entry之间使用","连接,即k1-v1 , k2-v2
  • 如果是list,还可以过滤null值(Java8流filter),以及将null值替换为指定值(Java8流map)
    public void test() {
        List<Integer> list = Lists.newArrayList(1, 2, 3, null);
        String res = Joiner.on("#").skipNulls().join(list);
        System.out.println(res);//1#2#3
    }

    public void test2() {
        List<Integer> list = Lists.newArrayList(1, 2, 3, null);
        String res = Joiner.on("#").useForNull("0").join(list);
        System.out.println(res);//1#2#3#0,将null替换为0
    }
2、将结果写入文件log.txt,内容为1#2#3#0
    public void test3() {
        List<Integer> list = Lists.newArrayList(1, 2, 3, null);
        try (FileWriter fileWriter = new FileWriter(new File("D:\\新建文件夹\\log.txt"))){
            FileWriter res = Joiner.on("#").useForNull("0").appendTo(fileWriter, list);
        } catch (IOException e) {
        }
    }
3、补充:正常的Join功能等效Java8流中的Collectors类中的Joining方法

1.2 Splitter

1、“2023051411:59:59” =>[2023, 0514, 11:59]
    public void test() {
        List<String> res = Splitter.fixedLength(4).
            omitEmptyStrings().limit(3).splitToList("2023051411:59:59");
        System.out.println(res);//[2023, 0514, 11:59]
    }
  • fixedLength:4个字符,转为集合中的一个元素
  • omitEmptyStrings:忽略空元素
  • limit : 取前三个元素
2、"2023-0514-11:59:59 "=> [0514]
    public void test1() {
        List<String> list = Splitter.on("-").omitEmptyStrings()
                .splitToList("2023-0511-11:59:53");
        System.out.println(list);
    }
3、“mjp-18,wxx-20”转为map
    public void test2() {
        Map<String, String> res = Splitter.on(Pattern.compile("\\,")).omitEmptyStrings()
                .withKeyValueSeparator(":").split("wxx:18,mjp:20");
        System.out.println(res);//{wxx=18, mjp=20}
    }
  • on:可以直接传split的字符,也可以传正则表达式
  • withKeyValueSeparator将字符串内容解析为map

1.3 Predictions

    @Test(expected = IllegalArgumentException.class)
    public void test1() {
        User wxx = null;
        Preconditions.checkArgument(Objects.nonNull(wxx), "用户不能为空");
    }
  • expected:这里会报参数错误,使用expected则测试通过

1.4 Strings

建议使用StringUtils

1、两个字符串公共前缀
    public void test1() {
        String s = Strings.commonPrefix("java", "jar");
        System.out.println(s);//ja
    }
2、填充字符
    public void test() {
        String s = Strings.padStart("32", 4, '0');//StringUtils.leftPad("1", 2, '0');
        System.out.println(s);//0032
    }

1.5 StringUtils

1、is判断类型
  • isNumeric 判断是否为数字
    public void test2() {
        boolean numeric = StringUtils.isNumeric("520w");
        System.out.println(numeric);//false
    }
  • containsOnly 判断s1是否为s2的子串
    public void test2() {
        boolean b = StringUtils.containsOnly("hlo", "hello");
        System.out.println(b);//true
    }

同理s1 和 s2是否有交集—

  • isAlpha : 判断字符串是否是字母(没有数字和特殊符号)
        boolean alpha = StringUtils.isAlpha("qq.com");
        System.out.println(alpha);//false
  • isAlphanumeric: 判断字符串是否全是由字母和数字组成
2、截取字符串
  • subStringBefore 遇到指定字符则截断,取之前或之后的内容
    public void test2() {
        String s ="2023-0514-11:59:59";
        String res = StringUtils.substringBefore(s, "-");
        System.out.println(res);//2023

        String res1 = StringUtils.substringBeforeLast(s, "-");
        System.out.println(res1);//2023-0514
    }
  • abbreviate 截断指定长度
    public void test2() {
        String res = StringUtils.abbreviate("Hello Java","-", 3);//前2个字符正常取,>=3后面的元素使用-代替
        System.out.println(res);//He-
    }
    public void test2() {
        String res = StringUtils.abbreviate("Hello Java","", 3);//取前3个字符串
        System.out.println(res);//Hel
    }
  • left 截取字符串左侧指定长度的字符串
        String s = StringUtils.left("hello java", 3);
        System.out.println(s);//hel
  • truncate 截取字符串(从offset位置起,截指定长度字符串)
String s = StringUtils.truncate("hello java", 0, 5);
System.out.println(s);//hello
  • mid 从pos开始截取字符串指定长度的一部分
        String s = StringUtils.mid("2023-0514-11:59:59", 5, 4);
        System.out.println(s);//0514
  • strip 去掉字符串两端的空白符 == trime
public void test2() {
        String strip = StringUtils.strip(" java ");
        System.out.println(strip);//java
 }
        String s = " i love java ";

        String strip = StringUtils.strip(s);
        String trim = StringUtils.trim(s);
        String deleteWhitespace = StringUtils.deleteWhitespace(s);
        System.out.println(strip);//i love java
        System.out.println(trim);//i love java
        System.out.println(deleteWhitespace);//ilovejava
  • deleteWhitespace 删除所有的空space(参上)
3、 转换
  • capitalize 首字母大写
    public void test2() {
        String s = "java";
        String res = StringUtils.capitalize(s);
        System.out.println(res);//Java
    }
  • toCodePoints 将字符串的每个字符转换成10进制的unicode码
        int[] att = StringUtils.toCodePoints("aA你");
        System.out.println(Arrays.toString(att));//[97, 65, 20320]
4、countMatches 计算字符串在另一个字符串中出现的次数
    public void test2() {
        int nCount = StringUtils.countMatches(
                "UPDATE tb_table SET x =? , y=?, z=? WHERE id=?", "?");
        System.out.println(nCount);//4
    }
  • reverse : i love java => java love i
        String s = " hello world i love java ! ";
        String[] strings = StringUtils.split(s.trim(), " ");
        CollectionUtils.reverseArray(strings);
        
        String res = StringUtils.join(strings, " ");
        System.out.println(res);//! java love i world hello
5、填充字符串
  • leftPad : 在字符串左侧填充内容

给字符串"1"左侧填充0,填充后字符串长度为2,即"01"

String res = StringUtils.leftPad("1", 2, '0');//01
  • prependIfMissing 如果字符串未出现指定的前缀,则追加前缀,否则返回原字符串
String s = StringUtils.prependIfMissing("www.baidu.com", "https://");
System.out.println(s);//https://www.baidu.com
6、交、补、并等关系
  • 独有内容:difference
StringUtils.difference("abc456","abc123");//s2独有的内容:123
  • 多个字符串的公共前缀: getCommonPrefix
 String s = StringUtils.getCommonPrefix("abc456","ab123", "accc");
 System.out.println(s);//a
  • 获取字符串中的数字: getDigits
 String s = StringUtils.getDigits("[email protected]");
System.out.println(s);//15162265411

1.6 Charmatcher

1、移除|保留 数字和空格
    public void test1() {
        String s = 
            CharMatcher.inRange('0', '9').or(CharMatcher.whitespace()).removeFrom("15162 265421@qq. com");
        System.out.println(s);//@qq.com

        String s1 = 
            CharMatcher.inRange('0', '9').or(CharMatcher.whitespace()).retainFrom("15162 265421@qq. com");
        System.out.println(s1);//15162 265421 
    }
2、判断字符是否为数字||字符
    public void test1() {
        boolean matches = CharMatcher.inRange('0', '9').or(CharMatcher.whitespace()).matches('.');
        System.out.println(matches);//false
    }
StringUtils中的isAlpha入参为CharSequence,而matches入参为char
  • 判断字符串是否含有空格
        boolean result = CharMatcher.whitespace().matchesAnyOf("111 12222");
        System.out.println(result);//true
3、替换
    public void test1() {
        String res = CharMatcher.inRange('0', '9').or(CharMatcher.whitespace())
                .replaceFrom("i love java 1314", "-");
        System.out.println(res);//i-love-java-----
    }
  • collapseFrom 将连续的空格,替换成-
String s = CharMatcher.whitespace().collapseFrom(" i love    java", '-');
System.out.println(s);//-i-love-java
4、去掉转义字符(\t,\n,\b…)
    public void test1() {
        String str3 = " ab\tcd\nef\bg";
        String res = CharMatcher.javaIsoControl().removeFrom(str3);
        System.out.println(res);//abcdefg
    }
5、统计数字、字符、空格出现的此时
        int n = CharMatcher.inRange('0', '9').countIn("[email protected]");
        System.out.println(n);//14
6、空格:截取掉字符串头、尾的空格,并将中间连续的空格合并替换成指定char
String s1 = CharMatcher.whitespace().trimAndCollapseFrom(" i love    java ", '-');
System.out.println(s1);//i-love-java

二.things

2.1StopWatch

  • 打印方法的执行时间 == 结束时间 - 开始时间
    public void test1() throws InterruptedException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        func1();
        System.out.println(stopwatch.stop());//999.7 ms
    }

    private void func1() throws InterruptedException {
        TimeUnit.SECONDS.sleep(1L);
    }

Spring-StopWatch

项目实战已使用

    public void test() throws InterruptedException {
        StopWatch stopWatch = new StopWatch();

        stopWatch.start("查询加量算法");
        // 模拟查询加量算法方法
        TimeUnit.SECONDS.sleep(1L);
        stopWatch.stop();


        stopWatch.start("计算流程");
        TimeUnit.SECONDS.sleep(2L);
        stopWatch.stop();

        System.out.println(stopWatch.prettyPrint());
    }

StopWatch '': running time (millis) = 3015
-----------------------------------------
ms     %     Task name
-----------------------------------------
01013  034%  查询加量算法
02002  066%  计算流程

2.3 ServiceLoad中的spi

1、简介
  • JDK1.6引入的特性,用来实现SPI(Service Provider Interface),一种服务发现机制。
2、JDBC举例
2.1、引入mysql依赖jar
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
image-20230516214131535
2、前置了解:
  • JDBC:Java DataBase Connectivity,是sun公司提供的一套操作数据库的标准规范
  • java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口(规范)
  • 这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现(mysql、Oracle)
  • mysql的Driver也需要实现这个接口,以下是mysql的具体实现类
package com.mysql.jdbc;
import java.sql.DriverManager;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}
  • mysql的jar中把具体实现的类Driver,放在META-INF/services中接口全路径file即java.sql.Driver中,内容为具体的实现类全路径com.mysql.jdbc.Driver
image-20230515224947319
2.3、数据库连接过程(这里重点分析一中的spi)
        String url = "jdbc:mysql://localhost:3306/test?user=root&password=root";  //定义连接数据库的url
        //一、获取mysql的数据库连接对象
        Connection conn = DriverManager.getConnection(url);
        //二、获取SQL语句执行对象
        Statement statement = conn.createStatement();
        //三、执行SQL语句
        int result = statement.executeUpdate("sql语句");
2.4、分析过程

将服务中所有.jar包下META-INF/services/java.sql.Driver文件中的实现类(eg:mysql的com.mysql.jdbc.Driver)加载到内存list中,并在getConnection中从list中获取实例对象

public class DriverManager {
    static {
        loadInitialDrivers();
    }
    
    public static Connection getConnection(String url)
        ///
    }
 }

2.4.1、通过spi,获取所有Driver接口实现类对象,放入list

1)、DriverManager.getConnection(url),因为getConnection是静态方法,会触发DriverManager类的首次主动使用,会调用()即调用static{}中loadInitialDrivers

private static void loadInitialDrivers() {
        // 一、load
		ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
        // 二、hasNext
         while(driversIterator.hasNext()) {
             // 三、next
             driversIterator.next();
         }
  }

2)、load():指定上下文类加载器加载作为Driver接口的具体实现类com.mysql.jdbc.Driver的加载器

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();//上下文加载器,去加载Deiver接口的实现类
        return ServiceLoader.load(service, cl);
    }
打破双亲委派机制:

SPI接口实现类的加载需要破坏双亲委派模型。

  • java.sql.Driver是由根类加载器加载(因为其在rt.jar下)
image-20230516220517409
  • 而不同厂商具体的实现(mysql、Oracle)的jar放在classpath(com|META-INF)下,根类加载器无法加载classpath路径下的类
image-20230516214131535
  • 通过Thread.currentThread().setContextClassLoader,设置系统类加载器来加载(线程上下文中默认存放的系统类加载器)

3)、hasNext():按行读取所有.jar中含有"META-INF/services/com.sql.Driver"的file文件内容,将实现类全路径加入Iterator

        private boolean hasNextService() {
            //1.首次加载为null
            if (configs == null) {
                    // 2.这里的fullName = "META-INF/services/" + com.sql.Driver
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        // 3.获取url(不重要)
                        configs = loader.getResources(fullName);
               
            }
            while ((pending == null) || !pending.hasNext()) {
                // 4.会读取所有.jar,含有"META-INF/services/com.sql.Driver"的file文件
                // 一行一行的读取file的内容,比如读取com.msql.jdbc.Driver,将Diver接口所有的实现类的全路径放Iterator
                Iterator<String> pending = parse(service, configs.nextElement());//parseLine(service,names)
            }
            nextName = pending.next();
            return true;
        }

4)、next():创建Driver接口实现类的实例对象,并将实例对象放入list,便于后续getConnection时从list中获取实例对象

      private S nextService() {
			// 1.Diver接口实现类的全路径,比如com.mysql.jdbc.Driver
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                // 2.使用上下文加载器,创建class(com.mysql.jdbc.Driver)类对象
                c = Class.forName(cn, false, loader);
            } 
            
            try {
                // 3.生成Driver实现类的实例对象(重要)
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } 
        }


// 这里特别重要的一个点是c.newInstance(),即对实现类Diver创建实例
根据类的首次主动使用原则,会触发com.mysql.jdbc.Driver类的static{}
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } 
    }
}

    
//registerDriver方法,会将com.mysql.jdbc.Driver实例,加入list中,便于后续getConnection的获取
public static synchronized void registerDriver(java.sql.Driver driver){

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            //CopyOnWriteArrayList registeredDrivers 
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } 
}
  1. 4.2 getConnection
    public static Connection getConnection(String url)
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    
        // CopyOnWriteArrayList registeredDrivers
        // 这里遍历上述2.4.1中存入list的所有Driver接口的实现类对象,然后connect,这里以com.mysql.jdbc.Driver的connect
        // 为例
        for(DriverInfo aDriver : registeredDrivers) {
            Connection con = aDriver.driver.connect(url, info);
            if (con != null) {
               println("getConnection returning " + aDriver.driver.getClass().getName());
               return (con);
             }      
 }
public class NoRegisterDriver{
   public Connection connect(String url, Properties info) throws SQLException {
        
        // 1.一般我们都会使用.properties将数据库连接的url、port、user、password等属性放在文件中使用Properties加载
        Properties props = null;
         // 2.创建mysql的数据库连接(localhost即ip地址信息、port端口信息等,都可以从String url = "jdbc:mysql://localhost:3306/test?user=root&password=root"中解析)
        com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);
                return newConn;
            }
        }
    }
}


    protected static Connection getInstance(String hostToConnectTo, int portToConnectTo, Properties info, 				String databaseToConnectTo, String url)  {
        // 底层还是通过反射,Constructor ctor.newInstance(args)创建的JDBC4对应的mysql的con对象
    }     

至此,就得到了mysql数据库的con连接对象

3、自定义操作

3.1 定义client模块,内含自定义接口Myservice以及其实现类MyServiceImpl

public interface MyService {
    public void show();
}




public class MyServiceImpl implements MyService {
    @Override
    public void show() {
        System.out.println("load MyServiceImpl spi");
    }
}

3.2 将自定义接口实现类全路径,作为内容写在清单文件file(自定义接口全路径)中

  • 创建清单文件:resources/META-INF/services/创建file,file的名称为自定义接口的全路径:com.mjp.service.MyService
  • file清单文件内容自定义接口实现类的全路径:com.mjp.service.impl.MyServiceImpl(可以在多行写多个实现类)
  • install模块client生成jar,后续在service中pom引入此jar

3.3 定义service模块

  • pom中依赖client模块
    <dependencies>
        <dependency>
            <groupId>CodeBetter</groupId>
            <artifactId>com.mjp.client</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
  • 编写ServiceLoader
    public static void main(String[] args) {
        ServiceLoader<MyService> services = ServiceLoader.load(MyService.class);
        for (MyService service : services) {
            service.show();//load MyServiceImpl spi
        }
    }
4、作用

spi服务发现机制,更具有插拔性。

  • 每当接口有新的实现类时,只要实现类遵循file文件的存放目录以及命名和内容,则ServiceLoader.load就帮你把所有的实现类都加载到ServiceLoader<接口> loadedServices中,程序员无需感知,可以直接在代码中使用新增的实现类对象,而不是在硬编程的方式,自己再new实现类对象。

三、Collections

3.1 Lists

1.笛卡尔积
    public void test() {
        List<Integer> list1 = Lists.newArrayList(1, 2);
        List<Integer> list2 = Lists.newArrayList(3, 4);
        List<List<Integer>> res = Lists.cartesianProduct(list1, list2);
        System.out.println(res);//[[1, 3], [1, 4], [2, 3], [2, 4]]
    }

3.2 Sets

1. 子集
    public void test() {
        Set<Integer> set = Sets.newHashSet(1, 2, 3);
        
        //[1]
        //[2]
        //[3]
        Set<Set<Integer>> combinations1 = Sets.combinations(set, 1);//1是表示子集中元素个数为1的子集
        
        //[1, 2]
        //[1, 3]
        //[2, 3]
        Set<Set<Integer>> combinations2= Sets.combinations(set, 2);
        
        //[1, 2, 3]
        Set<Set<Integer>> combinations3= Sets.combinations(set, 3);
    }

差集、交集等

3.3 Maps

1. 创建
    public void test() {
        Map<Integer, String> map = Maps.asMap(Sets.newHashSet(1, 2, 3), k -> "hello" + k);
        System.out.println(map);//{1=hello1, 2=hello2, 3=hello3}
    }
2. 对map的val操作,产生新的map
    public void test() {
        Map<String, Integer> sourceMap = new HashMap<>();
        sourceMap.put("wxx", 18);
        sourceMap.put("mjp", 20);

        Map<String, Integer> targetMap = Maps.transformValues(sourceMap, sourceV -> sourceV + 1);
        System.out.println(targetMap);//{mjp=21, wxx=19}
    }
3. fromProperties转为不可变map
  ImmutableMap map = Maps.fromProperties();//Properties
4. key对应多个val
  • 这样就不用使用Map map形式存储了

    否则就需要遍历list,然后再new一个新的newList,然后使用map.putIfAbsent方式一个一个遍历元素不存在再放入newList

  • 直接遍历list,put即可

    public void test() {
        Multimap<String, String> multimap = LinkedHashMultimap.create();
        multimap.put("1","1");
        multimap.put("1","2");
        System.out.println(multimap);//{1=[1, 2]}
        Collection<String> strings = multimap.get("1");

        Multimap<String, User> multimap = LinkedHashMultimap.create();
        multimap.put("1",new User("wxx", 18));
        multimap.put("1",new User("mjp", 20));
        System.out.println(multimap);//{1=[User(name=wxx, age=18), User(name=mjp, age=20)]}
    }

3.4 Table

1. HashBasedTable
    public void test() {
        Table<String, String, String> table = HashBasedTable.create();
        table.put("Language", "Java", "jdk1.8");
        table.put("Language", "python", "3.5");
        table.put("DataBase", "MySQL", "8.0");
        table.put("DataBase", "Oracle", "x");
        System.out.println(table);//{Language={Java=jdk1.8, python=3.5}, DataBase={MySQL=8.0, Oracle=x}}

        // row
        Map<String, String> languageMap = table.row("Language");
        System.out.println(languageMap);//{Java=jdk1.8, python=3.5}

        // col
        Map<String, String> dataBaseMap = table.column("Java");
        System.out.println(dataBaseMap);//{Language=jdk1.8}

        // entry
        Set<Table.Cell<String, String, String>> cells = table.cellSet();
        for (Table.Cell<String, String, String> cell : cells) {
            System.out.println(String.format("rowKey:[%s], colKey:[%s], val:[%s]",
                    cell.getRowKey(), cell.getColumnKey(), cell.getValue()));
        }

        // toRowMap
        Map<String, Map<String, String>> map = table.rowMap();
        System.out.println(map);//{Language={Java=jdk1.8, python=3.5}, DataBase={MySQL=8.0, Oracle=x}}
    }

3.5 Range

>=2
Range.atLeast(2);
>2
Range.greaterThan(2);
<=5
Range.atMost(5);
< 5
Range.lessThan(5);
(1,5)
Range.open(1, 5);
[1,5)
Range.openClosed(1, 5);
(1,5]
Range.closedOpen(1, 5);
[1,5]
Range.closed(1, 5);
(-,+)
Range.all();
TreeRangeMap

[0,60):不及格

[60,70):及格

[70,80):中等

    public void test() {
        TreeRangeMap<Integer, String> rangeMap = TreeRangeMap.create();
        rangeMap.put(Range.closedOpen(0, 60), "不及格");
        rangeMap.put(Range.closedOpen(60, 70), "及格");
        rangeMap.put(Range.closedOpen(70, 80), "中等");
        rangeMap.put(Range.closedOpen(80, 90), "良好");
        rangeMap.put(Range.closed(90, 100), "优秀");

        System.out.println(rangeMap);//[[0..60)=不及格, [60..70)=及格, [70..80)=中等, [80..90)=良好, [90..100]=优秀]
        String res = rangeMap.get(78);
        System.out.println(res);//中等
    }

3.6 Immutable

不可变map、list即不允许再添加 元素,否则报错

    public void test() {
        ImmutableMap<String, Integer> immutableMap = ImmutableMap.<String, Integer>builder()
                .put("wxx", 17)
                .put("mjp", 18)
                .build();
        System.out.println(immutableMap);
    }

3.7 Ordering -可对null排序

    public void test() {
        List<Integer> list = Lists.newArrayList(1, 3, 5, 2, null);
        Collections.sort(list, Ordering.natural().nullsLast());
        System.out.println(list);//[1, 2, 3, 5, null]

        Collections.sort(list, Ordering.natural().thenComparing());
    }

四.Cache

4.1 LRU

4.1.1 LinkedHashMap实现
@AllArgsConstructor
public class MyLRU<K, V> extends LinkedHashMap<K,V>{

    private Integer limit;


    public MyLRU(int limit) {
        super(16, 0.75f, true);//必须要有
        this.limit = limit;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > limit;
    }
}
public void test() {
        MyLRU<String, String> lru = new MyLRU<>(3);

        lru.put("1", "1");
        lru.put("2", "2");
        lru.put("3", "3");

        // 右进,左出
        System.out.println(lru);//{1=1, 2=2, 3=3}

        lru.put("4", "4");
        System.out.println(lru);//{2=2, 3=3, 4=4}

        lru.get("2");
        System.out.println(lru);//{3=3, 4=4, 2=2}最新最少使用的在左边

        lru.put("5","5");
        System.out.println(lru);//{4=4, 2=2, 5=5}
    }

1、原理

put -> afterNodeInsertion -> removeEldestEntry(如果size > limt了就会触发removeNode方法) -> removeNode

        if (evict && (first = head) != null && removeEldestEntry(first)) {
            K key = first.key;
            removeNode(hash(key), key, null, false, true);
        }
  1. 设计模式:MyLRU extends LinkedHashMap 这样MyLRU 就具有了LinkedHashMap的全部方法,对外提供的方法太多了。违背了接口隔离原则。可以在MyLRU 中定义内部类去extends LinkedHashMap,去重写removeEldestEntry。MyLRU 只需要提供对外暴露的几个方法即可
4.1.2 缺点&改进

1、这里的lru是强引用。也就是说存入的数据,GC无法直接回收。可能造成OOM

    @Test
    public void test() {
        MyLRU<String, byte[]> cache = new MyLRU<>(100);

        for (int i = 0; i < 100; i++) {
            cache.put(String.valueOf(i), new byte[1024 * 1024 * 2]);//2M,指定-Xmx128M -Xms64M -XX:+PrintGCDetails,则cache放不到100个元素,估计60个左右元素就会OOM,java.lang.OutOfMemoryError: Java heap space
            System.out.println(i);
        }
    }

2、使用软引用即可(当内存不够用时,会触发GC将软引用删除)

@AllArgsConstructor
public class MyLRU<K, V> extends LinkedHashMap<K, SoftReference<V>>{

    private Integer limit;


    public MyLRU(int limit) {
        super(16, 0.75f, true);
        this.limit = limit;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, SoftReference<V>> eldest) {
        return size() > limit;
    }
}
    public void test() {
        MyLRU<String, byte[]> cache = new MyLRU<>(100);

        for (int i = 0; i < 100; i++) {
            cache.put(String.valueOf(i), new SoftReference<>(new byte[1024 * 1024 * 2]));//2M,指定-Xmx128M -Xms64M -XX:+PrintGCDetails,因为Value是弱引用的,当cache存储60个左右元素时,发现内存不够了,则会GC,回收若引用的内容,不会造成OOM
            System.out.println(i);
        }
    }

        // 如果弱引用内容被回收了,则get时
        SoftReference<byte[]> softReference = cache.get("1");
        if (softReference == null) {
            //去db查询真实的数据
            return ---
        } else {
            return softReference.get();
        }

3、补充:虚引用PhantomReference:

  • 虚引用解释:虚引用会被放到queue中,当对象被回收之后,所以虚引用经常被用作跟踪你这个被回收对象中是否有指向其他内存的资源是否被回收
  • 实战:commons-io中FileCleaningTracker(q.remove()这里的q就是ReferenceQueue)
public void run() {
     FileCleaningTracker.Tracker tracker = (FileCleaningTracker.Tracker)FileCleaningTracker.this.q.remove();
     FileCleaningTracker.this.trackers.remove(tracker);
     if (!tracker.delete()) {
            FileCleaningTracker.this.deleteFailures.add(tracker.getPath());
      }

      tracker.clear();
}
     /**
     * Removes the next reference object in this queue, blocking until one
     * becomes available.
     */
	public Reference<? extends T> remove() throws InterruptedException {
        return remove(0);
    }
  • 这里的q.remove()是阻塞方法,如果q中有元素则返回引用,如果q中没元素,则一直再这儿阻塞住等到虚引用回收的对象放入q中。起到了监听作用
  • 然后Tracker再根据q中虚引用信息,找到对应的资源再去删除,避免了资源无法删除问题

4.2 Cache

这里的cache都是进程级别的不是分布式的

五.EventBus

5.1 EventBus

1. create、post、register、@Subscribe
  • register:类似MQ的subscriber订阅者即消费组

  • post:类似MQ的Provider类中的send方法

  • @Subscribe:类似于Consumer类中的receive方法

    如果receive方法耗时特别长,建议异步起线程执行。否则影响下一个event的接收,反而影响bus

场景一:event本身为字符串

  • 生产者
public class Provider {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();

        // subscriber订阅者
        eventBus.register(new MyConsumer());

        // send
        eventBus.post("mjp");
    }
}
  • 消费者
public class MyConsumer {

    @Subscribe
    public void receive(String event){
        System.out.println(event);
    }
}

场景二:event本身为类

  • 生产者
public class Provider {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();

        // subscriber订阅者
        eventBus.register(new MyConsumer());

        // send
        eventBus.post(new User("mjp", 18));
    }
}
  • 消费者
public class MyConsumer {

    @Subscribe
    public void receive(User user){
        System.out.println(user.getName());
    }
}
2.Exception handle
public class Provider {
    public static void main(String[] args) {
        // 匿名内部类,相当于消费者receive消费方法抛出异常了。会走到{}处理,不影响总的Bus
        final EventBus eventBus = new EventBus((e, context) -> {
            log.error("consumer:[{}], receive方法:[{}], msg:[{}], 流程异常",
                    GsonUtil.toJsonString(context.getSubscriber()),
                    GsonUtil.toJsonString(context.getSubscriberMethod()),
                    GsonUtil.toJsonString(context.getEvent()), e);
        });

        // subscriber订阅者
        eventBus.register(new MyConsumer());

        // send
        eventBus.post(new User("mjp", 0));
    }
}
public class MyConsumer {

    @Subscribe
    public void myReceive(User user){
        if (user.getAge() < 1) {
            throw  new IllegalArgumentException("年龄错误");
        }
    }
}

5.2 AsyncEventBus

背景:

        eventBus.register(new MyConsumer());
        eventBus.register(new MyConsumer2());

        // send
        eventBus.post(new User("mjp", 0));
        eventBus.post("hello");
public class MyConsumer {

    @Subscribe
    public void myReceive(User event) throws InterruptedException {
        TimeUnit.SECONDS.sleep(5L);//耗时特别久
        System.out.println(event.getName());
    }
}
  • 如果其中某个Consumer的receive方法耗时特别久,因为是同步执行,所以可能会影响其他register在这个bus上的consumer,为此,我们可以使用异步AsyncEventBus,这样多个consumer之间可以并行执行
public class Provider {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(2);

        AsyncEventBus asyncEventBus = new AsyncEventBus(threadPool, ((e, context) -> {
            log.error("consumer:[{}], receive方法:[{}], msg:[{}], 流程异常",
                    GsonUtil.toJsonString(context.getSubscriber()),
                    GsonUtil.toJsonString(context.getSubscriberMethod()),
                    GsonUtil.toJsonString(context.getEvent()), e);
        }));


        // subscriber订阅者
        asyncEventBus.register(new MyConsumer());
        asyncEventBus.register(new MyConsumer2());

        // send
        asyncEventBus.post(new User("mjp", 0));
        asyncEventBus.post("hello");
    }
}
  • 补充:除了使用AsyncEventBus之外,如果某个receive方法耗时特别的就,建议这个receive方法内部使用异步处理的方式,这样可以快速结束receive方法

5.3 场景

1、进程级别

guava的eventbus是个进程内级别的,无法跨进程即不是分布式的(Kafka、RockMQ)

  • 建议使用:

    服务内部解耦:落数据模块、计算模块、合单模块。彼此之间是进程级别的,都会在一台服务的Java进程中执行,没啥问题

  • 不建议使用:returnplan服务调用oih服务,这种要是有Kafka

2、并发不高

高并发环境下不建议使用,因为可能造成线程池拒绝

六. File

6.1 Files

1. 将文件A,copy到文件B
    public void test1() throws IOException {
        File file1 = new File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceA");//必须存在
        File file2 = new File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceB");//不存在会创建
        Files.copy(file1, file2);
    }
2. 将文件A,move到文件B

本质相当于将sourceB文件名称换为sourceC

    public void test1() throws IOException {
        File file1 = new File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceB");
        File file2 = new File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceC");
        Files.move(file1, file2);
    }
3. 将文件A的内容,读取为字符串
    public void test1() throws IOException {
        File file2 = new File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceC");
        List<String> strings = Files.readLines(file2, UTF_8);
        String res = Joiner.on("\n").join(strings);
        System.out.println(res);//结果和在文件中一样,还是换行展示的字符串
    }

6.2 charSource: 一行一行读取文件并解析

1.将文件A的内容,读取为字符串
    public void test1() throws IOException {
        File file = new File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceC");
        CharSource charSource = Files.asCharSource(file, UTF_8);
        String res = charSource.read();
        System.out.println(res);
    }
2. 一行一行读取文件A的内容,并在一行一行读取过程中,使用行解析器自定义处理每行内容

source file文件的内容如下

i
love

java 1314
public void test1() throws IOException {
        File file = new File("D:\\CodeBetter\\src\\main\\resources\\io\\source");
        CharSource charSource = Files.asCharSource(file, UTF_8);

        LineProcessor<List<Integer>> listLineProcessor = new LineProcessor<List<Integer>>() {
            private final List<Integer> lineLengthList = new ArrayList<>();
            @Override
            public boolean processLine(String s) throws IOException {
                // 获取file文件每行的字符个数,将长度添加至list中,如果遇到空行,则结束对文件的处理
                if (s.length() == 0) {
                    return false;
                }
                lineLengthList.add(s.length());
                return true;
            }

            @Override
            public List<Integer> getResult() {
                return lineLengthList;
            }
        };
        List<Integer> lineLengthList = charSource.readLines(listLineProcessor);
        System.out.println(lineLengthList);//[1, 4]因为第三行是空行,长度为0,return false,则后面的行都不再解析
    }

6.3 Charsink:将字符串写入文件

1.覆盖写
    public void test1() throws IOException {
        File file = new File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceA");
        String s = "i" +
                "\n" +
                "love" +
                "\n" +
                "java";
        Files.asCharSink(file, UTF_8).write(s);//每次执行都是从头开始覆盖写
    }
2.追加写
    public void test1() throws IOException {
        File file = new File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceA");
        String s1 = "mjp";
        CharSink charSink = Files.asCharSink(file, UTF_8, FileWriteMode.APPEND);
        charSink.write(s1);

        String s2 = "wxx";
        charSink.write(s2);
    }

文件内容为mjpwxx

6.4 文件|文件夹操作

递归遍历查找rootFile下的所有文件和文件夹

    public void test1() throws IOException {
        File rootFile = new File("D:\\CodeBetter\\src\\main\\java\\com");
        Traverser<File> fileTraverser = Files.fileTraverser();
        Iterable<File> fileIterable = fileTraverser.depthFirstPreOrder(rootFile);
        fileIterable.forEach(f -> {
            if (f.isFile()) {
                System.out.println(f);
            }
        });
    }

6.5 closer-关闭IO

1.try-catch-final
public void test1() throws IOException {
        try {
            throw new IOException("read error");
        } catch (IOException e) {
            throw e;
        } finally {
            try {
                throw new IOException("close  error");//close关闭资源中发送了异常
            } catch (Exception e) {
                throw e;
            }
        }
    }
等效:try-with
    public void test() throws IOException {
        try(FileInputStream fileInputStream = new FileInputStream(new File("a"))) {
            fileInputStream.read();
        } catch (IOException e) {
            throw new IOException("read error");
        }
    }

try-with形式会自动finally关闭资源,原理是InputStream实现了Closeable接口,重写了close()方法

    public void close() throws IOException {
        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

以上方式都有个问题即:在close资源的时候如果也发送了异常,则close异常会覆盖掉read异常

java.io.IOException: close  error//只有close时异常,没有read时候的异常了
3.closer
    public void test1() throws Exception {
        Closer closer = Closer.create();
        ByteSource byteSource = Files.asByteSource(new 		                        File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceA"));
        try {
            InputStream inputStream = closer.register(byteSource.openStream());
            closer.register(inputStream);
        } catch (Exception e) {
            throw closer.rethrow(e);
        } finally {
            closer.close();
        }
    }

这样在close异常的时候,也不会覆盖try中的异常,2个异常都会抛出来

6.6 BaseEncoding

    public void test1(){
        BaseEncoding base64 = BaseEncoding.base64();
        String encode = base64.encode("hello".getBytes());
        System.out.println(encode);//aGVsbG8=

        byte[] bytes = base64.decode(encode);
        String decode = new String(bytes);
        System.out.println(decode);//hello
    }

1、原理

a -> 97 -> 01100001 ->从高位去6位,剩下的不够6位补0(这里需要补4个0) -> 011000 0100000 -> 二进制(6位补0变8位)转为十进制24 16 ->去64位编码表中找24 和 16对应的字符 -> Y Q -> 然后结果再根据补0的个数添加=,2个0对应一个= 即YQ==

  • 数字转为二进制
    public void test1(){
        String binary = Integer.toBinaryString(97);
        System.out.println(binary);//1100001
    }
  • 二进制转数字
    public void test1(){
        Integer value = Integer.valueOf("1100001", 2);
        System.out.println(value);//97
    }

上方式都有个问题即:在close资源的时候如果也发送了异常,则close异常会覆盖掉read异常

java.io.IOException: close  error//只有close时异常,没有read时候的异常了
3.closer
    public void test1() throws Exception {
        Closer closer = Closer.create();
        ByteSource byteSource = Files.asByteSource(new 		                        File("D:\\CodeBetter\\src\\main\\resources\\io\\sourceA"));
        try {
            InputStream inputStream = closer.register(byteSource.openStream());
            closer.register(inputStream);
        } catch (Exception e) {
            throw closer.rethrow(e);
        } finally {
            closer.close();
        }
    }

这样在close异常的时候,也不会覆盖try中的异常,2个异常都会抛出来

6.6 BaseEncoding

    public void test1(){
        BaseEncoding base64 = BaseEncoding.base64();
        String encode = base64.encode("hello".getBytes());
        System.out.println(encode);//aGVsbG8=

        byte[] bytes = base64.decode(encode);
        String decode = new String(bytes);
        System.out.println(decode);//hello
    }

1、原理

a -> 97 -> 01100001 ->从高位去6位,剩下的不够6位补0(这里需要补4个0) -> 011000 0100000 -> 二进制(6位补0变8位)转为十进制24 16 ->去64位编码表中找24 和 16对应的字符 -> Y Q -> 然后结果再根据补0的个数添加=,2个0对应一个= 即YQ==

  • 数字转为二进制
    public void test1(){
        String binary = Integer.toBinaryString(97);
        System.out.println(binary);//1100001
    }
  • 二进制转数字
    public void test1(){
        Integer value = Integer.valueOf("1100001", 2);
        System.out.println(value);//97
    }

你可能感兴趣的:(guava,java)