本文发表于KuTear's Blog,转载请注明
最简单的例子说起
数组
粗糙数组
数组中构成矩阵的每个向量都可以具有任意的长度
int[][] array = new int[][]{
new int[]{1, 2, 3},
new int[]{1, 2}
};
System.out.println(Arrays.deepToString(array));//输出 [[1, 2, 3], [1, 2]]
System.out.println(array[1][2]);// java.lang.ArrayIndexOutOfBoundsException: 2
基础
- 新生成一个数组,其中所有引用被自动初始化为null,基本类型被初始化为0(boolean是false)
Arrays
方法 | 说明 |
---|---|
equals() | 比较两个数字是否相等 |
deepEquals() | 用于多维数组比较 |
fill() | 填充数组 |
sort() | 数组排序 |
binarySearch() | 在已经排序的数组中查找元素 |
toString | 产生数组的String表示 |
hashCode() | 产生数组的散列码 |
asList() | 接受任意的序列或数组作为其参数,并转换为List容器 |
数组与泛型
通常,数组与泛型不能很好结合,你不能实例化具有参数化类型的数组。
Peel<Banana>[] peels = new Peel<>[10]
不合法,擦除会移除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。
类型擦除
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2); //Output: true
参数化方法
private <T> Class get(T data){
return data.getClass();
}
参数化类
public class Container<K, V> {
private K key;
private V value;
public Container(K k, V v) {
key = k;
value = v;
}
}
泛型自动打包拆包
HashMap<String,Integer> map = new HashMap<>();
map.put("one",1); // int ---> Integer
int value = map.get("one"); // Integer ---> int
通过反编译查看实现
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/util/HashMap
3: dup
4: invokespecial #3 // Method java/util/HashMap."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String one
11: iconst_1
12: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;[打包]
15: invokevirtual #6 // Method java/util/HashMap.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/
lang/Object;
18: pop
19: aload_1
20: ldc #4 // String one
22: invokevirtual #7 // Method java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
25: checkcast #8 // class java/lang/Integer
28: invokevirtual #9 // Method java/lang/Integer.intValue:()I [拆包]
31: istore_2
32: return
}
字符串
String对象不可变
任意的包含改变String的方法其实都是重新生成对象
//String.java
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
"+"重载原理
String result1 = "AAA" + "BBB";
String result2 = "AAABBB";
上面的result1==result2
.
通过反编译可得
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String AAABBB
2: astore_1
3: ldc #2 // String AAABBB
5: astore_2
6: return
java编译器对它进行了优化.
public static void main(String[] args) {
String result1 = new String("AAA");
String result2 = result1+"BBB";
}
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String AAA
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: new #5 // class java/lang/StringBuilder
13: dup
14: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
17: aload_1
18: invokevirtual #7 // Method java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: ldc #8 // String BBB
23: invokevirtual #7 // Method java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_2
30: return
}
由上可以看出"+"的实现是利用StringBuilder
的append()
实现
for(int i = 0; i < fields.length; i++) {
result += fields[i];
}
根据上面的原理知道这里的+=
会生成临时StringBuilder
N多个,会造成不必要的消耗,这时应主动用StringBuilder
StringBuilder result = new StringBuilder();
for(int i = 0; i < fields.length; i++) {
result.append(fields[i]);
}
return result.toString();
StringBuilder & StringBuffer
StringBuffer是线程安全的,速度慢些;StringBuilder是线程不安全的,但是速度快些;
性能: StringBuilder > StringBuffer > String
字符格式化
System.out.println("%d %f", x, y);
System.out.printf("%d %f", x, y);
java.util.Formatter
String.format("%d %f", x, y);
正则表达式
符号 | 说明 | 符号 | 说明 | 符号 | 说明 |
---|---|---|---|---|---|
^ | 一行的起始 | \W | 非字母数字 | \w | 字母数字 |
$ | 一行的末尾 | \D | 非数字 | \d | 数字 |
+ | >=1 | ? | 0或1次 | * | 任意次 |
. | 非换行任意字符 | [^x] | 非X字符的任意字符 | \S | 任意非空白符字符 |
补充
Java中的String内存分析
栈(Stack) :存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)
堆(heap):存放所有new出来的对象和数组。
常量池(constant pool):在堆中分配出来的一块存储区域,存放储显式的String常量和基本类型常量(float、int等)。另外,可以存储不经常改变的东西(public static final)。常量池中的数据可以共享。
静态存储:存放静态成员(static定义的)。
Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid"
;,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid")
.
当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
当我们使用了new来构造字符串对象的时候,不管字符串常量池中有没有相同内容的对象的引用,新的字符串对象都会创建。
String str1 = "droid";
String str2 = "droid";
String str3 = new String("droid");
System.out.println(str1 == str2); //true
System.out.println(str1 == str3);//false
使用new
创建对象,会在堆中创建对象(真正的内存占用),同时会在栈中创建指向堆中该对象的首地址的一个引用(相当于指针,同样占一小部分内存),当程序运行到该变量的作用域之后,栈中的引用会被置为空,但是此时堆中的内存并没有被即可释放,会根据程序所占用的内存Java GC在合适的时候回收堆中的内存.
对于String
的比较问题,我们需要记住
String a = new String("A");
String b = new String("A");
对于new
出来的对象,==
操作是不相等的.
String a = "A";
String b = a + "B";
String c = "A" + "B";
通过反编译,发现b
这里其实利用StringBuilder
,最后用StringBuilder#toString()
方法生成String
,是经过new
出来的,而前面说过,new
出来的对象是栈中的引用指向堆中的地址,而字面量形式是栈中的引用指向常量区的地址.当然是不相等的.
而c
经编译器优化等同于String c = "AB"
.
//StringBuilder.java
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}