Java对流类资源关闭的自动执行

对于流类,有3个相当重要的接口:CloseableFlushableAutoColseable,其中AutoColseable是JDK7新添加的接口,这个接口让我们对流类的异常处理的有了新的写法。

本来对于流类,由于代码出现异常时,异常后面的代码不会被执行,通常会导致后面对资源的关闭操作无法被执行,从而会出现不可预料的错误,因此我们必须把资源关闭的操作写到finally{}里以保证代码会被执行:

1
2
3
4
5
6
7
8
try {
// 1. resource registered
// 2. do something
} catch (IOException e) {
// exception handler
} finally {
// 3. resource close
}

但是Java7为我们优化了对这种问题的处理方式,我们只需把流类资源的申请放到try后面的小括号里即可,不必进行显式的资源关闭,try语句可以自动执行资源关闭,即便在try代码块中发生了异常:

1
2
3
4
5
6
7
try(
// 1. resource registered [auto close]
){
// 2. do something
}catch(IOException e){
// exception handler
}

当然,能使用这种方式的必须是实现了java.lang.AutoCloseable的类。

JDK源码分析-ArrayList

基本概述

  • ArrayList的本质是数组,占用一块连续的内存空间,可以动态扩容;
  • 没有实现同步,并非线程安全的;
  • 实现了接口RandomAccess,支持通过下标进行快速随机访问;
  • 实现了接口Cloneable,可以被克隆;
  • 实现了接口Serializable,支持序列化;

构造方法

三种构造方法:

1
2
3
public ArrayList(); // 无参构造
public ArrayList(int initialCapacity); // initialCapacity为初始化数组的长度大小
public ArrayList(Collection<? extends E> c); // 用Collection对象的元素来构建

特别提一下的是在JDK1.6中,无参构造的数组默认长度是10,是调用带数组长度的构造方法来初始化的:

1
2
3
4
// JDK1.6
public ArrayList() {
this(10);
}

而在JDK1.7、1.8中,无参构造则是直接赋予一个空数组(即默认长度为0),在调用add方法的时候才做扩容:

1
2
3
4
5
// JDK1.8
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

动态扩容

当数组的实际长度将要比当前预留的要大时,需要对数组进行扩容。

上面提到新版本JDK的无参构造是直接赋予一个空数组,而调用add方法时,会判断变更后长度是否比默认长度小,是的话则设置数组长度为DEFAULT_CAPACITY,即默认最小长度10:

1
2
3
4
5
6
7
8
private static final int DEFAULT_CAPACITY = 10; // 默认最小长度为10
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// 如果小于默认长度10,则取默认长度
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}

而在实际添加元素时空间不够会调用grow()方法来调整数组长度,先约定扩大长度为当前的1.5倍,依然不够的话则设置为实际需要的长度:

1
2
3
4
5
6
7
8
9
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容为原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; // 如果依然不够,则直接设置为需要的大小
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}

由上面可以得知,每次扩容数组都会执行一次Arrays.copyOf(elementData, newCapacity);,但ArrayList的扩容算法使这些频繁的复制操作基本都集中在前面数据量较小处,到了后期数据量大的时候复制操作相对比较少了,所以设置一个合理的长度虽然可以减少数据复制的次数,但也只能“稍微”提高一点性能,反而设置了过大的长度还可能会浪费内存。如何取舍看具体场景。

另外,ArrayList有一个设置数组长度为当前实际长度的trimToSize()方法:

1
2
3
4
5
6
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);
}
}

线程安全

ArrayList没有实现同步,所以不是线程安全的。代码文档中提到,ArrayList类大致等同于Vector类,而Vector是线程安全的,所以是可以使用Vector来做多线程的实现。另外也提到可以使用Collections.synchronizedList方法将ArrayList“包装”起来:

1
List list = Collections.synchronizedList(new ArrayList(...));

当然,因为Collections.synchronizedList实质上底层存储的还是构造时传进来的参数list,只是对它做了一层包装,所以对数据操作起来会比使用Vector慢一点点。Oracle文档里的原话:

If you need synchronization, a Vector will be slightly faster than an ArrayList synchronized with Collections.synchronizedList.

JDK源码分析-BigDecimal

java.math.BigDecimal是由任意精度的整数非标度值和32位的整数标度(scale)组成的不可变的、任意精度的有符号十进制数,表示的数值是 unscaledValue × 10^(-scale) 。在计算的时候BigDecimal提供了精确的数值计算,所以也理所当然地经常会需要进行数值的舍入。

赋值

  • 传入字符串的赋值方式:
1
new BigDecimal("1.22");
  • 调用valueOf(double)方法的赋值方式:
1
BigDecimal.valueOf(1.22);
  • JDK源码注释里描述对参数类型为double的构造方法要求参数是double 的二进制浮点值准确的十进制表示形式,因此直接传入double值结果有一定的不可预知性,大部分情况下这种构造方式的结果不会正好等于传入的double值(因为不能表示为任何有限长度的二进制小数):
1
2
new BigDecimal(1.22);
// = 1.2199999999999999733546474089962430298328399658203125

舍入模式 (RoundingMode)

  • ROUND_UP

    远离零的舍入模式,即绝对值始终增大。

  • ROUND_DOWN

    接近零的舍入模式,即绝对值始终减小。

  • ROUND_CEILING

    接近正无穷大的舍入模式,即原始值始终增大。

  • ROUND_FLOOR

    接近负无穷大的舍入模式,即原始值始终减小。

  • ROUND_HALF_UP

    向最接近的数字舍入,如果距两边数字差值相等则向上舍入,即绝对值四舍五入。

  • ROUND_HALF_DOWN

    向最接近的数字舍入,如果距两边数字差值相等则向下舍入,即绝对值五舍六入。

  • ROUND_HALF_EVEN

    向最接近的数字舍入,如果距两边数字差值相等则向偶数边舍入,即绝对值四舍六入,五靠偶数。

  • ROUND_UNNECESSARY

    不做舍入,具有断言的作用,指定后如出现需要舍入的情况则会抛出ArithmeticException异常。

基本运算

1
2
3
4
public BigDecimal add(BigDecimal augend);                       //加法
public BigDecimal subtract(BigDecimal subtrahend); //减法
public BigDecimal multiply(BigDecimal multiplicand); //乘法
public BigDecimal divide(BigDecimal divisor, int roundingMode); //除法,一般都需要指定舍入模式

Java获取服务器根目录

获取服务器Tomcat目录下webapps(项目发布的位置)的根路径,对Linux和Windows系统下分别使用斜杠和反斜杠的问题进行了处理。

具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Gets the root path of server.
*
* @return the root path
*/
public static String getRootPath() {
String classPath = Thread.currentThread().getContextClassLoader()
.getResource("").getPath();
String rootPath = "";

/** For Windows */
if ("\\".equals(File.separator)) {
String path = classPath.substring(1, classPath.indexOf("/WEB-INF/classes"));
rootPath = path.substring(0, path.lastIndexOf("/"));
rootPath = rootPath.replace("/", "\\");
}

/** For Linux */
if ("/".equals(File.separator)) {
String path = classPath.substring(0, classPath.indexOf("/WEB-INF/classes"));
rootPath = path.substring(0, path.lastIndexOf("/"));
rootPath = rootPath.replace("\\", "/");
}
return rootPath;
}