10.1 字符串相关的类

10.1.1 String

1、String的特性

    public final class String
        implements java.io.Serializable, Comparable<String>, 
                                CharSequence,Constable, ConstantDesc {
            @Stable
        private final byte[] value;
            private int hash;
  • String:代表字符串。Java程序中的所有字符串字面值(如“abc”)都作为此类的实例实现。

  • String是一个final类,代表不可变的字符序列,不可以被继承。

  • 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。

  • String对象的字符内容是存储在一个final的字符数组value[]中的。

  • String实现了Serializable接口,表示字符串是支持序列化的;实现了Comparable接口,表示String可以比较大小。

  • String代表不可变的字符序列。简称:不可变性。

    • 当对字符串重新赋值时,需要重新制定该内存区域赋值,不能使用原有的value进行赋值。
    • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能在原有的value上进行赋值。
    • 当调用String的replace()方法修改制定字符或字符串时,也需要重新指定内存区域赋值,不能在原有的value上进行赋值。
  • 通过字面量的方式(区别与new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。

  • 字符串常量池中是不会存储相同内容的字符串的。

    // 不可变性
    @Test
    public void test1() {
        String s1 = "abc";  // 字面量的定义方式
        String s2 = "abc";
        System.out.println(s1 == s2);   // 比较s1和s2的地址值
        s1 = "hello";
        System.out.println(s1); // "hello"
        System.out.println(s2); // "abc"
        String s3 = "abc";
        s3 += "def";
        System.out.println("-------------------");
        System.out.println(s3); // "abcdef"
        System.out.println(s2);
        System.out.println("-------------------");
        String s4 = "abc";
        String s5 = s4.replace('a', 'm');
        System.out.println(s4);
        System.out.println(s5);
    }

    2、String对象的创建

      String str = "hello";
    
      // 本质上this.value = new char[0]
      String s1 = new String();
    
      // this.value = original.value;
      String s2 = new String(String original);
    
      // this.value = Arrays.copyof(value, value.length);
      String s3 = new String(char[] a);
    
      String s4 = new String(char[] a, int startIndex, int count);
  • "abc"与new String("abc")的区别?

    • 字符串常量存储在字符串常量池,目的是共享。

    • 字符串非常量对象存储在堆中。

    • new String("abc")在内存中创建了两个对象:一个是堆空间中new结构,另一个是char[]对应的常量池中的数据:"abc"。

      /**
      
      * String的实例化方式:
      * 方式一:通过字面量定义的方式
      * 方式二:通过new+构造器的方式
       */
       @Test
       public void test2() {
       // 通过字面量定义的方式:此时的s1和s2的数据JavaEE声明在方法区中的字符串常量池中
       String s1 = "javaEE";
       String s2 = "javaEE";
       // 通过new + 构造器的方式:此时的s3和s4保存的地址值,是数据在堆空间开辟空间以后对应的地址值
       String s3 = new String("javaEE");
       String s4 = new String("javaEE");
       System.out.println(s1 == s2);   // true
       System.out.println(s1 == s3);   // false
       System.out.println(s1 == s4);   // false
       System.out.println(s3 == s4);   // false
       }
  • 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。

  • 只要其中有一个是变量,结果就在堆中。

  • 如果拼接的结果调用intern()方法,返回值就在常量池中。

    import org.junit.jupiter.api.Test;
    
    import java.io.UnsupportedEncodingException;
    import java.util.Arrays;
    
    public class StringMethodTest {
        /**
         * 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。
         * 只有其中有一个是变量,结果就在堆中。
         * 如果拼接的结果调用intern()方法,返回值就在常量池中。
         */
        @Test
        public void test3() {
            String s1 = "javaEE";
            String s2 = "hadoop";
            String s3 = "javaEEhadoop";
            String s4 = "javaEE" + "hadoop";
            String s5 = s1 + "hadoop";
            String s6 = "javaEE" + s2;
            String s7 = s1 + s2;
            final String s8 = "javaEE";  // 常量
            String s9 = s8 + "hadoop";
            System.out.println(s3 == s9);   // true
            System.out.println(s3 == s4);   // true
            System.out.println(s3 == s5);   // false
            System.out.println(s3 == s6);   // false
            System.out.println(s5 == s6);   // false
            System.out.println(s3 == s7);   // false
            System.out.println(s5 == s7);   // false
            String s10 = s5.intern();        // 返回得到的s8使用的常量池中已经存在的"javaEEhadoop"
            System.out.println(s3 == s10);   // true
        }
    }

    3、String常用方法

  • int length():返回字符串的长度(return value.length)。

  • char charAt(int index):返回某索引处的字符(return value[index])。

  • boolean isEmpty():判断是否是空字符串(return value.length == 0)。

  • String toLowerCase():使用默认语言环境,将String中的所有字符转换为小写。

  • String toUpperCase():使用默认语言环境,将String中的所有字符转换为大写。

  • String trim():返回字符串的副本,忽略前导空白和尾部空白。

  • boolean equals(Object obj):比较字符串的内容是否相同。

  • boolean equalsIgnoreCase(String anotherString):与equals方法类似,忽略大小写。

  • String concat(String str):将指定字符串连接到此字符串的结尾。等价于使用连接符“+”。

  • int compareTo(String anotherString):比较两个字符串的大小。

  • String substring(int beginIndex):返回一个新的字符串,它是此字符串的从beginIndex开始截取到最后的一个子字符串。

  • String substring(int beginIndex, int endIndex):返回一个新字符串,它是此字符串从beginIndex开始截取到endIndex(不包含)的一个子字符串。

    import org.junit.jupiter.api.Test;
    
    public class StringMethodTest {
        @Test
        public void test1() {
            String s1 = "HelloWorld";
            System.out.println(s1.length());    // 10
            System.out.println(s1.charAt(1));   // 3
    //        System.out.println(s1.charAt(10)); java.lang.StringIndexOutOfBoundsException
            System.out.println(s1.isEmpty());   // false
            String s2 = s1.toLowerCase();
            System.out.println(s1);     // HelloWorld
            System.out.println(s2);     // helloworld
            String s3 = "  hello world  ";
            String s4 = s3.trim();
            System.out.println("--" + s3 + "--");   // "--  hello world  --"
            System.out.println("--" + s4 + "--");   // "--hello world--"
            System.out.println(s1.equals(s2));      // false
            System.out.println(s1.equalsIgnoreCase(s2));    // true
            String s5 = "abc";
            String s6 = s5.concat("def");
            System.out.println(s6);     //"abcdef"
            String s7 = new String("abd");
            System.out.println(s5.compareTo(s7));   // -1,涉及到字符串排序
            String s8 = "0123456789";
            String s9 = s8.substring(2);
            System.out.println(s8);     // "0123456789"
            System.out.println(s9);     // "23456789"
            String s10 = s8.substring(2, 5);
            System.out.println(s10);    // "234"
        }
    }
  • boolean endsWith(String suffix):测试此字符串是否以指定的后缀结束。

  • boolean startsWith(String prefix):测试此字符串是否以指定的前缀开始。

  • boolean startsWith(String prefix, int toffset):测试此字符串从指定索引开始的子字符串是否以指定前缀开始。

  • boolean contains(CharSequences s):当且仅当此字符串包含指定的char值序列时,返回true。

  • int indexOf(String str):返回指定子字符串在此字符串中第一次出现处的索引。

  • int indexOf(String str, int fromIndex):返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。

  • int lastIndexOf(String str):返回指定子字符串在此字符串中最右边出现处的索引。

  • int lastIndexOf(String str, int fromIndex):返回指定字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。

    import org.junit.jupiter.api.Test;
    
    public class StringMethodTest {
        @Test
        public void test1() {
            String s1 = "helloworld";
            System.out.println(s1.endsWith("ld"));  //true
            System.out.println(s1.startsWith("He"));    // false
            System.out.println(s1.startsWith("ll", 2)); // true
            System.out.println(s1.contains("hello"));   // true
            System.out.println(s1.indexOf("ll"));   // 2
            System.out.println(s1.indexOf("lll"));   // -1
            System.out.println(s1.indexOf("l", 4)); // 8
            System.out.println(s1.lastIndexOf("l"));    // 8
            System.out.println(s1.lastIndexOf("l", 7)); // 3
        }
    }
  • String replace(char oldChar, char newChar):返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的。

  • String replace(CharSequence target, CharSequence replacement):使用指定的字面值替换序列替换此字符串所有匹配字面值目标序列的子字符串。

  • String replaceAll(String regex, String replacement):使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串。

  • String replaceFirst(String regex, String replacement):使用给定的replacement替换此字符串匹配给定的正则表达式的第一个子字符串。

  • boolean matches(String regex):告知此支付查是否匹配给定的正则表达式。

  • String[] split(String regex):根据给定正则表达式的匹配拆分此字符串。

  • String[] split(String regex, int limit):根据匹配给定的正则表达式来拆分此字符串,最多不超过limit个,如果超过了,剩下的全部都放到最后一个元素中。

    import org.junit.jupiter.api.Test;
    
    import java.util.Arrays;
    
    public class StringMethodTest {
        @Test
        public void test1() {
            String s1 = "001122334455";
            String s2 = s1.replace('5', '8');
            System.out.println(s1);     //"001122334455"
            System.out.println(s2);     //"001122334488"
            String s3 = s1.replace("0", "1");
            System.out.println(s3);     //"111122334455"
            String s4 = s1.replaceAll("\\d+", "a");
            System.out.println(s4);     // "a"
            System.out.println(s1.matches("\\d+")); // true
            String[] split = s1.split("");
            System.out.println(Arrays.toString(split)); // [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
            String[] split1 = s1.split("", 5);
            System.out.println(Arrays.toString(split1)); // [0, 0, 1, 1, 22334455]
        }
    }

    4、String与其他结构之间的转换

  • String ←→ 基本数据类型、包装类

    import org.junit.jupiter.api.Test;
    
    import java.util.Arrays;
    
    public class StringMethodTest {
        /**
         * String <---> 基本数据类型、包装类
         * 1、String ---> 基本数据类型、包装类:调用包装类的静态方法parseXxx(str)
         * 2、基本数据类型、包装类 ---> String:调用String重载的valueOf(xxx)
         */
        @Test
        public void test1() {
            String s1 = "123";
            int num = Integer.parseInt(s1);
            System.out.println(num);    //123
            String s2 = String.valueOf(num);
            String s3 = num + "";       //"123",存放在堆里
            System.out.println(s2);     //"123"
        }
    }
  • String ←→ char[]

    import org.junit.jupiter.api.Test;
    
    import java.util.Arrays;
    
    public class StringMethodTest {
        /**
         * String <---> char[]
         * 1、String ---> char[]:调用String的toCharArray()
         * 2、char[] ---> String:调用String的构造器
         */
        @Test
        public void test1() {
            String s1 = "123";
            char[] chars = s1.toCharArray();
            System.out.println(Arrays.toString(chars)); //[1, 2, 3]
            char[] arr = new char[]{'h', 'e', 'l', 'l', 'o'};
            String s2 = new String(arr);
            System.out.println(s2); //"hello"
        }
    }
  • String ←→ byte[]

    import org.junit.jupiter.api.Test;
    
    import java.io.UnsupportedEncodingException;
    import java.util.Arrays;
    
    public class StringMethodTest {
        /**
         * String <---> byte[]
         * 1、String ---> byte[]:调用String的getBytes()
         * 2、byte[] ---> String:调用String的构造器
         * 
         * 编码:字符串 --> 字节
         * 解码:字节 --> 字符串
             * 说明:解码时,要求解码使用的字符集必须与编码使用的字符集一致。
         */
        @Test
        public void test1() throws UnsupportedEncodingException {
            String s1 = "abc123中国";
            byte[] bytes1 = s1.getBytes();  // 使用默认的字符集,进行编码
            System.out.println(Arrays.toString(bytes1)); //[97, 98, 99, 49, 50, 51, -28, -72, -83, -27, -101, -67]
            byte[] gbks = s1.getBytes("gbk");   // 使用gbk字符集进行编码
            System.out.println(Arrays.toString(gbks));  //[97, 98, 99, 49, 50, 51, -42, -48, -71, -6] 
    
            String s2 = new String(bytes1); // 使用默认的字符集,进行解码
            System.out.println(s2);     //"abc123中国"
            String s3 = new String(gbks,"gbk"); // 使用gbk字符集进行解码
            System.out.println(s3); //"abc123中国"
        }
    
    }

    5、常见算法题目

  • 模拟一个trim方法,去除字符串两端的空格。

  • 将一个字符串进行反转。将字符串中指定部分进行反转。比如"abcdefg"反转为"abfedcg"。

    import org.junit.jupiter.api.Test;
    
    public class StringTest {
    
        @Test
        public void test() {
            String s = "0123456";
            String reverse = reverse1(s, 2, 5);
            System.out.println(reverse);    //0154326
            String reverse2 = reverse2(s, 2, 5);
            System.out.println(reverse2);   //0154326
            String reverse3 = reverse3(s, 2, 5);
            System.out.println(reverse3);   //0154326
        }
    
        /**
         * 将一个字符串中指定的部分进行反转。
         * @param str
         * @param startIndex
         * @param endIndex
         * @return
         */
        public static String reverse1(String str, int startIndex, int endIndex) {
            if (str == null || str.length() == 0) {
                return str;
            }
            char[] chars = str.toCharArray();
            char temp;
            for (int x = startIndex, y = endIndex; x < y; x++, y--) {
                temp = chars[x];
                chars[x] = chars[y];
                chars[y] = temp;
            }
            return new String(chars);
        }
    
        public static String reverse2(String str, int startIndex, int endIndex) {
            if (str == null || str.length() == 0) {
                return str;
            }
            String reverseStr = str.substring(0, startIndex);
            for (int i = endIndex; i >= startIndex; i--) {
                reverseStr += str.charAt(i);
            }
            reverseStr += str.substring(endIndex+1);
            return reverseStr;
        }
    
        public static String reverse3(String str, int startIndex, int endIndex) {
            if (str == null || str.length() == 0) {
                return str;
            }
            StringBuilder builder = new StringBuilder(str.length());
            builder.append(str.substring(0, startIndex));
            for (int i = endIndex; i >= startIndex; i--) {
                builder.append(str.charAt(i));
            }
            builder.append(str.substring(endIndex+1));
            return builder.toString();
        }
    }
  • 获取一个字符串在另一个字符串中出现的次数。比如:获取"ab"在"abkkcadkabkebfkabkskab"中出现的次数。

    import org.junit.jupiter.api.Test;
    
    public class StringTest {
    
        @Test
        public void test() {
            String mainStr = "abkkcadkabkebfkabkskab";
            String subStr = "ab";
            int count = getCount1(mainStr, subStr);
            System.out.println(count);  //4
            System.out.println(getCount2(mainStr, subStr));  //4
        }
    
        /**
         * 获取字符串subStr在另一个字符串mainStr中出现的次数。
         * 比如:获取"ab"在"abkkcadkabkebfkabkskab"中出现的次数。
         * @param mainStr
         * @param subStr
         * @return
         */
        public static int getCount1(String mainStr, String subStr) {
            int mLen = mainStr.length();
            int sLen = subStr.length();
            if (mLen < sLen) {
                return 0;
            }
            int count = 0;
            int j;
            boolean flag;
            for (int i = 0; i <= mLen-sLen; i++) {
                j = 0;
                flag = true;
                while (j < sLen) {
                    if (mainStr.charAt(i+j) != subStr.charAt(j++)) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    count++;
                }
            }
            return count;
        }
    
        public int getCount2(String mainStr, String subStr) {
            int mLen = mainStr.length();
            int sLen = subStr.length();
            if (mLen < sLen) {
                return 0;
            }
            int count = 0;
            int index = 0;
    
    //        while ((index = mainStr.indexOf(subStr)) != -1) {
    //            count++;
    //            mainStr = mainStr.substring(index + sLen);
    //        }
            while ((index = mainStr.indexOf(subStr, index)) != -1) {
                count++;
                index += sLen;
            }
            return count;
        }
    }
  • 获取两个字符串中最大相同子串。比如:str1 = "abcwerthelloyuioderf"、str2 = "cvhellobnm"。提示:将短的那个串进行长度依次递减的子串与较长的串比较。

    import org.junit.jupiter.api.Test;
    
    public class StringTest {
    
        @Test
        public void test() {
            String str1 = "abcwerthelloyuioderf";
            String str2 = "cvhellobnm";
            System.out.println(getMaxSameString(str1, str2));   // hello
        }
    
        /**
         * 获取两个字符串中最大相同子串(前提:只有一个最大相同子串)。
         * 比如:str1 = "abcwerthelloyuioderf"、str2 = "cvhellobnm"。
         * 提示:将短的那个串进行长度依次递减的子串与较长的串比较。
         * @param str1
         * @param str2
         * @return
         */
        public static String getMaxSameString(String str1, String str2) {
            if (str1 == null || str2 == null) {
                return null;
            }
            String maxStr = (str1.length() >= str2.length()) ? str1 : str2;
            String minStr = (str1.length() < str2.length()) ? str1 : str2;
            int length = minStr.length();
            for (int i = 0; i < length; i++) {
                for (int x = 0, y = length-i;y <= length;x++,y++) {
                    String subStr = minStr.substring(x, y);
                    if (maxStr.contains(subStr)) {
                        return subStr;
                    }
                }
            }
            return null;
        }
    }
  • 对字符串中字符进行自然顺序排序。提示:1)字符串编程字符数组;2)对数组排序,选择、冒泡、Arrays.sort();3)将排序后的数组编程字符串。

10.1.2 StringBuffer、StringBuilder

1、StringBuffer

  • java.lang.StringBuffer代表可变的字符序列,JDK 1.0中声明,可以对字符串内容进行增删,此时不会产生新的对象。

  • 很多方法与String相同。

  • 方法都是synchronized的。

  • 作为参数传递时,方法内部可以改变值。

    public final class StringBuffer
        extends AbstractStringBuilder
        implements java.io.Serializable, Comparable<StringBuffer>, CharSequence {
            @Override
        public synchronized int length() {
            return count;
        }
            ……
    //=================
    abstract class AbstractStringBuilder implements Appendable, CharSequence {
            byte[] value;  // value没有final,value可以不断扩充
            int count;    // count记录有效字符的个数

    2、StringBuilder

  • 与StringBuffer类似,只是方法不是同步的。

    public final class StringBuilder
        extends AbstractStringBuilder
        implements java.io.Serializable, Comparable<StringBuilder>, CharSequence {
            @Override
        public int compareTo(StringBuilder another) {
            return super.compareTo(another);
        }
            ……
    //=================
    abstract class AbstractStringBuilder implements Appendable, CharSequence {
            byte[] value;  // value没有final,value可以不断扩充
            int count;    // count记录有效字符的个数

    3、StringBuffer、StringBuilder的底层

      import org.junit.jupiter.api.Test;
    
      /**
       * 关于StringBuffer和StringBuilder的使用
       */
      public class StringBufferBuilderTest {
          /**
           * 面试题:String、StringBuffer、StringBuilder的异同
           *    String:不可变的字符序列;JDK 1.0;底层使用char[]存储。
           *    StringBuffer:可变的字符序列;JDK 1.0;线程安全的,效率低;底层使用char[]存储;父类AbstractStringBuilder。
           *    StringBuilder:可变的字符序列;JDK 1.5;线程不安全的,效率高;底层使用char[]存储;父类AbstractStringBuilder。
           *
           *  源码分析:
           *  String str = new String();  // char[] value = new char[0];
           *  String str1 = new String("abc");    // char[] value = new char[]{'a','b','c'};
           *
           *  StringBuffer sb1 = new StringBuffer(); // char[] value = new char[16];的层创建了一个长度是16的数组。
           *  sb1.append('a');    // value[0] = 'a';
           *  sb1.append('b');    // value[1] = 'b';
           *
           *  StringBuffer sb2 = new StringBuffer("abc"); // char[] value = new char["abc".length() + 16];
           *
           *  问题1:System.out.println(sb2.length());  // 3
           *  问题2(扩容问题):如果要添加的数据底层数组装不下了,那就需要扩容底层的数组。
           *                  (ensureCapacityInternal(count + len);)
           *                  开发中建议使用:StringBuffer(int capacity)或StringBuilder(int capacity)
           */
    
          @Test
          public void test1() {
              StringBuffer s1 = new StringBuffer("abc");
              s1.setCharAt(0, 'm');
              System.out.println(s1);     //"mbc"
              System.out.println(s1.length());    // 3
          }
      }

    4、StringBuffer的常用方法(StringBuilder与此相似)

  • StringBuffer append(xxx):提供了很多的append()方法,用于进行字符串拼接。

  • StringBuffer delete(int start, int end):删除指定位置的内容。

  • StringBuffer replace(int start, int end, String str):把[start, end)位置替换为str。

  • StringBuffer insert(int offset, xxx):在指定位置插入xxx。

  • StringBuffer reverse():把当前字符序列逆转。

  • public int indexOf(String str)

  • public String substring(int start, int end)

  • public int length()

  • public char charAt(int n)

  • public void setCharAt(int n, char ch)

    import org.junit.jupiter.api.Test;
    
    /**
    
     * 关于StringBuffer和StringBuilder的使用
       */
       public class StringBufferBuilderTest {
    
       @Test
       public void test1() {
           StringBuffer s1 = new StringBuffer("abc");
           s1.append(1);
           s1.append('1');
           System.out.println(s1); //"abc11"
           s1.delete(2, 4);
           System.out.println(s1); //"ab1"
           s1.replace(1, 2, "hello");
           System.out.println(s1); //"ahello1"
           s1.insert(0, false);
           System.out.println(s1); //"falseahello1"
           s1.reverse();
           System.out.println(s1); //"1ollehaeslaf"
       }
       }
  • 方法链操作:return this

  • 总结:

    • 增:append()
    • 删:delete()
    • 改:setCharAt()/replace()
    • 查:charAt()
    • 插:insert()
    • 长度:length()
    • 遍历:for + charAt() / toString()

5、String、StringBuffer、StringBuilder的异同

  • String:不可变的字符序列;JDK 1.0;底层使用char[]存储。
  • StringBuffer:可变的字符序列;JDK 1.0;线程安全的,效率低;底层使用char[]存储;父类AbstractStringBuilder。
  • StringBuilder:可变的字符序列;JDK 1.5;线程不安全的,效率高;底层使用char[]存储;父类AbstractStringBuilder。

6、String与StringBuffer、StringBuilder之间的转换

  • String —> StringBuffer、StringBuilder:调用StringBuffer、StringBuilder的构造器
  • StringBuffer、StringBuilder —> String:
    • 调用String构造器
    • StringBuffer、StringBuilder的toString()方法

7、练习

    import org.junit.jupiter.api.Test;

    public class StringTest {

        @Test
        public void test() {
            String str = null;
            StringBuffer sb = new StringBuffer();
            sb.append(str);     //

            System.out.println(sb.length());

            System.out.println(sb);    // 4

            StringBuffer sb1 = new StringBuffer(str);   // java.lang.NullPointerException
            System.out.println(sb1);    // 不执行
        }

        /**底层:
         *     public AbstractStringBuilder append(String str) {
         *         if (str == null) {
         *             return appendNull();
         *         }
         *         int len = str.length();
         *         ensureCapacityInternal(count + len);
         *         putStringAt(count, str);
         *         count += len;
         *         return this;
         *     }
         *     
         *         private AbstractStringBuilder appendNull() {
         *         ensureCapacityInternal(count + 4);
         *         int count = this.count;
         *         byte[] val = this.value;
         *         if (isLatin1()) {
         *             val[count++] = 'n';
         *             val[count++] = 'u';
         *             val[count++] = 'l';
         *             val[count++] = 'l';
         *         } else {
         *             count = StringUTF16.putCharsAt(val, count, 'n', 'u', 'l', 'l');
         *         }
         *         this.count = count;
         *         return this;
         *     }
         *
         *     public StringBuffer(String str) {
         *         super(str.length() + 16);
         *         append(str);
         *     }
         */

    }

10.2 JDK8之前的日期时间API

1、java.lang.System

  • System类提供的public static long currentTimeMillis()用来返回当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差。

  • 此方式适于计算时间差。

    import org.junit.jupiter.api.Test;
    
    public class TimeTest {
        /**
         * java.lang.System.currentTimeMillis()
         */
        @Test
        public void test() {
            // 当前时间与1970年1月1日0时0分0秒之间以毫秒为单位的时间差(时间戳)。
            long timeMillis = System.currentTimeMillis();
            System.out.println(timeMillis);
        }
    }
  • 计算世界时间的主要标准有:

    • UTC(Coordinated Universal Time)
    • GMT(Greenwich Mean Time)
    • CST(Central Standard Time)

2、java.util.Date

  • 表示特定的瞬间,精确到毫秒。

  • 构造器:

    • Date():使用无参构造器创建的对象可以获取本地当前时间。
    • Date(long date)
  • 常用方法:

    • getTime():返回自1970年1月1日00:00:00 GMT以来此Date对象表示的毫秒数。
    • toString():把此Date对象转换为以下形式的String:dow mon dd hh:mm:ss zzz yyyy。其中dow是一周中的某一天(Sun,Mon,Tue,Wed,Thu,Fri,Sat),zzz是事件标准。
    • 其他很多方法都过时了。
      import org.junit.jupiter.api.Test;
      import java.util.Date;
      public class TimeTest {
        /**
         * java.util.Date
         */
        @Test
        public void test() {
            // 构造器一:Date()创建一个对应当前时间的Date对象
            Date date1 = new Date();
            System.out.println(date1);  //Mon Feb 24 14:17:47 CST 2020
            System.out.println(date1.getTime());    //1582525067541
            // 构造器二:Date(long)创建指定毫秒数的Date对象
            Date date2 = new Date(1582525067541L);
            System.out.println(date2);  //Mon Feb 24 14:17:47 CST 2020
        }
      }
  • java.sql.Date

    import org.junit.jupiter.api.Test;
    
    public class TimeTest {
        /**
         * java.sql.Date extends java.util.Date
         */
        @Test
        public void test() {
            // 如何实例化
            java.sql.Date date = new java.sql.Date(1582525067541L);
            System.out.println(date);   //2020-02-24
    
            // java.sql.Date ---> java.util.Date:多态
            java.util.Date date1 = date;
            System.out.println(date1);  //2020-02-24
    
            // java.util.Date ---> java.sql.Date
            java.util.Date date2 = new java.util.Date();
            System.out.println(date2);  //Mon Feb 24 14:31:34 CST 2020
            java.sql.Date date3 = new java.sql.Date(date2.getTime());
            System.out.println(date3);  //2020-02-24
        }
    }

    3、java.text.SimpleDateFormat

  • Date类的API不易于国际化,大部分被废弃了,java.text.SimpleDateFormat类是一个不与语言环境有关的方式来格式化和解析日期的具体类。

  • 它允许进行格式化:日期—>文本;解析:文本—>日期。

  • 格式化:

    • SimpleDateFormat():默认的模式和语言环境创建对象。
    • public SimpleDateFormat(String pattern):该构造方法可以用参数pattern指定的格式创建鬼对象,该对象调用:public String format(Date date)方法格式化时间对象date。
  • 解析:

    • public Date parse(String source):从给定字符串的开始解析文本,以生成一个日期。

      import org.junit.jupiter.api.Test;
      import java.text.ParseException;
      import java.text.SimpleDateFormat;
      import java.util.Date
      public class SimpleDateFormatTest {
        /**
         * SimpleDateFormat的使用:对日期Date类的格式化和解析。
         * 1、两个操作:
         *      格式化:日期 ---> 字符串
         *      解析:字符串 ---> 日期
         * 2、实例化
         *      SimpleDateFormat()
         *      SimpleDateFormat(String pattern)
         */
        @Test
        public void test() throws ParseException {
            // 实例化:使用默认的构造器
            SimpleDateFormat sdf = new SimpleDateFormat();
      
            // 格式化:日期 ---> 字符串
            Date date = new Date();
            System.out.println(date);   //Mon Feb 24 17:12:48 CST 2020
            String formatDate = sdf.format(date);
            System.out.println(formatDate);     //2020/2/24 下午5:12
      
            // 解析:字符串 ---> 日期
            String str = "2020/1/23 上午0:00";
            Date date1 = sdf.parse(str);
            System.out.println(date1);      //Thu Jan 23 00:00:00 CST 2020
      
            //***************************
            // 调用带参的构造器,按照指定的方式格式化和解析
            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            String format = sdf1.format(date);
            System.out.println(format);     //2020-02-24 05:19:58
            Date date2 = sdf1.parse("2020-01-23 05:19:58"); // 字符串必须符合格式,否则抛异常
            System.out.println(date2);      //Thu Jan 23 05:19:58 CST 2020
        }
      }
  • 练习:字符串 —> java.sql.Date

    import org.junit.jupiter.api.Test;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class SimpleDateFormatTest {
    
        @Test
        public void test() throws ParseException {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String str = "2020-09-08";
            Date date = dateFormat.parse(str);
            java.sql.Date sqlDate = new java.sql.Date(date.getTime());
            System.out.println(sqlDate);    //2020-09-08
        }
    
    }
  • 练习:“三天打鱼两天晒网”

4、java.util.Calendar

  • Calendar是一个抽象基类,主要用于完成日期字段之间的相互操作的功能。

    public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
            public static Calendar getInstance() {
            Locale aLocale = Locale.getDefault(Locale.Category.FORMAT);
            return createCalendar(defaultTimeZone(aLocale), aLocale);
        }
  • 获取Calendar实例的方法:

    • Calendar.getInstance()
    • 子类GregorianCalendar的构造器
  • 一个Calendar的实例时系统事件的抽象表示,通过get(int field)方法来获取想要的时间信息。比如YEAR、MONTH、DAY_OF_WEEK、HOUR_OF_DAY、MINUTE、SECOND

    • public void set(int field, int value)
    • public void add(int field, int amount)
    • public final Date getTime()
    • public final void setTime(Date date)
  • 注意:

    • 获取月份时:一月是0,二月是1,以此类推。

    • 获取星期时:周日是1,周一是2,以此类推。

      import org.junit.jupiter.api.Test;
      import java.util.Calendar;
      import java.util.Date;
      public class CalendarTest {
        @Test
        public void test() {
            // 1、实例化
            // 方式一:创建其子类(GregorianCalendar)的对象
            // 方式二:调用其静态方法getInstance()
            Calendar calendar = Calendar.getInstance();
      
            // 2、常用方法
            // get()
            int days = calendar.get(Calendar.DAY_OF_MONTH);
            System.out.println(days);
            System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
            // set()
            calendar.set(Calendar.DAY_OF_MONTH, 22);
            System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
            // add()
            calendar.add(Calendar.DAY_OF_MONTH, 3);
            System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
            calendar.add(Calendar.DAY_OF_MONTH, -3);
            System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
            // getTime()
            Date date = calendar.getTime();
            System.out.println(date);
            // setTime()
            calendar.setTime(date);
        }
      }

      10.3 JDK8中新日期时间API

1、新日期时间API出现的背景

  • 问题:
    • 可变性:像日期和时间这样的类应该是不可变的
    • 偏移性:Date中的年份都是从1990年开始的,而月份都是从0开始的。
    • 格式化:格式化只对Date有用,Calendar则不行。
    • 此外,它们也不是线程安全的;不能处理闰秒等。
  • 第三次引入的API是成功的,并且Java 8中引入的java.time API已经纠正了过去的缺陷,将来很长一段时间内它都会为我们服务。
  • Java 8吸收了Jooda-Time的精华,以一个新的开始为Java创建优秀的API。新的java.time中包含了所有关于本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的Date类新增了toInstant()方法,用于把Date转换成新的表示形式。这些新增的本地化时间日期API大大简化了日期时间和本地化的管理。

2、新时间日期API

  • java.time:包含值对象的基础包
  • java.time.chrono:提供对不同的日历系统的访问
  • java.time.format:格式化和解析时间、日期
  • java.time.temporal:包括底层框架和扩展特性
  • java.time.zone:包含时区支持的类
  • 说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概只会用到其中的三分之一。

3、LocalDate、LocalTime、LocalDateTime

  • LocalDate、LocalTime、LocalDateTime类是其中较重要的几个类,它们的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。

  • 它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与失去相关的信息。

    • LocalDate代表IOS格式(yyyy-MM-dd)的日期,可以存储生日、纪念日等日期。
    • LocalTime表示一个时间,而不是日期。
    • LocalDateTime是用来表示日期和时间的,这是一个最常用的类之一。
  • 注:ISO-8601日历系统是国际标准化组织指定的现代公民的日期和时间的表示法,也就是公历。

    import org.junit.jupiter.api.Test;
    
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    
    public class JDK8DateTimeTest {
        /**
         * LocalDate、LocalTime、LocalDateTime
         * 说明:
         *      1、LocalDateTime相较于LocalDate、LocalTime,使用频率更高
         *      2、类似于Calendar
         */
        @Test
        public void test() {
            // now():获取当前的日期、时间、日期时间
            LocalDate localDate = LocalDate.now();
            LocalTime localTime = LocalTime.now();
            LocalDateTime dateTime = LocalDateTime.now();
            System.out.println(localDate);  //2020-02-24
            System.out.println(localTime);  //19:20:48.728399800
            System.out.println(dateTime);   //2020-02-24T19:20:48.729400500
    
            // of():设置指定的年、月、日、时、分、秒没有偏移量
            LocalDateTime localDateTime = LocalDateTime.of(2020, 10, 12, 8, 6, 56);
            System.out.println(localDateTime);  //2020-10-12T08:06:56
    
            // getXxx():获取相关的属性
            System.out.println(localDateTime.getYear());
            System.out.println(localDateTime.getMonth());
            System.out.println(localDateTime.getDayOfMonth());
            System.out.println(localDateTime.getHour());
            System.out.println(localDateTime.getMinute());
            System.out.println(localDateTime.getSecond());
    
            // withXxx():设置相关的属性(体现不可变性)
            LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(22);
            System.out.println(localDateTime);
            System.out.println(localDateTime1);
    
            // plusXxx():加(体现不可变性)
            LocalDateTime localDateTime2 = localDateTime.plusMonths(3);
            System.out.println(localDateTime2);
    
            // minusXxx():减(体现不可变性)
            LocalDateTime localDateTime3 = localDateTime.minusDays(6);
            System.out.println(localDateTime3);
        }
    
    }

    4、Instant

  • Instant:时间线上的一个瞬时点。这可能被用来记录应用共程序中的事件时间戳。

  • 在处理时间和日期的时候,我们通常会想到年、月、日、时、分、秒。然而,这只是时间的一个模型,是面向人类的。第二种通用模型是面向机器的,或者说是连续的。在此模型汇总,时间线中的一个点表示为一个很大的数,这有利于计算机处理。在UNIX中,这个数从1970年开始,以秒为单位;同样的,在Java中,也是从1970年开始,但以毫秒为单位。

  • java.time包通过值类型Instant提供机器视图,不提供处理人类意义上的时间单位。Instant表示时间线上的一点,而不需要任何上下文信息,例如 ,时区。概念上讲,它只是简单的表示自1970年1月1日0时0分0秒(UTC)开始的秒数。因为java.time包是基于纳秒计算的,所以Instant的精度可以达到纳秒级。

    import org.junit.jupiter.api.Test;
    
    import java.time.Instant;
    import java.time.OffsetDateTime;
    import java.time.ZoneOffset;
    
    public class InstantTest {
        /**
         * Instant
         * 类似于java.util.Date
         */
        @Test
        public void test() {
            // now():获取本初子午线对应的标准时间
            Instant now = Instant.now();
            System.out.println(now);    //2020-02-24T11:58:37.035101800Z
    
            // 添加时间的偏移量
            OffsetDateTime offsetDateTime = now.atOffset(ZoneOffset.ofHours(8));
            System.out.println(offsetDateTime); //2020-02-24T19:58:37.035101800+08:00
    
            // 获取自1970年1月1日0时0分0秒(UTC)开始的毫秒数
            long time = now.toEpochMilli();
            System.out.println(time);
    
            // ofEpochMilli():通过给定的毫秒数,获取Instant实例
            Instant instant = Instant.ofEpochMilli(time);
            System.out.println(instant);
        }
    
    }

    5、java.time.format.DateTimeFormatter

      import org.junit.jupiter.api.Test;
    
      import java.time.LocalDate;
      import java.time.LocalDateTime;
      import java.time.format.DateTimeFormatter;
      import java.time.format.FormatStyle;
      import java.time.temporal.TemporalAccessor;
    
      public class DateTimeFormatterTest {
          /**
           * DateTimeFormatter:格式化或解析日期、时间
           * 类似于SimpleDateFormat
           */
          @Test
          public void test() {
              // 方式一:预定义的标准格式。
              // 如:ISO_LOCAL_DATE_TIME
              DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
              // 格式化:日期 --> 字符串
              LocalDateTime localDateTime = LocalDateTime.now();
              String str1 = formatter.format(localDateTime);
              System.out.println(localDateTime);
              System.out.println(str1);
              // 解析:字符串 --> 日期
              TemporalAccessor parse = formatter.parse("2020-02-24T20:26:02.9276941");
              System.out.println(parse);
    
              // 方式二:本地化相关的格式。
              // 如:ofLocalizedDateTime(FormatStyle.LONG),适用于LocalDateTime
              DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
              // 格式化
              String str2 = formatter1.format(localDateTime);
              System.out.println(str2);
              // 如:ofLocalizedDate
              DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
              String str3 = formatter2.format(LocalDate.now());
              System.out.println(str3);
    
              // 方式三:自定义的格式。
              // 如:ofPattern("yyyy-MM-dd hh:mm:ss")
              DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
              // 格式化
              String str4 = formatter3.format(LocalDateTime.now());
              System.out.println(str4);
              // 解析
              TemporalAccessor parse1 = formatter3.parse("2020-02-24 20:36:17");
              System.out.println(parse1);
          }
      }

    10.4 Java比较器

  • 在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。

  • Java中的对象,正常情况下,只能进行比较:==或≠,不能使用>或<。但是在开发场景中,我们需要对多个对象进行排序,言外之意,就需要比较对象的大小。

  • 如何实现?使用两个接口中的任何一个:Comparable或Comparator。

  • Java实现对象的排序的方式有两种:

    • 自然排序:java.lang.Comparable
    • 定制排序:java.util.Comparator

1、java.lang.Comparable(自然排序)

    package java.lang;
    import java.util.*;

    public interface Comparable<T> {
        public int compareTo(T o);
    }
  • 像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象大小的方式。

  • 像String、包装类重写compareTo()方法以后,进行了从小到大的排序。

  • 重写compareTo(obj)的规则:

    • 如果当前对象this大于形参对象obj,则返回正整数;
    • 如果当前对象this小于形参对象obj,则返回负整数;
    • 如果当前对象this等于形参对象obj,则返回零。
  • 对于自定义类来说,如果需要排序,我们可以让自定义类实现Comparable接口,重写compareTo(obj)方法,在compareTo(obj)方法中指明如何排序。

    import org.junit.jupiter.api.Test;
    
    import java.util.Arrays;
    
    public class CompareTest {
        /**
         * Comparable接口的使用举例
         */
        @Test
        public void test1() {
            String[] arr = new String[]{"C", "D", "A", "B"};
            Arrays.sort(arr);
            System.out.println(Arrays.toString(arr));   // [A, B, C, D]
        }
    
        @Test
        public void test2() {
            Goods[] arr = new Goods[4];
            arr[0] = new Goods("A", 9.98);
            arr[1] = new Goods("B", 2.98);
            arr[2] = new Goods("C", 5.98);
            arr[3] = new Goods("D", 4.98);
            Arrays.sort(arr);
            System.out.println(Arrays.toString(arr));
        }
    
    }
    
    class Goods implements Comparable {
        private String name;
        private double price;
    
        public Goods() {
        }
    
        public Goods(String name, double price) {
            this.name = name;
            this.price = price;
        }
    
        @Override
        public String toString() {
            return "Goods{" +
                    "name='" + name + '\'' +
                    ", price=" + price +
                    '}';
        }
    
        @Override
        public int compareTo(Object o) {
            if (o instanceof Goods) {
                Goods that = (Goods) o;
                if (this.price > that.price) {
                    return 1;
                } else if (this.price < that.price) {
                    return -1;
                } else {
                    return 0;
                }
            }
            throw new RuntimeException("传入的数据类型不一致!");
        }
    
    }

    2、java.util.Comparator(定制排序)

      package java.util;
    
      import java.io.Serializable;
      import java.util.function.Function;
      import java.util.function.ToIntFunction;
      import java.util.function.ToLongFunction;
      import java.util.function.ToDoubleFunction;
      import java.util.Comparators;
    
      public interface Comparator<T> {
          int compare(T o1, T o2);
  • 当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用Comparator的对象来排序,强行对多个对象进行整体排序的比较。

  • 重写compare(Object o1, Object o2)方法,比较o1和o2的大小:如果方法返回正整数,则表示o1大于o2;如果返回0,表示相等;返回负整数,表示o1小于o2.

  • 可以将Comparator传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。

  • 还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

    import org.junit.jupiter.api.Test;
    
    import java.util.Arrays;
    import java.util.Comparator;
    
    public class ComparatorTest {
        @Test
        public void test() {
            String[] arr = new String[]{"C", "D", "A", "B"};
            Arrays.sort(arr, new Comparator<String>() {
                @Override
                public int compare(String o1, String o2) {
                    return -o1.compareTo(o2);
                }
            });
            System.out.println(Arrays.toString(arr));
    
            Goods[] arr2 = new Goods[4];
            arr2[0] = new Goods("A", 9.98);
            arr2[1] = new Goods("B", 2.98);
            arr2[2] = new Goods("C", 5.98);
            arr2[3] = new Goods("D", 4.98);
            // 从大到小排序
            Arrays.sort(arr2, new Comparator<Goods>() {
                @Override
                public int compare(Goods o1, Goods o2) {
                    if (o1.getPrice() > o2.getPrice()) {
                        return -1;
                    } else if (o1.getPrice() == o2.getPrice()) {
                        return 0;
                    } else {
                        return 1;
                    }
                }
            });
            System.out.println(Arrays.toString(arr2));
        }
    
    }
    class Goods{
        private String name;
        private double price;
    
        public Goods() {
        }
    
        public Goods(String name, double price) {
            this.name = name;
            this.price = price;
        }
    
        public String getName() {
            return name;
        }
    
        public double getPrice() {
            return price;
        }
    
        @Override
        public String toString() {
            return "Goods{" +
                    "name='" + name + '\'' +
                    ", price=" + price +
                    '}';
        }
    
    }

    3、Comparable与Comparator的使用对比

  • Comparable接口的方式一旦指定,保证实现类的对象在任何位置都可以比较大小。

  • Comparator接口的方式属于临时性的比较。

10.5 System类

  • System类代表系统,系统级的很多属性和控制方法都放在该类的内部。该类位于java.lang包。
  • 由于该类的构造器是private的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static的,所以也可以很方便的进行调用。
  • 成员变量:
    • System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
  • 成员方法:
    • native long currentTimeMillis():该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间和GMT时间1970年1月1号0时0分0秒所差的毫秒数。
    • void exit(int status):该方法的作用是退出程序。其中status的值为0代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
    • void gc():该方法的作用是请求系统进行垃圾回收。至于系统是否立即回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
    • String getProperty(String key):该方法的作用是获得系统中属性名为key的属性对应的值。

10.6 Math类

  • java.lang.Math提供了一系列静态方法用于科学计算。其方法的参数和返回值类型一般为double型。

10.7 BigInteger与BigDecimal

  • Integer类作为int的包装类,能存储的最大整型值为2^(31)-1,Long类也是有限的。如果要表示再打的整数,不管是基本数据类型还是他们的包装类都无能为力了。
  • java.math包的BigInteger可以表示不可变的任意精度的整数。BigInteger提供所有Java的基本整数操作符的对应物,并提供java.lagn.Math的所有相关方法。
  • 一般的Float类和Double类可以用来做科学计算或工程计算,但在商业计算中,要求数字精度比较高,故用到java.math.BIgDecimal类。