常用基础类

  • 包装类
  • 基本用法
  • 包装类的共同点
  • 重写Object方法
  • Comparable
  • 包装类和String
  • 常用常量
  • Number
  • 不可变性
  • 剖析Integer与二进制算法
  • 位翻转
  • 循环移位
  • valueOf的实现
  • 剖析Character
  • 字符属性
  • 字符转换
  • 剖析String
  • 基本用法
  • 常用方法:
  • String内部
  • 编码转换
  • 不可变性
  • 常量字符串
  • hashCode
  • 正则表达式
  • 剖析StringBuilder
  • 基本用法
  • 基本实现原理
  • String的+和+=运算符
  • 剖析Arrays
  • 用法:
  • toString
  • 排序
  • 查找
  • 更多方法:
  • 多维数组
  • 实现原理
  • 二分查找:
  • 排序
  • 小结
  • 剖析日期和时间
  • 基本概念
  • 时区
  • 时刻和纪元时
  • 年历
  • 日期和时间API
  • Date
  • TimeZone
  • Locale
  • Calendar
  • DateFormat
  • SimpleDateFormat
  • 局限性
  • 随机
  • Math.random
  • Random
  • 随机的基本原理
  • 小结


包装类

基本类型

byte

short

int

long

double

float

boolean

char

包装类

Byte

Short

Integer

Long

Double

Float

Boolean

Character

字节数

1

2

4

8

8

4

2

1

基本用法

  • 装箱:Xxx.valueOf(xxx);
  • 拆箱:xxxValueOf(Xxx);
  • Java 5 以后引入了自动装箱和拆箱技术。
  • 除了使用ValueIf方法,还可以new Xxx()。但每次都会创建一个新对象,除了Float和Double,其他包装类都会缓存包装类对象。Java 9 开始,构造方法已经过时,推荐使用静态的valueof方法。

包装类的共同点

重写Object方法

所有的包装类重写了Object的方法:

  • boolean equals(Object obj)
  • int hashCode()
  • String toString()
    1. equals
  • Object类默认实现是比较地地址,对于两个变量,只有两个变量指向同一对象时,equals才会返回true,它和比较运算法==的结果是一样的。
  • queals反映的是对象间的逻辑相等关系,所以默认实现不合适。
  • 重写将包装类拆箱后再进行比较,进行值(内容)的比较
    2.hashCode
  • hashCode返回一个对象的哈希值。哈希值是一个int类型的数,由对象中一般不变的属性映射得来的,用于快速对对象进行区分、分组等。一个对象的哈希值不能改变,相同对象的哈希值必须一样,但可以有对象不同而哈希值相同。
  • hashCode和equals方法联系密切,对两个对象,如果equals方法返回true,则hashCode也必须一样。反之不要求。
  • hashCode的默认实现一般是将对象的内存地址转换为正数。
  • 子类如果重写了equals方法,也必须重写hashCode。

Comparable

  • 每个包装类都实现了Java API 中的Comparable接口。
  • compareTo方法,当前对象与参数对象进行比较,在小于、等于、大于参数时,应分别返回-1、0、1。
public interface Comparable<T>{
	public int compareTo(T o)
}

包装类和String

包装类有与String相关的方法。

  • 除了Character外,每个包装类都有一个静态的valueOf(String)方法,根据字符串表示返回包装类对象。
  • 也有一个静态的parseXxx(String)方法,根据字符串表示返回基本类型值。
//包装类对象
Boolean b = Boolean.valueOf("true");
Float f = Float.valueOf("123.45f");
//基本类型值
boolean b = Boolean.parseBoolean("true");
double b = Double.parseDouble("123.45");
  • 静态的toString方法,根据基本类型值返回字符串表示。
  • 对于整数类型,字符串表示出了默认的十进制外,还可以表示为其他进制,如二进制,八进制,十六进制,包装类有静态方法进行相互转换。
//包装类对象
Integer.toBinaryString(12345);  //输出二进制
Integer.toHexString(12345);  //输出十六进制
Integer.parseInt("3039",16)  //按十六进制解析

常用常量

包装类中除了定义静态方法和实例方法外,还定义了一些静态变量
对于Boolean:

public static final Boolean TRUE = new boolean(true);
public static final Boolean FALSE = new boolean(false);

所有数值类型都定义了 MAX_VALUE 和 MIN_VALUE,表示能表示的最大、最小值。
Integer:

public static final int MAX_VALUE = new boolean(0x80000000);
public static final int MIN_VALUE = new boolean(0x7fffffff);

Float和Double还定义了一些特殊数值,如正无穷,负无穷,非数值。
Double:

public static final double POSITIVE_INFINITY= 1.0/0.0; //正无穷
public static final double NEGATIVE_INFINITY= -1.0/0.0;  //负无穷
public static final double NaN= 0.0d/0.0;  //非数值

Number

6中数值类型包装类有一个共同的父类Number。Number是一个抽象类,定义了如下方法:
通过这些方法,包装类实例可以返回任意的基本数值类型。

byte byteValue()
 short shrotValue()
 int intValue()
 long longValue()
 float floatValue()
 double doubleValue()

不可变性

包装类都是不可变类。不可变:实例对象一旦创建,就没有办法修改了。(数据安全)

  • 所有包装类都声明为final,不能被继承
  • 内部基本类型是私有的,且都声明为final
  • 没有定义setter方法。

剖析Integer与二进制算法

位翻转

Integer有两个静态方法,可以按位进行翻转:

  • reverse是按位进行互换,reverseByte是按byte进行互换。
public static int reverse(int i);
public static int reverseBytes(int i)

方法详解在《Java 编程的逻辑》P154页

  • reverseByte()内部实现通过字节反转(挪动)
  • reserve()内部实现通过二进制反转(挪动)
  • cpu指令不能高效的操作单个位,它操作的最小数据单位一般是32位(32位及其)
  • cpu可以高效地实现移位和逻辑运算,但实现加、减、乘、除运算比较慢。
  • reverse是充分利用CPU的特性,并行高效地执行相邻位的交换。

循环移位

Integer有两个静态方法可以进行循环移位:

  • rightLeft方法是循环左移
  • rotateRight方法时循环右移
  • distance是移动的位数
public static int rotateLeft(int i, int distance)
public static int rotateRight(int i, int distance)

循环移位,是相对于普通的移位而言的,普通移位会进行补0操作。

valueOf的实现

创建包装类对象时建议使用valueOf而不是new。

  • 静态valueOf的类方法中有个IntegerCache类,这是一个私有静态内部类。
  • IntegerCache表示Integer缓存,其中的cache变量是一个静态Integer数组,在静态初始化代码块中被初始化,默认情况下,保存 -128~127共256个整数对应的Integer对象。
  • 在valueOf代码中,如果数值位于被缓存的范围,即默认 -128~127,则直接从IntegerCatche中获取已预先创建的Integer对象,只有不再缓存时,才通过new创建对象。【如果没有创建过,则创建new,如果找到创建过的对象,则返回已经创建过的对象】
  • 通过共享常用对象,可以节省内存空间,由于Integer是不可变的,所以缓存对象可以被安全地共享。【享元模式,Flyweight:共享的轻量级元素】

剖析Character

Character除了封装了char,还封装了Unicode字符级别的高中操作,是Java文本处理的基础,注意:不是char级别,Unicode字符并不等同于char。

此处忽略部分内容

字符属性

Unicode在给每个字符分配一个编号之外,还分配了一些属性,Charater类封装了对Unicode字符属性的检查和操作,下面介绍一些主要的属性。
获取字符类型(general category):

public static int getType(int codePoint)
public static int getType(char ch)

Unicode给每个字符分配了一个类型,这个类型非常重要,很多其他检查和操作都是基于这个类型的。
getType方法的参数可以是int类型的code Point,也可以使char类型

  • char类型只能处理BMP字符
  • int类型可以处理所有字符。
  • Character类定义了很多静态常量表示这些类型。

字符

type值

常量名称

‘A’

1

a

‘a’

2}

LOWERCASE_LETTER

‘马’

5

OTHER_LETTER

‘1’

9

DECIMAL_DIGIT_NUMBER

’ ’

12


‘\n’

15


‘-’

20


‘{’

21


‘_’

23


‘&’

24


‘<’

25


‘$’

26


  • 检查字符是否在Unicode中被定义
  • getType()返回值不为0,则被定义,为0则无定义
//是否被定义
public static boolean isDefined(int codePoint)
//检查字符是否为数字
public static boolean isDigit(int codePoint)
//检查字符是否为字母(Letter)
public static boolean isLetter(int codePoint)
//检查字符是否为字母或数字
public static boolean isLetterOrDigit(int codePoint)
//检查字符是否为字母(Alphabetic)
public static boolean isAlphabetic(int codePoint)
//检查是否为空格字符
public static boolean isSpaceChar(int codePoint)
//检查是否为空格符(更常用)
public static boolean isWhiterspace(int codePoint)
//检查是否为小写字符:
public static boolean isLowerCase(int codePoint)
//检查是否为大写字符
public static boolean isUpperCase(int codePoint)
//检查是否为表意象形文字
public static boolean isIdeographic(int codePoint)
//检查是否为ISO 8859-1编码中的控制字符
public static boolean isISOControl(int codePoint)
//检查是否可作为Java标识符的第一个字符
public static boolean isJavaIdentifierStart(int codePoint)
//检查是否可作为Java标识符的中间字符
public static boolean isJavaIdentifierPart(int codePoint)
//检查是否为镜像字符()、{}、<>、[]
public static boolean isMirrored(int codePoint)

字符转换

Unicode除了规定字符属性外,对有大小写对应的字符,规定了对应的大小写,对有数值含义的字符,规定了数值。

  • 大小写:Character有两个静态方法,对字符进行大小写转换:a ~ z、A ~ Z
public static int toLowerCase(int codePoint)
public static int toUpperCase(int codePoint)
  • 返回字符表示的数值:0 ~ 9返回0 ~ 9、a ~ z返回数值结果10 ~ 35
public static int getNumbericValue(int codePoint)
  • 返回按给定进制表示的数值,数值无效返回-1
public static int digit(int codePoint, int radix)
  • 返回给定数值的字符形式,数值无效返回’\0’
public static char forDigit(int codePoint, int radix)
  • 按字节反转
public static char reverseBytes(char ch)

Character在Unicode字符级别(而非char)级别封装了字符的各种操作,通过将字符处理的细节交给Character类,其他类可以再更高层次上处理文本。

剖析String

不可变类String

基本用法

字符串的创建和使用运算符的拼接:

String name = "Hello world!";
String name = new String("Hello world!")
name = "Zhang" + name;

常用方法:

public int length()//返回String字符串的长度
public boolean isEmpty()//字符串是否为空,方法内部判断长度是否为0

public String substring(int beginIndex)//返回一个字符串的子串。默认beginindex开始
public String substring(int beginIndex, int endIndex)//返回一个字符串。beginindex开始(包括beginIndex)endIndex结束(不包括endIndex)

public int indexOf(int ch)//返回指定字符在字符串中第一次出现的位置。
public int indexOf(String str)//返回指定字符在字符串中第一次出现的位置。 
public int lastIndexOf(intch)//从后面开始查找ch,返回在str中的正序坐标
public int lastIndexOf(String str)//从后面开始查找ch,返回在str中的正序坐标

public boolean contains(CharSequence s)//字符串包含给定字符串,则返回true

public boolean startsWith(Sring prefix)//判断字符串是否以给定字符串开头
public boolean endsWith(Sring prefix)//判断字符串是否以给定字符串结尾

public boolean equals(Object anObject)//与指定的对象比较,判断是否相同
public boolean equalsIgnoreCase(String anotherString)//忽略大小写比较,判断是否相同

public int compareTo(String anotherString)//比较字符串的大小
public int compareToIgnoreCase(String str)//忽略大小写,比较字符串的大小

public int toUpperCase(String str)//所有字符转换为大写字符,返回新字符串,原字符串不变
public int toLowerCase(String str)//所有字符转换为小写字符,返回新字符串,原字符串不变

public String concat(String sr)//将指定的字符串,连接到本字符串末尾。
public String replace(char oldChar, char newChar)//返回从字符串中替换oldChar为newChar的新字符串
public String replace(CharSequence target, CharSequence replacement)//返回从字符串中替换oldChar为newChar的新字符串
public String replaceAll(String regex, String replacement)//将str中regex字符串替换为replacement
public String replaceFirst(String regex, String replacement)//替换第一个regex为replacement

public String[] split(String regex)//将regex分割字符串为字符串数组,不存储regex
public String trim() //返回一个字符串,删除前后空格


public char charAt(int index)//返回index位置的字符,index超过则抛出异常
public codePointAt(int index)//返回字符(Unicode代码点)在指定的索引
public byte[] getBytes(Charset charset)//将String使用给定的编码编码为字节数组。
public int hashCode()//返回此字符串的哈希代码
public CharSequence subSequence(int beginIndex, int endIndex)//返回字符串子序列
public char[] toChaArray() //返回字符串对应的char数组,返回的是一个复制后的数组,而不是原数组。
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin)//将char数组中指定范围的字符复制入目标数组指定位置

String内部

String类内部用一个字符数组表示字符串,实例变量定义为:

private final char value[];

注:Java 9 对String的实现进行了优化,它的内部不是char数组,而是byte数组,如果字符都是ASCII字符,他就可以使用一个字节表示一个字符,而不用UTF-16BE编码,节省内存

String存在如下两个构造方法,可以根据char数组创建String变量:

public String(char value[])
public String(char value[], int ooffset, count)

String会根据参数新创建一个数组,并复制内从,不会直接用参数中的字符数组。String大部分方法内部也是操作这个字符数组。

  • length()方法返回的是这个数组的长度。
  • substring()方法时根据参数,调用构造方法String(char value[],int offset, int count)新建了一个字符串。
  • indexOf()方法查找字符串或子字符串时是在这个数组中进行查找。

编码转换

String内部按UTF-16BE处理字符,对BMP字符,使用一个char,两个字节。对于增补字符,使用两个char,四个字节。
Java使用Charset类表示各种编码,它们两个常用静态方法:

public static Charset defaultCharset()//返回系统的默认编码。
public static Charset forName(String charsetName)//返回给定编码名称的Charset对象

String类提供了如下方法,返回字符串按给定编码的字节表示:

public byte[] getBytes()//使用系统默认编码
public byte[] getBytes(String charsetName)//参数为编码名称
public byte[] getBytes(Charset charset)//参数为Charset

String存在如下构造方法,可以根据字节和编码创建字符串,即给定编码的字节表示,创建java的内部表示。

public String(byte bytes[], int offset, int length, String charsetName)
public String(byte bytes[], Charset charset)

不可变性

与包装类类似,String也是不可变类。String类声明了final,不能被继承,内部char数组value也是final的,初始化后就不能再改变了。
String类中提供了很多看似修改的方法,其实是通过创建行的Sring对象来实现的,原来的String对象不会被修改。
如果需要对字符串进行频繁修改,应当考虑StringBuilder和StringBuffer。

常量字符串

“这些”、“就是”、“所说的”、字符串常量,向一个String类型的对象,可以直接调用方法。

  • 实际上这些常量就是String类型的对象,在内存中,它们被放在一个共享的地方,这个地方称为字符串常量池,它保存所有的常量字符串,没个常量只会保存一份,被所有使用者共享。
  • 当通过常量的形式使用一个字符串时,使用的就是常量池中的那个对应的String类型的对象。
//str1==str2返回false,由于指向的地址不同。
String str1 = "Hello world!";
String str2 = new String("Hello world!")
String name1 = new String(str1);
String name2 = new String(str2)

String中以String为参数的构造方法如下:

  • hash是String类中的另一个实例变量,表示缓存的hashCode值。
public String(String original){
	this.value = original.value;
	this.hash = original.hash;
}


关于对象位置及引用存疑

Android 获取农历数据库_Android 获取农历数据库

hashCode

hash实例变量定义如下:

private in hash;//Default to 0
  • hash变量中缓存了hashCode方法的值,第一次调用hashCode方法时会把结果保存在hash变量中,在调用就直接返回保存的值。

String类的hashCode方法:

public int hashCode(){
	int h = hash;
	if(h == 0 && value.length > 0){
		char val[] = value;
		for(int i = 0; i < value.length; i++){
			h = 31 * h + val[i];
		}
		hash = h;
	}
	return h;
}

如果缓存的hash不为0,就直接返回,否则根据字符数组中的内容计算hash,计算方法是:

  • s[0] × 31n-1+ s[1] × 31n-2 + … + s[n-1]
  • s表示字符串,s[0]表示第一个字符,n表示字符串长度,s[0] × 31n-1表示31的(n-1)次方再乘第一个字符的值。
  • 使用这个式子可以让hash值与每个字符的值有关,也与每个字符的位置有关。
  • 一方面可以产生更分散的散列,即不同字符串hash值一般也不同
  • 一方面计算效率表较高,31×h与31×h-h即(h<<5)-h等价,可以更高效率的移位和减法操作代替乘法操作。

正则表达式

Java中有专门的类(Pattern和Macher)用于正则表达式。
String类提供了一些简单地操作:

public Stringp[]split(String regex)  //分隔字符串
public boolean matches(String regex)  //检查是否匹配
public String replaceFirst(String regex, String replacement)  //字符串替换
public String replaceAll(String regex, String replacement)  //字符串替换

剖析StringBuilder

线程安全。

基本用法

创建对象:

StringBuilder sb = new StringBuilder();

添加字符串:

sb.append("Hello");
sb.append(" ");
sb.append("world!");

通过toString()获取构建完成的字符串

sb.toString();

基本实现原理

StringBuilder类也封装了一个字符数组(不是final的),定义如下:

char[] calue;

字符数组中不一定所有位置都被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:

int count;

StringBuilder继承自AbstractStringBuilder,默认构造方法是:

//StringBuilder构造方法
public StringBuilder(){
	supper(16);
}
//AbstractStringBuilder构造方法
public AbstractStringBuilder(int capacity){
	value = new char[capacity]
}
  • 即,new StringBuilder()代码内部会创建一个长度为16的字符数组,count的默认值为0。

append方法的代码:

public AbstractStringBuilder append(String str){
	if(str == null) str == "null";
	int len = str.length();
	ensureCapacityInternal(count + len)
	str.getChars(0, len, value, count);
	count += len;
	return this;
}
  • append会直接复制字符到内部的字符数组中,如果字符数组长度不够,会进行拓展,实际使用的长度用count体现。
  • ensureCapacityInternal(count + len) 确保数组的长度足以容纳新添加的字符,(如果小,内部使用expandCapacity进行拓展)
  • 拓展的逻辑:分配一个足够长度的新数组,然后将原内容复制到这个新数组中,最后让内部的字符数组指向这个新数组,通过value = Arrays.copyOf(value, newCapacity)实现。
  • newCapacity怎么计算得到的:minimumCapacity表示最小需要的长度,这里的扩展策略时跟当前长度相关的,当前长度乘以2再加上而,如果这个长度不够最小需要的长度,采用minimumCapacity。【指数扩展策略,+2为了在原长度为0也可以工作】
  • str.getChars会复制新添加的字符到字符数组中,
  • count += len 会增加实际使用的长度。

如果预先知道需要长度,可以用另外一个构造方法:

public StringBuilder(int capacity){
	
}

public String toString(){
	//Create a copy, don't share the array
	return new String(value, 0, count);
}
  • 基于内部数组新建了一个String。注意,这个String构造方法不会直接用value数组,而会新建一个,以保证String的不可变性。

插入方法:在指定的字符offset处(该字符前)插入字符串str

public StringBuilder insert(int offset, String str)
  • 原来的字符后移,offset为0表示在开头插,为length()表示在结尾插

insert实现方法:

  • 在确保有足够长度后,首先将原数组中offset开始的内容向后挪动n个位置,n为待插入字符串的长度,然后将待插入字符串复制进offset位置。
  • 挪动位置调用了System.arraycopy()方法,这个是个比较常用的方法,它的声明如下:
  • public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
  • 将数组src中srcPos开始的length个元素复制到数组dest中destPos处,
  • 这个方法的优点:即使src和dest是同一个数组,它也可以正确处理。

String的+和+=运算符

String可以使用+和+=运算符,这是Java编译器提供的支持。背后Java编译器一般会生成StringBuilder,+和+=操作会转换为append。如:

String hellp = "hello";
hello += ",world!!"

StringBuilder hello = new StringBUilder("hello");
hello.append(".world!");
hello.toString();

对于简单情况,可以使用String的+和+=,对于复杂情况,使用StringBuilder【如存在循环,编译器将会创建多个StringBuilder对象】

剖析Arrays

数组是存储多个同类子那个元素的基本数据,数组中的元素在内存中连续存放,可以通过数组下标直接定位任何元素。【相比容器而言效率高】

用法:

toString、排序、查找、复制、比较、批量设置值、哈希计算等等。

toString

Arrays.toString()方法可以方便地输出也给数组的字符串形式。有9个重载方法,包括8个基本类型数组和1个对象类型数组。

public static String toString(int[] a)
public static String toString(Object[] a)

排序

同toString方法,对每种基本类型的数组,Arrays都有sort方法(boolean除外)

public static void sort(int[] a)
public static void sort(double[] a)

除了基本类型,sort还可以直接接受对象类型,但对象需要实现Comparable接口

public static void sort(Object[] a)
public static void sort(Object[] a, int fromIndex, int toIndex)

对String排序将会比较大小写字母的ASCII码。如果想忽略大小写,sort还有另外两个重载方法,可以接受一个比较器作为参数:

public static  <T> void sort(T[] a, comparator<? super T> c)
public static  <T> void sort(T[] a,int fromIndex, int toIndex, comparator<? super T> c)

String类有一个public静态成员,表示忽略大小写的比较器:

public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

返回逆序的方式:

  • 用匿名内部类来实现Comparator
  • 使用Collections类中的静态方法,可以返回逆序的Comparator
  • public static <T> Comparator<T> reverseOrder()
  • public static <T> Comparator<T> reverseOrder(Comparator<T> cmp)
  • 传递比较器Comparator给sort方法,体现了程序设计中的重要思维。将不变化和变化相分离,排序的基本步骤和算法是不变的,但按什么排序是变化的,sort方法将不变的算法设计为主体逻辑,而将变化的排序方式设计为参数,允许调用者动态指定,这也是一种常见的设计模式,称为策略模式,不同的排序方式就是不同的策略。

查找

Arrays包含很多与sort对应的查找方法,可以再已排序的数组中进行二分查找。

  • 从中间开始查找,如果小于中间元素,在前半部分查找,否则在后半部分查找。

二分查找,既可以针对基本类型数组,也可以针对对象数组,对对象数组,可以传递Comparator,也可以指定查找范围。

public static int binarySearch(int[] a, int key)
public static int binarySearch(int[] a, int fromIndex, int toIndex, int key)

针对对象数组:

public static int binarySearch(Object[] a, Object key)

指定自定义比较器:

public static <T> int binarySearch(T[] a, T key, Comparator<? super T> c)

如果没找到,会返回一个负数,这个负数等于-(插入点+1)

  • 插入点表示:如果在这个位置插入没找到元素,可以保持原数组有序。
  • int[] arr = {3, 5, 7, 13, 21};
  • 如果查找11,则会返回-4。插入点为3.

【注意:binarySearch针对的必须是已排序数组,如果制定了Comparator,需要和排序时指定的Comparator保持一致。如果数组中有多个匹配的元素,则返回哪一个是不确定的】

更多方法:

基于原数组,复制一个新数组,与toString一样,也有多重重载形式。

public static long[] copyOf(long[] original, int newLength)
public static <T>T[] copyOf(T[] original, int newLength)

判断两个数组是否相同,支持基本类型和对象类型:

public static boolean equals(boolean[] a, boolean[] a2)
public static boolean equals(Object[] a, Object[] a2)
  • 只有数组长度相同,且每个元素都相同,才返回true,否则返回false。
  • 对于对象,相同是指equals返回true。

Arrays包含多个fill方法,可以给数组中的每个元素设置一个相同的值:

public static void fill(int[] a, int val)

可以给数组中一个给定范围的元素设置一个相同的值:

public static void fill(int[] a, int fromIndex, int toIndex, int val)

针对数组,计算一个数组的哈希值:计算hashCode算法和String是类似的。

public static int hashCode(int a[])

多维数组

创建数组中,第一维的长度需要制定,其他维的长度不需要指定,甚至第一维和第二维的长度可以不同。
多维数组只是一个假象,只有一维数组,只是数组中的每个元素还可以是一个数组,这样就形成二维数组。
Arrays中的toString、equals、hashCode都有对应的针对多维数组的方法:

public static String deepToString(Object[] a)
public static String deepEquals(Object[] a1, Object[] a2)
public static int deepHashCode(Object a[])

这些deepXxx方法,都会判断参数中的元素是否也为数组,如果是,会自动递归进行操作。

实现原理

二分查找:

  • low、high。

排序

  • 基于基本类型的数组,Java采用的算法是双枢轴快速排序(Java7引入,在此之前是普通快速排序)
  • 对于对象类型,Java采用的算法是TimSort(Java 7 引入,在此之前是归并排序,是对归并排序的优化)
  • 如果数组长度比较小,会采用效率更高的插入排序。
  • 快速排序快但不稳定,归并排序稳定。
  • 对于基本类型,值相同就是完全相同,所以稳不稳定没关系
  • 对于对象类型,相同只是比较结果一样,他们还是不同的对象,其他实例对象也不一定一样,因此稳不稳定很重要,所以采用归并排序。

小结

Arrays中包含的数组方法比较少,很多常用操作没有。
Apache有一个开源包,里面有一个类ArrayUtils,包含了更多的常用数组操作。

剖析日期和时间

Java 8 之前的API介绍。

基本概念

时区、时刻、纪元时、年历

时区

全球一共有24个时区

  • 英国格林尼治是0时区 GMT+0
  • 北京是东八区,GMT+8:00
  • 格里尼治凌晨1点,北京早上9点。
  • 0时区的时间也称为GMT+0时间

时刻和纪元时

计算机系统内部都用一个整数表示时刻,这个整数是距离格林尼治标准时间1970年1月1日0时0分0秒的毫秒数。

  • 即格林尼治标准时间1970年1月1日0时0分0秒的毫秒数也被称为Epoch Time(纪元时)
  • 这个整数表示的是一个时刻,与时区无关,世界上各个地方都是同一个时刻,但各个地区对这个时刻的解读(年月日时分秒)不同
  • 对于1970年以前的时间,用负数表示。

年历

公历是世界上广泛采用的年历。
Java API的设计思想是支持国际化的,支持多种年历,但没有直接支持中国的农历。

日期和时间API

Java API中关于日期和时间,有三个主要的类:

  • Date:表示时刻,即绝对时间,与年月日无关。
  • Calender:表示年历,Calender是一个抽象类,其中表示公历的子类是GregorianCalender
  • DataFormat:表示格式化,能够将日期和时间与字符串进行相互转换,DataFormat也是一个抽象类,其中最常用的子类是SimpleDateFormat。

还有两个相关的类:

  • TimeZone: 表示时区
  • Locale:表示国家(或地区)和语言

Date

Date是JavaAPI中最早引用的关于日期的类,Date表示时刻,内部主要是一个long类型的值:

private transient long fastTime;

fastTime表示距离纪元时的毫秒数。
Date有两个构造方法:

public Date(long date){
	fastTime = date;
}
public Date(){
	this(System.currentTimeMillis())
}
  • 第一个构造方法是根据传入的毫秒数进行初始化;
  • 第二个构造方法时默认构造方法,根据System.currentTimeMillis()的返回值进行初始化。
  • System.currentTimeMillis()返回当前时刻距离纪元时的毫秒数

Date中大部分方法都已经过时了,其中没有过时的主要方法有下面这些:

public long getTime()  //返回毫秒数
public  boolean equals(Object obj)  //主要就是比较内部的毫秒数是否相同
public int compareTo(Date anotherDate)  //与其他Date进行比较,如果当前Date的毫秒数小于参数中的返回-1,相等返回0,否则返回1。
public boolean before(Date when)  //判断是否在给定日期之前
public boolean after(Date when)  //判断是否在给定日期之后
public inthashCode()  //哈希值算法与Long类似。

TimeZone

TimeZone表示时区,它是一个抽象类,有静态方法用于获取其实例。获取当前的默认时区,代码为:

TimeZone tz = TimeZone.getDefault();
tz.getID()

Java中有一个系统属性user.timezone,保存的就是默认时区。系统属性可以通过System.getProperty获得

System.getProperty("user.timezone");

属性可以再Java启动的时候传入参数进行修改,如:

  • java -Duser.timezone=Asia/Shanghai xxx

TimeZone 也有静态方法,可以获得任意给定时区的实例。比如,获取美国东部时区:

  • TimeZone tz = TimeZone.getTimeZone("US/Eastern");

ID除了可以是名称外,还可以是GMT形式表示的时区,如:

  • TimeZone tz = TimeZone.getTimeZone("GMT+08:00");

Locale

Locale表示国家和语言,它有两个主要参数:

  • 一个是国家(或地区)
  • 一个是语言
    每个参数都有一个代码,不过国家(或地区)并不是必须的。
    Locale类中定义了一些静态变量,表示长江的Locale,比如:
  • Locale.US:表示美国英语
  • Locale.ENGLISH:表示所有英语
  • Locale.TAIWAN:表示中国台湾地区所用的中文。
  • Locale.CHINESE:表示所有中文。
  • Locale.SIMPLIFIED_CHINESE:表示中国内地所用中文。

与TImeZone类似,Locale也有静态方法获取默认值,如:

Locale locale = Locale.getDefault();
System.out.ptintln(locale.toString());

Calendar

Calender类是日期和时间操作中的主要类,它表示与TimeZone和Locale相关的日历信息,可以进行各种相关运算。
内部组成:有一个表示时刻的毫秒数,定义为:

protected long time;

Calendar内部还有一个数组,表示日历中各个字段的值,定义为:

protected int fields[];
  • 这个数组的长度为17,保存一个日期中各个字段的值。
    都有哪些字段?Calender类中定义了一些静态变量,表示这些字段,主要有:
Calendar.YEAR //年
Calendar.MONTH //月,1月是0月,同样定义了表示各个月份的静态变量,如Calendar.JULY表示7月
Calendar.DAY_OF_MONTH  //表示日,没月的第一天是1
Calendar.HOUR_OF_DAY  //表示时,为0~23
Calendar.MINUTE  //表示分钟,为0~59
Calendar.SECONS  //表示秒,为0~59
Calendar.MILLISECOND  //表示毫秒,为0~999
Calendar.DAY_OF_WEEK  //表示星期几,周日是1,周一是2,周六是7,Calenar同样定义了表示各个星期的静态变量,如Calendar.SUNDAY表示周日

Calendar是抽象类,不能直接创建对象,它提供了多个静态方法,可以获取Calendar实例,比如:

public static Calendar getInstance()
public static Calendar getInstance(TimeZone zone, Locale aLocale)

最终调用的方法都是需要TimeZone和Locale的,如果没有则会使用上面介绍的默认值。
getInstance方法会根据TimeZone和Locale创建对应的Calendr子类,在中文系统中,子类一般是表示公历的GregorianCalendar。、

getInstance方法封装了Calendar对象创建的细节。

  • TimeZone和Locale不同,具体的子类可能不同,但都是Calendar。
  • 这种隐藏对象创建细节的方式,是“工厂方法”,getInstance就是一个工厂方法,它生产对象。

使用:

Calendar calender = Calendar.getInstance();
calender.get(Calender.YEAR);
calender.get(Calender.MONTH);
calender.get(Calender.DAY_OF_MONTH);
calender.get(Calender.HOUR_OF_DAY);
calender.get(Calender.MINUTE);
calender.get(Calender.SECOND);
calender.get(Calender.MILLISECOND);
calender.get(Calender.DAY_OF_WEEK);

在内部,Calender会将表示时刻的毫秒数,按照TimeZone和Locale对应的年历,计算各个日历字段的值,存放在fields数组中,Calender.get方法获取的就是fields数组中对应字段的值。

Calender支持根据陈Date或毫秒数设置时间:

public final void setTime(Date date)
public void setTimeMillis(long millis)

根据年月日等日历字段设置时间,比如:

public final void set(int year, int month, int date)
public final void set(int year, int month, int date, int hourOfDay int minute, int second)
public void set(int field, int value)

除了直接设置,Calendar支持根据字段添加和减少时间:

public void add(int field, int amount)
  • amount 为正数表示增加,负数表示减少。

更改时间,但不影响时间范围内更大的字段值:

calender.roll(Calender.MINUTE,3)
  • 如 14:59执行后是14:02

Calendar方便的转换为Date或毫秒数,方法是:

public final Date getTime()
public long getTimeInMillis()

与Date类似,Calendar之间也可以进行比较,也实现了Comparable接口,相关方法有:

public boolean equals(Object obj)
public int compareTo(Calendar anotherCalendar)
public boolean after(Object when)
public boolean before(Object when)

DateFormat

DateFormat类主要在Date和字符串表示之间进行相互转换,它有两个主要的方法:

public final String format(Date date)
public Date parse(String soutce)
  • format将Date转换为字符串,parse将字符串转换为Date。

格式化风格:日期的格式化风格,时间的格式化风格

  • DateFormat定义了4个静态变量,表示4种风格:SHORT、MEDIUM、LONG和FULL
  • 还定义了一个静态变量:DEFAULT,表示默认风格,值为MEDIUM

与Calendar类似,DateFormat也是抽象类,也用工厂方法创建对象,提供了多个静态方法创建DateFormat对象,三类方法:

public final static DateFormat getDateTimeInstance();
public final static DateFormat getDateInstane();
public final static DateFormat getTimeInstance();
  • getDateTimeInstance()方法即处理日期也处理时间,
  • getDateInstance方法值处理日期,
  • getTimeInstance方法只处理时间。

每类工厂方法都有两个重载的方法,接受日期和时间风格以及Locale作为参数:

DateFormat getDateTimeInstance(int dateStyle, int timeStyle)
DateFormat getDateTimeInstance(int dateStyle, int timeStyle, Locale aLocale)

DateFormat的工厂方法里,没有看到TimeZone参数,不过,DateFormat提供了一个setter方法,可以设置TimeZone:

public void setTimeZone(TimeZone zone)

DateFormat虽然比较方便,但如果我们要对字符串格式有更精确的控制,则应该使用SimpleDateFormat这个类。

SimpleDateFormat

SimpleDateFormat是DateFormat的子类,相比DateFormat,它的一个主要不同是,它可以接受一个自定义的模式(pattern)作为参数,这个模式规定了Date的字符串形式。
如:

Calendar calendar = Calendar.getInstance();
calendar.set(2016, 07, 15, 14, 15, 20);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 E HH时mm分ss秒");
System.out.println(sdf.format(calendar.getTime()));

SimpleDateFormat有个构造方法,可以接受一个pattern作为参数,这里pattern是:

  • yyyy年MM月dd日 E HH时mm分ss秒
  • pattern中的英文字符a ~ z 和A ~ Z表示特殊含义,其他字符原样输出。
  • yyyy:表示4位的年
  • MM:表示月,用两位数表示
  • dd:表示日,用两位数表示
  • HH:表示24小时制的小时数,用两位数表示。
  • mm:表示分钟,用两位数表示
  • ss:表示秒,用两位数表示。
  • E 表示星期几
  • 注:hh也表示小时数,但表示的是12小时制的小时数,而a表示上午还是下午。

局限性

  1. Date方法过时。
  2. Calendar操作繁琐
  3. DateFormat线程安全性

解决方案:

  • 每次使用DateFormat都新建一个对象
  • 使用线程同步
  • 使用ThreadLocal
  • 使用Joda-Time或Java 8的API因为是线程安全的。

随机

Math.random

Java中,对随机最基本的支持是Math类中的静态方法random(),它生成一个0~1的随机数,类型为double,包括0但不包括1.
内部实现:

  • 内部它使用一个Random类型的静态变量randomNumberGenerator,调用random()就是调用nextDouble()方法,这个Random变量只有在第一次使用的时候才创建。位于包java.util

Random

Random类提供了更为丰富的随机方法,它的方法不是静态类,使用Random,先要创建一个Random实例。
nextInt()产生一个随机的int,可能为正数,也可能为负数,newxtInt(100)产生一个随机int,范围是0~100,包括0但不包括100.
还有一些其他方法:

public long nextLong()  //随机生成一个long
public boolean nextBoolean()  //随机生成一个boolean
public void nextBytes(byte[] bytes)  //产生随机字节,字节数就是bytes的长度
public float nextFloat()  //随机浮点数,从0到1,包括0不包括1
public double nextDouble()  //随机浮点数,从0到1,包括0不包括1

除了默认构造方法,Random类还有一个构造方法,可以接受一个long类型的种子参数:

public Random(long seed)
  • 种子决定了随机产生的序列,种子相同,产生随机数的序列就是相同的。

除了在构造方法中指定种子,Random类还有一个setter实例方法:

synchronized public void setSeed(long seed)

效果与在构造方法中指定种子是一样的。
指定种子是为了实现可重复的随机。

随机的基本原理

Random产生的随机数不是真正的随机数,相反,它产生的随机数一般被从称为伪随机数。
种子

  • Random的默认构造方法中没有传递种子,它会自动生成一个种子,这个种子是一个真正的随机数。
  • 种子是seedUniquifier()与System.nanoTime()按位异或的结果,System.nanoTime()返回一个更高精度(纳秒)的当前时间,seedUniquifier()里面的代码涉及多线程的只是。
  • 简单来说,就是返回当前seedUniquifier(current变量)与一个常数181783497276652981L想乘的结果(newxt变量)然后,设置seedUniquifier的值为next,使用循环和compareAndSet都为了确保在多线程的环境下不会有两次调用返回相同的值,保证随机性。

有了种子怎样生成随机数:
nextseed = (oldseed * multiplier + addend) & mask;

  • 线性同余随机数生成器

需要知道的基本原理:

随机数基于一个种子,种子固定,随机数序列就固定,默认构造方法中,种子是一个真正的随机数。

小结

Random类是线程安全的,如果并发性高,会产生竞争,这时可以考虑使用多线程库中的ThreadLocalRandom类。
Java类库中还有一个随机类SecureRandom,可以产生安全性更高、随机性更强的随机数,用于安全加密等领域。