ACMLife-0_2-Java总结

ACMLife-0_2-Java总结

[toc]

序言

java作为一门为面向对象而生的语言,与cpp有着巨大的不同(比如万物皆“引用”)。利用java解决算法竞赛题目,在高精度、字符串处理、封装增强复用性等方面较cpp有优势。在ACM中若想用java替代cpp解决题目,理应对继承、多态、垃圾回收、自动打包解包机制,以及面对对象思想有基本的理解。

首先希望你对 java 基础有一定了解(比如对象判等要用.equals),本章更多的是深究 jdk API 中哪些对于 ACM 有优势。

IO

java中的输入输出流多带有一种”管道”的概念。

标准输入输出

标准输入输出的最简洁写法主要依靠两个类:Scanner、System。Scanner在java.util包,需要手动导入,System在java.lang包,会自动导入。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* 输入 */
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int a = sc.nextInt(2); // 将下一个整数二进制读入
long b = sc.nextLong();
long b = sc.nextLong(2); // 将下一个整数二进制读入
double d = sc.nextDouble();
String s = sc.next(); // 读入下一个字符串(自动跳过空白符)
String ss = sc.nextLine(); // 读入一行字符串
BigInteger = sc.nextBigInteger(); // 读入高精度整数
BigInteger = sc.nextBigInteger(2);// 读入二进制高精度整数
BigDecimal = sc.nextBigDecimal(); // 读入高精度小数

/* 多组读入 */
while(sc.hasNext()) {
int a = sc.nextInt();
}

/* 需要注意,倘若输入如下,两个整数+一行:
1 3
asdfggh dasd sd
*/
int a = sc.nextInt(), b = sc.nextInt();
String s = sc.nextLine();
// 上面的读法出错,此时读到的s是第一行末尾的换行符,故正确的用法如下:
int a = sc.nextInt(), b = sc.nextInt(); sc.nextLine(); // 应先消除第一行末的换行符
String s = sc.nextLine();

/* 输出 */
System.out.println("121212"); // 输出末带换行
System.out.print("213213"); // 输出末不带换行
System.out.printf("%d", 123); // 格式化输出

/* Scanner 本质上是一个 Iterartor,文档中的解释是"一个可以使用正则表达式来解析基本类型和字符串的简单文本扫描器" */
String input = "1 fish 2 fish red fish blue fish";
Scanner sc = new Scanner(input).useDelimiter("\\s*fish\\s*");
System.out.println(sc.nextInt()); // 输出1
System.out.println(sc.nextInt()); // 输出2
System.out.println(sc.next()); // 输出red
System.out.println(sc.next()); // 输出blue

/* 跳过下一个匹配某re模式的信息 */
sc.skip(StringOfRegex);

格式化输出

格式化输出,大多针对小数的保留几位小数的输出。利用printf像cpp一样格式化输出,二是利用DecimalFormat对象。

1
2
3
4
5
/* printf 格式化输出 */
System.out.printf("%d %10.5f\n", 1, 3.4);
/* 借由DecimalFormat */
DecimalFormat df = new DecimalFormat("0.000");
System.out.println("x = " + df.format(1.2345));

快速IO

快速IO,如同cpp一样,是预先将输入读到缓冲区,模拟读入。

  • 第一版快速IO
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
/* 代码易懂易记,适合正式比赛 */
public class Main
{
static BufferedReader in=new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer tok;
static String next() {hasNext();return tok.nextToken(); }
static String nextLine() {try{return in.readLine();}catch (Exception e) {return null;}}
static long nextLong() {return Long.parseLong(next());}
static int nextInt() {return Integer.parseInt(next());}
static PrintWriter out=new PrintWriter(new OutputStreamWriter(System.out));
static boolean hasNext()
{
while(tok==null||!tok.hasMoreTokens()) try{tok=new StringTokenizer(in.readLine());}catch(Exception e){return false;}
return true;
}

public static void main(String[] args)
{
int a = nextInt();
String s = nextLine();
out.println(s);

out.flush(); // 末要加上
}
}
  • 第二版快速IO
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
26
27
28
29
/* 代码经过压行,略微暴恐,适合平时做题用,效率略高于第一版 */
public class Main
{
public static void main(String[] args) throws IOException
{
/*
写代码
*/
out.flush();
out.close();
}
static FastReader in = new FastReader();
static PrintWriter out = new PrintWriter(System.out);
static class FastReader
{
private InputStream mIs;private byte[] buf = new byte[1024];private int curChar,numChars;public FastReader() { this(System.in); }public FastReader(InputStream is) { mIs = is;}
public int read() {if (numChars == -1) throw new InputMismatchException();if (curChar >= numChars) {curChar = 0;try { numChars = mIs.read(buf);} catch (IOException e) { throw new InputMismatchException();}if (numChars <= 0) return -1; }return buf[curChar++];}
public String nextLine(){int c = read();while (isSpaceChar(c)) c = read();StringBuilder res = new StringBuilder();do {res.appendCodePoint(c);c = read();}while (!isEndOfLine(c));return res.toString() ;}
public String next(){int c = read();while (isSpaceChar(c)) c = read();StringBuilder res = new StringBuilder();do {res.appendCodePoint(c);c = read();}while (!isSpaceChar(c));return res.toString();}
public long l(){int c = read();while (isSpaceChar(c)) c = read();int sgn = 1;if (c == '-') { sgn = -1 ; c = read() ; }long res = 0; do{ if (c < '0' || c > '9') throw new InputMismatchException();res *= 10 ; res += c - '0' ; c = read();}while(!isSpaceChar(c));return res * sgn;}
public int i(){int c = read() ;while (isSpaceChar(c)) c = read();int sgn = 1;if (c == '-') { sgn = -1 ; c = read() ; }int res = 0;do{if (c < '0' || c > '9') throw new InputMismatchException();res *= 10 ; res += c - '0' ; c = read() ;}while(!isSpaceChar(c));return res * sgn;}
public double d() throws IOException {return Double.parseDouble(next()) ;}
public boolean isSpaceChar(int c) { return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1; }
public boolean isEndOfLine(int c) { return c == '\n' || c == '\r' || c == -1; }
public void scanIntArr(int [] arr){ for(int li=0;li<arr.length;++li){ arr[li]=i();}}
public void scanLongArr(long [] arr){for (int i=0;i<arr.length;++i){arr[i]=l();}}
public void shuffle(int [] arr){ for(int i=arr.length;i>0;--i) { int r=(int)(Math.random()*i); int temp=arr[i-1]; arr[i-1]=arr[r]; arr[r]=temp; } }
}
}

重定向IO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* 标准输入输出 */
static Scanner sc;
static {
sc = new Scanner("in.txt");
try {
System.setOut(new PrintStream("out.txt"));
} catch (FileNotFoundException e) {
System.exit(0);
}
}

/* 第一版快速IO+重定向 */
static BufferedReader in;
static PrintWriter out;
static {
try {
in = new BufferedReader(new InputStreamReader(new FileInputStream("in.txt")));
out = new PrintWriter(new OutputStreamWriter(new FileOutputStream("out.txt")));
} catch (FileNotFoundException e) {
System.exit(0);
}
}

Integer

基本数据类型的包装类中,包含对进制的处理方法、字符串到整型的转换。这里仅拿Integer作范例,Long、Short、Boolean、Double理应有相应的用法。

进制相关

对于进制转换通过10进制为中介,理应可以处理任意进制间的转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 进制转换 */
Integer.toString(111, 16); // 111是十进制数,16是目标base进制,最大可支持的radix是"0123456789abcdefghijklmnopqrstuvwxyz"
Integer.valueOf("FFFF", 16) // 16->10
Integer.valueOf("776", 8) // 8->10
Integer.valueOf("010101", 2) // 2->10
Integer.toBinaryString(12); // 将12转成忽略前导零的二进制String,得到1100
Integer.toOctalString(12); // 将12转成忽略前导零的八进制String,得到14
Integer.toHexString(12); // 将12转成忽略前导零的十六进制String,得到c

/* 二进制位处理 */
Integer.bitCount(7); // 7的二进制补码中多少个"1"
Integer.highestOneBit(11); // 11的二进制最左位的幂次 如11=8+2+1, 则返回8
Integer.lowestOneBit(11); // 11的二进制最右位的幂次 如11=8+2+1, 则返回1
Integer.numberOfLeadingZeros(12); // 返回二进制补码中前导零的个数,如12=1100(2),返回32-4=28
Integer.numberOfTrailingZeros(12); // 返回二进制补码中末导零的个数,如12=1100(2),返回2
Integer.reverse(12); // 以bit为单位反转12的二进制得到805306368
Integer.reverseByte(12); // 以Byte为单位反转12的二进制得到201326592
Integer.rotateLeft(12, 2) // 将12的二进制循环左移2位,得到110000,注意是循环左移
Integer.rotateRight(12, 2) // 将12的二进制循环右移2位

类型转换

1
2
int a = Integer.parseInt("12345");         // 经常用来将读入String转int
long b = Long.parseLong("233333333333333");

Character

基本数据类型 char 的包装类,主要使用其对字符的判断方法、以及case转换。

1
2
3
4
5
6
7
8
Character.isDigit('1');
Character.isLetter('a');
Character.isLetterOrDigit('1');
Character.isLowerCase('a');
Character.isUpperCase('A');
Character.isWhitespace(' ');
Character.toUpperCase('a');
Character.toLowerCase('A');

Math

数学库

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
26
27
28
29
30
31
32
33
34
35
36
Math.abs(double a) 
Math.max(a, b) // 返回两个值中较大的一个
Math.min(a, b) // 返回两个值中较小的一个
Math.sin(double a)
Math.tan(double a)
Math.sqrt(double a) // 返回正确舍入的 double 值的正平方根
Math.sinh(double x) // 返回 double 值的双曲线正弦
Math.tanh(double x) // 返回 double 值的双曲线余弦
Math.toDegrees(double angrad) // 将用弧度表示的角转换为近似相等的用角度表示的角
Math.toRadians(double angdeg) // 将用角度表示的角转换为近似相等的用弧度表示的角
Math.acos(double a) // 返回一个值的反余弦;返回的角度范围在 0.0 到 pi 之间
Math.asin(double a) // 返回一个值的反正弦;返回的角度范围在 -pi/2 到 pi/2 之间
Math.atan(double a) // 返回一个值的反正切;返回的角度范围在 -pi/2 到 pi/2 之间
Math.atan2(double y, double x) // 将矩形坐标 (x, y) 转换成极坐标 (r, theta),返回所得角 theta
Math.cbrt(double a) // 返回 double 值的立方根
Math.ceil(double a) // 返回最小的(最接近负无穷大)double 值,该值大于等于参数,并等于某个整数
Math.copySign(double magnitude, double sign) // 返回带有第二个浮点参数符号的第一个浮点参数
Math.copySign(float magnitude, float sign) // 返回带有第二个浮点参数符号的第一个浮点参数
Math.cos(double a) // 返回角的三角余弦
Math.cosh(double x) // 返回 double 值的双曲线余弦
Math.exp(double a) // 返回欧拉数 e 的 double 次幂的值。
Math.expm1(double x) // 返回 ex -1
Math.floor(double a) // 返回最大的(最接近正无穷大)double 值,该值小于等于参数,并等于某个整数。
Math.getExponent(double d) // 返回 double 表示形式中使用的无偏指数。
Math.getExponent(float f) // 返回 float 表示形式中使用的无偏指数。
Math.hypot(double x, double y) // 返回 sqrt(x2 +y2),没有中间溢出或下溢
Math.log(double a) // 返回 double 值的自然对数(底数是 e)
Math.log10(double a) // 返回 double 值的底数为 10 的对数。
Math.log1p(double x) // 返回参数与 1 之和的自然对数。
Math.nextAfter(double start, double direction) // 返回第一个参数和第二个参数之间与第一个参数相邻的浮点数
Math.nextUp(double d) // 返回 d 和正无穷大之间与 d 相邻的浮点值
Math.pow(double a, double b) // 返回第一个参数的第二个参数次幂的值
Math.rint(double a) // 返回最接近参数并等于某一整数的 double 值
Math.round(double a) // 返回最接近参数的 long, 四舍五入
Math.scalb(double d, int scaleFactor) // 返回 d × 2scaleFactor,其舍入方式如同将一个正确舍入的浮点值乘以 double 值集合中的一个值
Math.signum(double d) // 返回参数的符号函数;如果参数为 0,则返回 0;如果参数大于 0,则返回 1.0;如果参数小于 0,则返回 -1.0

Random

伪随机数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* Math库中的简易版 */
Math.random() // 返回带正号的 double 值,该值大于等于 0.0 且小于 1.0。

/* 新建随机生成器48位 */
Random rand = new Random(); // 随机种子
Random rand = new Random(aValueOfLong); // 填入Long作为种子, 不要填入常数

/* 生成随机数 */
rand.nextInt(); // 在2^32个数中随机生成一个
rand.nextInt(3); // 在[0,3)中生成一个随机数
rand.nextInt(18)-3;// [-3,15)
rand.nextLong(); // 随机生成一个Long, 由于Random是48位故不会生成所有的Long
rand.nextBoolean();
rand.nextDouble(); // [0.0,1.0)
rand.nextDouble()*1.5+1; // [1,2.5)

/* 实现:随机抽出数组中的k个数 */
int[] result = new int[k];
for(int i = 0; i < result.length; i++) {
int idx = (int) (Math.random() * n);
result[i] = numbers[idx];
numbers[idx] = numbers[n-1];
n--;
}

BigInteger

高精度整数

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/* 声明 */
BigInteger bigI1 = new BigInteger("123124");
BigInteger bigI2 = BigInteger.valueOf(1234L);
long i1 = bigI1.longValue(); // 转long

/* 常量 */
BigInteger.ONE、BigInteger.ZERO、BigInteger.TEN

/* 加add、减substart、乘multiply、除divide、取模mod、取余rem */
bigI1 = bigI1.multiply(bigI2); // 时间复杂度有优化 n^1.585 n^1.465
System.out.println(bigI1);
bigI1.divide(bigI2);
bigI1.remainder(bigI2);
bigI1.divideAndRemainder(bigI2); // 返回包含(bigI1 / bigI2)、(bigI1 % bigI2)的两个BigInteger的数组, 效率
bigI1.pow(bigI2); // bigI1^bigI2

/* 比较 compareTo、equals、max、min */
System.out.println(bigI1.equals(bigI2)); // 返回 false
System.out.println(bigI1.compareTo(bigI2)); // 返回 1、0、-1 分别表示 > = <

/* 位操作相关 */
BigInteger.valueOf(1+2+4).bitCount(); // 返回3,二进制补码中与符号不同的位的数量
BigInteger.valueOf(-1-2-4).bitCount();// 返回2
BigInteger.valueOf(1+2+4).bitLength();// 返回3,最小二进制补码表示形式的位数,不包括符号位。
BigInteger.valueOf(1+2+4).clearBit(3);// 指定位二进制置零, 右数第3位(下标从0开始算)
BigInteger.valueOf(1+2+4).flipBit(3); // 指定位二进制翻转, 右数第3位(下标从0开始算)
BigInteger.valueOf(1+2+4).getLowestSetBit(); // 得到右数首个1的位置(下标从0开始算), 若返回-1则数=0
// 还有 setBig(n)指定位置1、shiftLeft(n)左移、shiftRight(n)右移、testBit(n)第n位是不是1

/* 位运算相关and、or、xor */
bigI1 = bigI1.and(bigI2); // 位与
bigI1 = bigI1.andNot(bigI2); // 位与 bigI1&~bigI2
bigI1 = bigI1.or(bigI2); // 位或
bigI1 = bigI1.xor(bigI2); // 位异或
bigI1 = bigI1.not(); // 返回取非, 当且仅当此 BigInteger 为非负时,此方法返回一个负值

/* 数学 */
bigI1.abs();
bigI1.negate(); // 返回负的bigI1
bigI1.gcd(bigI2);
bigI1.isProbablePrime(); // 返回bigT1是否可能为素数, 底层是Miller-Rabin判素
bigI1.isProbablePrime(certainty);// 出错率为(1-(1/2)^certainty), certainty越大时间复杂度越高
bigI1.modInverse(m); // 返回m的逆元,可能会抛出异常ArithmeticException,当m<=0或者无逆元(即不是m的相对素数)
bigI1.modPow(n,mod); // bigI1^n%MOD,
bigI1.nextProbablePrime(); // 返回这个数的可能的下一个素数, 出错率小于2^-100
BigInteger.probablePrime(int bitLength, Random rnd) // 返回有可能是素数的、具有指定长度的正 BigInteger

/* 进制转换 */
new BigInteger("4123", 5).toString(30); // 5进制转为30进制输出, 进制不超过可用字符26+数位10=36, 超过就变回十进制

/* 拓展:多乘优化 */
private static final class MultiplyTask extends RecursiveTask<BigInteger> {
private final BigInteger b1, b2;
public MultiplyTask(BigInteger b1, BigInteger b2) {
this.b1 = b1;
this.b2 = b2;
}
protected BigInteger compute() {
return b1.multiply(b2);
}
}
MultiplyTask mt1 = new MultiplyTask(xh, yh);
mt1.fork();
BigInteger p2 = xl.multiply(yl); // p2 = xl*yl
BigInteger p1 = mt1.join();//xh.multiply(yh); // p1 = xh*yh

BigDecimal

高精度浮点数

没写完,留坑!

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/* 声明 */
BigDecimal a = new BigDecimal("12.2121");
BigDecimal b = new BigDecimal(12121);
BigDecimal c = BigDecimal.valueOf(121.12);

/* 常量 */
BigDecimal.ONE、BigDecimal.ZERO、BigDecimal.TEN

/* 舍入模式常量 */
BigDecimal.ROUND_CEILING // 接近正无穷大的舍入模式
BigDecimal.ROUND_FLOOR // 接近负无穷大的舍入模式
BigDecimal.ROUND_UP // 舍入远离零的舍入模式
BigDecimal.ROUND_DOWN // 接近零的舍入模式
BigDecimal.ROUND_HALF_DOWN // 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则上舍入
BigDecimal.ROUND_HALF_UP // 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则下舍入
BigDecimal.ROUND_HALF_EVEN // 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向偶数舍入
BigDecimal.ROUND_UNNECESSARY // 断言请求的操作具有精确的结果,因此不需要舍入

/* 加减乘除模余add、subtact、multiply、divide remainder*/
BigDecimal addResult = a.add(b);
BigDecimal subResult = b.subtract(c);
BigDecimal mulResult = a.multiply(c);
BigDecimal divResult = b.divide(a, 20, BigDecimal.ROUND_DOWN); // 规定精度和舍入规则
BigDecimal remResult = a.remainder(c); // a%c

/* 比较 compareTo、equals、max、min*/
System.out.println(a.compareTo(b));// 1 0 -1 -> > = <
System.out.println(a.equals(b));

/* 小数点操作 */
movePointLeft(int n)
movePointRight(int n)
stripTrailingZeros() // 末导零去除

/* 数学 */
abs()
negate()// 转负数
plus() // 转正数
pow(n) // 幂次

/* 输出 */
System.out.println(a.toString()); // 科学计数法表示
System.out.println(a.toEngineeringString()); // 工程计数法表示
System.out.println(a.toPlainString()); // 10进制小数表示

/* 精度处理 */
MathContext mc = new MathContext(精度位数n, 舍入模式);
BigDecimal a = new BigDecimal("12.2121", mc);
setScale(n)
precision()
scale()

// 如果 BigDecimal 对象用作 SortedMap 中的键或 SortedSet 中的元素,则应特别小心,因为 BigDecimal 的自然排序与 equals 方法不一致。

/* 牛顿法开方 */
public static BigDecimal sqrt(BigDecimal value, int scale) {
BigDecimal num2 = BigDecimal.valueOf(2);
int precision = 120;
MathContext mc = new MathContext(precision, RoundingMode.HALF_UP);
BigDecimal deviation = value;
int cnt = 0;
while (cnt < precision) {
deviation = (deviation.add(value.divide(deviation, mc))).divide(num2, mc);
cnt++;
}
deviation = deviation.setScale(scale, BigDecimal.ROUND_HALF_UP);
return deviation;
}

// 牛顿迭代法介绍

设$f(x)$,求当$f(x)=0$时x的值,则设初始值为$x_0(其值随意)$,则根据牛顿迭代法可得$x_{k+1}=x_k-f(x_k)/f`(x_k)$,若$x_0$在解的邻域内,则牛顿迭代法就可以找到解。

Bitset

Arrays

此类包含用来操作数组(比如排序和搜索)的各种静态方法。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* 【数组填充】
* fill源码是for循环
*/
int a[] = new int[15];
Arrays.fill(a, 1);
Arrays.fill(a, 0, 8, 2); // 左闭右开

/* 【转Collection】
* Arrays.asList()
*/
int a[] = new int[15];
Arrays.asList(a);

/* 【数组输出】
* Arrays.toString(数组)
*/
System.out.println(Arrays.toString(a));
for(int i : a) System.out.print(i + " "); // for-each循环

/* 【数组拷贝】
* Arrays.copyOf(数组, len); // 从0开始复制返回数组对象
* Arrays.copyOfRange(数组, from, to);
*/
int a[] = {1,2,3,4};
int b[] = Arrays.copyOf(a, 3);
int c[] = Arrays.copyOfRange(a, 1, 3); // 左闭右开拷贝

/* 【数组判等】
* Arrays.equals(a, b);
*/
int a[] = {1,2,3}, b[] = {1,2,3};
Arrays.equals(a, b);

/* 【二分搜索】
* 需要先排序,可以为对象数组,要实现比较接口
* 返回值>=0时,为搜索值下标
* 返回值 <0时,如-3,则表明该数应该为数组第3个数
*/
int a[] = {1,3,6,7,98,1090,12332,32431231};
System.out.println(Arrays.binarySearch(a, 111));

Collections

此类包含用来容器类对象(比如排序和搜索)的各种静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 【反转】 */
Collections.reverse(容器类对象);

/* 【取大取小】*/
Collections.max(容器类对象); // 还有min

/* 【无交集判断】*/
Collections.disjoint(a, b); // 返回boolean

/* 【打乱】 */
Collections.shuffle(容器类对象);

/* 【二分】 */
int i1 = Collections.binarySearch(buy, m);


// ...等

Java 容器架构

Java 容器主要可划分为 4 个部分:List 列表、Map 映射、Set 集合、工具类 (Iterator迭代器、Enumeration枚举类、Arrays和Collections)

1567154272941

上图为 Java 容器的框架图,主干部分有两个:

  • Collection,高度抽象的接口,定义了一个集合的基本操作和属性,分为 List 和 Set 两大分支
    • List,有序列表,每个元素都有其索引。有具体的实现类如 ArrayList、LinkedList、Vector、Stack
    • Set,不重复集,每个元素是特殊唯一的。有具体的实现类如 HastSet、TreeSet
  • Map,抽象的映射容器接口,即 <key, value> 键值对的集合。有具体的实现类如 HashMap,TreeMap,WeakHashMap

有了 Java 中的多态特性,在具体使用中都建议不直接声明接口的具体实现类,而是用接口接收声明的具体的实现类(形如 List list=new ArrayList()),面向接口编程思想,规范使用,易于扩展。

Collection 容器

主要先介绍两个 List 的实现类:ArrayList 和 LinkedList。ArrayList 是可随机访问的变长数组,对应 C++ 中的 Vector。LinkedList 是众多接口例如 List、Queue、Deque 等的实现类。

Iterator

Iterator 是一个接口,它是集合的迭代器。集合可以通过 Iterator 去遍历集合中的元素。Iterator 提供的 API 接口,包括:是否存在下一个元素、获取下一个元素、删除当前元素。

注意:Iterator 遍历 Collection 时,是 fail-fast 机制的。但是由于 ACM 的单线程,故不介绍。

迭代器不是根据索引定义的,而是根据调用next()previous()所返回的最后一个元素操作定义的。

它的位置可以这样理解 ^ element(0) ^ element(1) ^ element(2) ^ element(3) ^ element(4) ^,”^”号表示迭代器当前位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* Iterator */
Iterator<E> it = ...; // 声明好的迭代器
while(it.hasNext()) {
System.out.println(it.next()); // 输出当前元素
}
it.remove(); // 移除当前元素, 比起循环过程中间remove掉某个元素造成整个集合变化,使用迭代器进行遍历过程中的remove是可行的

/* ListIterator */
ListIterator<E> it = ...; // 声明好的队列迭代器
hasNext(); // 迭代器是否处于末尾
hasPrevious(); // 迭代器是否处于首部
nextIndex(); // 后个元素下标
previousIndex(); // 前个元素下标
previous(); // 返回前个元素并移动迭代器
next(); // 返回后个元素并移动迭代器
remove(); // 删除上一次next()或previous()操作返回的元素, 没有则异常
set(E); // 设置上一次next()或previous()操作返回的元素为E, 没有则异常

ArrayList

ArrayList,实现了有序序列接口 List,用户可对列表中每个元素的插入位置进行精确地控制,可根据元素的整数索引访问元素。尽管支持使用迭代器 Iterator 进行顺序访问。但由于又实现了 RandomAccess 接口,比起 Iterator,直接使用索引访问会更快。

ArrayList 与 Vector 一样是可变数组,与 Vector 不同的有:

  1. ArrayList 是不同步的,在多线程模式下有安全问题,而 Vector 是同步的,同一时刻只有一个线程能访问 Vector,故带来了时耗,ACM 中选择 ArrayList,本文不介绍 Vector
  2. ArrayList 的内存拓展策略是 50% + 1,Vector 是直接加倍,故提前指定数组大小是较明智的

1566567956896

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* ArrayList声明 */
ArrayList<Integer> a = new ArrayList<>(); // 新建初始容量为10的空列表
ArrayList<Integer> a = new ArrayList<>(100);// 新建初始容量为100的空列表

/* 调整容量 (ArrayList实现类特有方法) */
a.ensureCapacity(2000); // 调整容量为2000
a.trimToSize(); // 调整容量至当前数组大小,释放内存

// ------------------------------------------------------------------------

/* 接口接收实现类声明 */
List<Integer> a = new ArrayList<>(); // 新建初始容量为10的空列表
List<Integer> a = new ArrayList<>(100);// 新建初始容量为100的空列表

/* 添加 */
a.add(10); // 末尾加10
a.add(0,19); // 在第0位置放上元素19, 注意下标不要超过数组范围!
a.set(0,22); // 当第0位置的元素更改为22, 注意要当前元素存在

/* 访问 */
a.get(0); // 获得索引为0的元素
a.indexOf(10); // 获得元素10首次出现的位置
a.lastIndexOf(10); // 获得元素10最后一次出现的位置
a.contains(10); // 返回a是否包含元素10
for(Integer x : a)
System.out.print(x);// 遍历ArrayList
System.out.print(a); // 直接输出ArrayList,形如[1,3,45]

/* lambda 与 foreach*/
a.forEach((x) -> System.out.print(x+" ")); // 遍历输出每个元素

/* 删除 */
a.clear(); // 清空
a.remove(2); // 删除第2个元素, 参数是基本数据类型int
a.remove((Integer)2); // 删除元素2首次出现的位置, 注意泛型Integer(int的包装类)

/* 其他 */
a.size();
a.isEmpty();
Object[] objects = a.toArray(); // 转成Object数组
// 以下为转成数组的demo,注意 Integer[] as = (Integer[]) a.toArray(); 的用法是错误的
Integer list[] = a.toArray(new Integer[a.size()]);
List<Integer> sub = a.subList(1, 2); // 截取下标1~2的视图,注意不是拷贝,在sub上操作会反馈回a

LinkedList

LinkedList,List 接口的链表实现。除此之外,此类实现 Deque 接口,add、poll 提供先进先出队列操作,可作为堆栈、队列、双端队列使用。注意,LinkedList 是不同步的。其接口方法单独作为用法讲。

1567152262544

1
2
/* 声明 */
LinkedList<Integer> list = new LinkedList<>(); // 声明空列表

List

List,可译为 “列表”或”链表”。其在 Java 中是 “列表” 的抽象接口。在此节中将其用作为 “链表”

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
26
27
28
29
30
31
32
33
34
35
36
37
/* 接口接收实现类声明 */
List<Integer> a = new LinkedList<>(); // 新建空链表

/* 添加 */
a.add(10); // 末尾加10
a.add(0,19); // 在第0位置放上元素19, 注意下标不要超过数组范围!复杂度是O(n/2)
a.set(0,22); // 当第0位置的元素更改为22, 注意要当前元素存在。 复杂度是O(n/2)

/* 访问 */
a.get(0); // 获得索引为0的元素
a.indexOf(10); // 获得元素10首次出现的位置
a.lastIndexOf(10); // 获得元素10最后一次出现的位置
a.contains(10); // 返回a是否包含元素10
for(Integer x : a)
System.out.print(x);// 遍历LinkedList
System.out.print(a); // 直接输出LinkedList,形如[1,3,45]

/* lambda 与 forEach */
a.forEach((x) -> System.out.print(x+" ")); // 遍历输出每个元素

/* 删除 */
a.clear(); // 清空
a.remove(2); // 删除第2个元素, 参数是基本数据类型int
a.remove((Integer)2); // 删除元素2首次出现的位置, 注意泛型Integer(int的包装类)

/* 迭代器 */
Iterator<Integer> it = a.iterator(); // 迭代器
Iterator<Integer> it = list.listIterator(); // 链表(双向)迭代器
Iterator<Integer> it = list.listIterator(3); // 从下标3开始的链表得带器

/* 其他 */
a.size();
a.isEmpty();
Object[] objects = a.toArray(); // 转成Object数组
// 以下为转成数组的demo,注意 Integer[] as = (Integer[]) a.toArray(); 的用法是错误的
Integer list[] = a.toArray(new Integer[a.size()]);
List<Integer> sub = a.subList(1, 2); // 截取下标1~2的视图,注意不是拷贝,在sub上操作会反馈回a

Queue (带 PriorityQueue)

主要介绍 Queue 的方法,还有特殊的 Queue 实现类 PriorityQueue 的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 声明 */
Queue<Integer> q = new LinkedList<>(); // 声明空队列

/* 赋值 */
q.offer(123); // 将元素123入队

/* 访问 */
int x = q.peek(); // 获取队列头,队列空返回null
int x = q.poll(); // 获取并移除队列头,队列空返回null

/* 带抛出异常的操作方法 */
q.add(123); // 将元素123入队,超出容量则异常
q.remove(); // 获取并移除队列首,队列为空则异常
q.element(); // 获取但不移除队列首,队列为空则异常

/* 输出 */
System.out.println(q);

优先队列 PriorityQueue 的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。优先级队列不允许使用 null 元素。注意其类是不同步的。

1
2
3
4
5
6
7
8
9
10
11
12
/* 声明 */
Queue<Integer> q = new PriorityQueue<>(); // 声明小根堆
Queue<Integer> q = new PriorityQueue<>((o1, o2) -> {
return o2 - o1;
}); // 声明大根堆

/* 线性构造 */
Integer[] Is = new Integer[10];
for (int i = 0; i < 10; i++) Is[i] = (int) (Math.random()*10+1); // 赋值
Queue<Integer> q = new PriorityQueue<>(Arrays.asList(Is));

// 操作方法的与Queue一致

Deque (作 Stack 用)

double ended queue,提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(nullfalse,具体取决于操作)

第一个元素(头部)最后一个元素(尾部)
抛出异常特殊值抛出异常特殊值
插入addFirst(e)offerFirst(e)addLast(e)offerLast(e)
移除removeFirst()pollFirst()removeLast()pollLast()
检查getFirst()peekFirst()getLast()peekLast()
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
26
27
/* 声明 */
Deque<Integer> q = new LinkedList<>(); // 声明空队列

/* 赋值 */
q.offerLast(123); // 将元素123入队尾
q.offerFirst(123); // 将元素123入队首

/* 访问 */
int x = q.peekFirst(); // 获取队列头,队列空返回null
int x = q.peekLast(); // 获取队列尾,队列空返回null
int x = q.pollFirst(); // 获取并移除队列头,队列空返回null
int x = q.pollLast(); // 获取并移除队列头,队列空返回null
int sz = q.size(); // 获得双端队列大小

/* 迭代器 */
Iterator<Integer> it = q.descendingIterator(); // 逆向迭代器
Iterator<Integer> it = q.iterator();

/* 带抛出异常的操作方法 */
// 略

/* 特殊方法 */
q.removeFirstOccurrence((Integer)123); // 从此双端队列移除第一次出现的指定元素
q.removeLastOccurrence((Integer)123); // 从此双端队列移除第一次出现的指定元素

/* 输出 */
System.out.println(q);

作 Stack 使用

1
2
3
4
5
6
7
8
9
10
/* 声明 */
Deque<Integer> s = new LinkedList<>(); // 声明空栈

/* 赋值 */
s.push(123); // 将元素123压入栈

/* 访问 */
int x = s.pop(); // 获取并弹栈
int x = s.peek(); // 获取但不弹栈
int sz = q.size(); // 获得栈大小

Map 容器

架构

1567164779330

接口:

  1. Map 是映射接口,Map中存储的内容是键值对(key-value)。
  2. AbstractMap,继承于Map的抽象类,实现了Map中的大部分API。其它Map的实现类可以通过继承AbstractMap来减少重复编码。
  3. SortedMap 是继承于Map的接口。SortedMap中的内容是排序的键值对,排序的方法是通过比较器(Comparator)。
  4. NavigableMap 是继承于SortedMap的接口。相比于SortedMap,NavigableMap有一系列的导航方法;如”获取大于/等于某对象的键值对”、“获取小于/等于某对象的键值对”等等。

实现类:

  1. TreeMap,基于红黑树的 Map、SortedMap、NavigableMap 等接口的实现类,与 C++ 中的 map 对应。适合对节点大小顺序有要求的需求场景

  2. HashMap,基于 Hash 的 Map 接口实现类。理想情形下能 O(1) 查找键值对。适合数据熵较高且不关心节点顺序的需求场景。

  3. WeakHashMap,其键是弱引用,WeakHashMap会在系统内存范围内,保存所有表项目,一旦内存不够,在GC时,没有被引用的表项很快会被清除掉,从而避免系统内存溢出。可以类比 LRU 策略,WeakHashMap 适用于小场景的缓存以提高内存命中提高查找效率。本文不做介绍。

  4. Hashtable,线程安全的 HashMap,但其继承的接口与 HashMap 有所不同。ACM 中选择 HashMap 即可。本文不做介绍。

注意 Java 自带容器并没有像 c++ 那样的 multimap。

Map.Entry

Map.Entry是Map中内部的一个接口,Map.Entry是键值对,Map 可通过 entrySet() 获取Map.Entry的键值对集合,从而通过该集合实现对键值对的操作。

1
2
3
getKey()
getValue()
setValue(V object)

Map

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/* 声明 */
Map<String, Integer> map = new TreeMap<>(); // 底层是红黑树的map
Map<String, Integer> map = new HashMap<>(); // 底层是哈希表的map

/* 赋值 */
map.put("tt", 123);

/* 访问 */
map.containsKey("tt"); // 是否包含此键
map.containsValue(123); // 是否包含此值 O(n)
int v = map.get("tt"); // 获取值
int v = map.remove("tt"); // 移除并返回键所对应的值, 不存在则返回null,将null自动解包到int会异常

/* java中的map计数 使用必须先删再回加 */
map.put("tt", map.remove("tt")+1); // 注意可能异常
map.put("tt", (map.containsKey("tt")?map.remove("tt"):0) + 1);
/* 更高效的map计数 -> 自己写一个类MutableInteger,写自增 */
Map<String, MutableInteger> map = new HashMap<>();
MutableInteger initValue = new MutableInteger(1);
MutableInteger oldValue = efficientCounter.put(a, initValue);
if(oldValue != null) initValue.set(oldValue.get() + 1);

/* 其他 */
map.size(); // map的大小
map.isEmpty();

/* 遍历所有entry */
Collection<Entry<String, Integer>> values = map.entrySet();
// foreach遍历
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
// Iterator遍历
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<Integer, Integer> entry = entries.next();
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

/* 遍历所有keys、values */
Collection<Integer> keys = map.keys();
Collection<Integer> values = map.values();

/* 输出 */
System.out.println(map);

/* 按value排序 */
Map<Integer, Integer> map = new HashMap<>();
List<Entry<Integer, Integer>> list = new ArrayList<>();
list.addAll(map.entrySet());
Collections.sort(list, (o1, o2) -> {
return o1.getValue() == o2.getValue() ? o1.getKey() - o2.getKey() : o2.getValue() - o1.getValue();
});

hashMap 重写 hashCode() 和 equals()

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
26
27
28
29
30
31
/* 【HashMap key类 重写hashCode()和equals()】 */
/* hashCode()用来定位要存放的位置,equal()用来判断是否相等(有时候我们要的是逻辑上的相等)。
原则:
1.同一个对象(没有发生过修改)无论何时调用hashCode()得到的返回值必须一样。
如果一个key对象在put的时候调用hashCode()决定了存放的位置,而在get的时候调用hashCode()得到了不一样的返回值,这个值映射到了一个和原来不一样的地方,那么肯定就找不到原来那个键值对了。
2.hashCode()的返回值相等的对象不一定相等,通过hashCode()和equals()必能唯一确定一个对象
不相等的对象的hashCode()的结果可以相等。hashCode()在注意关注碰撞问题的时候,也要关注生成速度问题,完美hash不现实
3.一旦重写了equals()(重写equals的时候还要注意要满足自反性、对称性、传递性、一致性),就必须重写hashCode()。而且hashCode()的生成哈希值的依据应该是equals()中用来比较是否相等的字段。
如果两个由equals()规定相等的对象生成的hashCode不等,对于hashMap来说,他们很可能分别映射到不同位置,没有调用equals()比较是否相等的机会,两个实际上相等的对象可能被插入不同位置,出现错误。其他一些基于哈希方法的集合类可能也会有这个问题

方法:hashCode() 对所有对象的属性,都得到一个int散列值c,然后将变量的散列值依次 hash = hash*37 + c 合并。
float c = Float.floatToIntBits(f);
double long l = Double.doubleToLongBits(f); c = (int(l^(l>>>32)))
Object c = object.hashCode();
*/
@Override
public int hashCode() {
int result = 17;
result = 37*result+name.hashCode();
result = 37*result+age;
result = 37*result+(sex ? 0 : 1);
return result;
}
@Override
public boolean equals(Object obj) {
return obj instanceof Student &&
this.name.equals(((Student)obj).name) &&
this.age == ((Student)obj).age &&
this.sex == ((Student)obj).sex;
}
// 当前,java的IDE一般都可以直接 Source->Generate hashCode and equals() 不用手写

SortedMap

1
2
3
4
5
6
7
8
9
10
11
12
/* 声明 */
SortedMap<String, Integer> map = new TreeMap();
SortedMap<Integer, Integer> map = new TreeMap<>((o1, o2) -> {
return o1.compareTo(o2);
});

/* 较Map新增API */
K firstKey()
SortedMap<K, V> headMap(K endKey)
K lastKey()
SortedMap<K, V> subMap(K startKey, K endKey)
SortedMap<K, V> tailMap(K startKey)

继承于SortedMap的接口。一个可导航的键-值对集合,具有了为给定搜索目标报告最接近匹配项的导航方法。

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
26
/* 声明 */
NavigableMap<String, Integer> map = new TreeMap();

/* 提供操作<键,值>对的方法, 不存在则返回null */
Entry<K, V> lowerEntry(key) // 返回小于key所对应的键值对的第一个键值对
Entry<K, V> floorEntry(key) // 返回小于等于key所对应的键值对的第一个键值对
Entry<K, V> ceilingEntry(key) // 返回大于等于key所对应的键值对的第一个键值对
Entry<K, V> higherEntry(key) // 返回大于key所对应的键值对的第一个键值对

firstEntry(); // 返回首个键值对
pollFirstEntry(); // 弹出首个键值对
lastEntry(); // 返回末个键值对
pollLastEntry(); // 弹出末个键值对

/* 提供操作键的方法 */
K lowerKey(key) // 返回小于key的第一个键
K floorKey(key) // 返回小于等于key的第一个键
K ceilingKey(key) // 返回大于等于key的第一个键
K higherKey(key) // 返回大于key的第一个键

/* 获取键集 */
NavigableSet<String> subset = map.navigableKeySet(); // 获取正序的键集
NavigableSet<String> subset = map.descendingKeySet();// 获取反序的键集

/* 获取当前map的逆序视图 */
NavigableMap<String, Integer> subset = map.descendingMap();

Set 容器

架构

Map 同样继承于 Collection 接口,但在本文将其单独拿出来讲述。Set的实现类都是基于Map来实现

1567231051893

接口:

  1. Set 是继承于Collection的接口。它是一个不允许有重复元素的集合。
  2. AbstractSet 是一个抽象类,它继承于AbstractCollection,AbstractCollection实现了 Set中的绝大部分函数,其它Set的实现类可以通过继承AbstractSet 来减少重复编码。
  3. SortedSet、NavigableSet 可以类比 Map

实现类:

  1. TreeSet,依赖于 TreeMap,底层同样是红黑树。元素有序。
  2. HashSet,依赖于 HashMap,底层是拉链法的哈希。元素无序。

Set

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
26
27
/* 声明 */
Set<Integer> set = new TreeSet<>(); // 底层是红黑树
Set<Integer> set = new HashSet<>(); // 底层是哈希

/* 赋值 */
set.add(123);

/* 删除 */
set.remove(123);
set.clear();

/* 访问 */
set.contains(123);
for(Integer x : set) {
System.out.println(x);
}
set.forEach(x -> System.out.println(x));

/* 其他 */
set.size();
set.isEmpty();
Object[] objects = set.toArray(); // 转成Object数组
// 以下为转成数组的demo,注意 Integer[] as = (Integer[]) a.toArray(); 的用法是错误的
Integer list[] = set.toArray(new Integer[set.size()]);

/* 输出 */
System.out.println(set);

SortedSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 声明 */
SortedSet<Integer> set = new TreeSet<>(); // 从小到大排序
SortedSet<Integer> set = new TreeSet<>((o1, o2) -> {
return o2 - o1;
}); // 从大到小排序
SortedSet<String> set2 = new TreeSet<>(); // 从小到大排序

/* 较Set新增API */
set.first(); // 最低元素
set.last(); // 最高元素
set.tailSet(123); // 返回set的部分视图,其元素大于等于123
set.subSet(123, 126); // 返回set的部分视图,其元素[123,126)间
set2.subSet(low, high+"\0");// 返回set2的部分视图,其元素[low,high]间
set2.subSet(low+"\0", high);// 返回set2的部分视图,其元素(low,high)间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 声明 */
NavigableSet<Integer> set = new TreeSet<>(); // 从小到大排序
NavigableSet<Integer> set = new TreeSet<>((o1, o2) -> {
return o2 - o1;
}); // 从大到小排序
NavigableSet<String> set2 = new TreeSet<>(); // 从小到大排序

/* 较NavigableSet新增API */
set.ceiling(123); // 返回大于等于123的第一个元素,不存在返回null
set.floor(123); // 返回小于等于123的第一个元素,不存在返回null
set.lower(123); // 返回小于123的第一个元素,不存在返回null
set.higher(123); // 返回小于123的第一个元素,不存在返回null
set.headSet(123); // 返回set的部分视图,其元素小于123
set.headSet(123, true); // 返回set的部分视图,其元素小于等于123
set.pollFirst(); // 获取并移除最低元素,set为空返回null
set.pollLast(); // 获取并移除最高元素,set为空返回null

/* 其他 */
descendingIterator(); // 逆序迭代器
descendingSet(); // 逆序视图

排序

排序大多是针对数组、容器类对象施展的,同样依靠Arrays、Collections,单独提取出来总结。

  • Arrays.sort() 对[原生数组]操作
  • Collections.sort() 对[容器类对象]操作

实现Comparable接口类的排序

可比较接口,实现后类的实例可以直接用compareTo比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 原生数组 */
Student[] ss = new Student[10];
for(int i=0;i<10;i++) ss[i] = new Student(i);
Arrays.sort(ss);

/* 容器类对象 */
List<Student> ss = new LinkedList<>();
for(int i=0;i<10;i++) ss.add(new Student(i));
Collections.sort(ss);

/* student类的定义 */
class Student implements Comparable<Student> // 要求实现Comparable
{
int age = 0;
public int compareTo(Student o) { return age-o.age; } // 年龄升序
public Student(int age) { this.age = age; }
}

借由Comparator接口类的排序

比较器类,帮助对象进行比较,作排序传参。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 原生数组 */
Student[] ss = new Student[10];
for(int i=0;i<10;i++) ss[i] = new Student(i);
Arrays.sort(ss, new StudentCompartor());

/* 容器类对象 */
List<Student> ss = new LinkedList<>();
for(int i=0;i<10;i++) ss.add(new Student(i));
Collections.sort(ss, new StudentCompartor());

/* student类的定义 */
class Student
{
int age = 0;
public Student(int age) { this.age = age; }
}

/* studentCompartor类的定义 */
class StudentCompartor implements Comparator<Student>
{
@Override
public int compare(Student o1, Student o2) { return o1.age-o2.age; } // 年龄升序
}

匿名内部类实现的排序

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
26
27
/* 原生数组 */
P[] ps = new P[10];
for(int i=0;i<10;i++) ps[i] = new P(i);
Arrays.sort(ps, new Comparator<P>() { // 第1关键字升序第2关键字降序
public int compare(P o1, P o2) {
if(o1.a != o2.a) return o1.a-o2.a;
return o2.b-o1.b;
}
});

/* 容器类对象 */
List<P> ps = new LinkedList<>();
for(int i=0;i<10;i++) ps.add(new P(i,10-i));
Collections.sort(ps, new Comparator<P>() { // 第1关键字升序第2关键字降序
public int compare(P o1, P o2)
{
if(o1.a != o2.a) return o1.a-o2.a;
return o2.b-o1.b;
}
});

/* P类的定义 */
class P
{
int a, b;
public P(int a, int b) { this.a = a;this.b = b; }
}

Lambda

java8 新增了对Lambda表达式的支持。

简化多关键字排序

1
2
3
4
5
6
7
8
9
10
11
12
/* 原生数组 */
Arrays.sort(ps, (o1, o2)->(o1.a!=o2.a ? o1.a-o2.a : o2.b-o2.a));

/* 容器类对象 */
Collections.sort(ps, (o1, o2)->(o1.a!=o2.a ? o1.a-o2.a : o2.b-o2.a));

/* P类的定义 */
class P
{
int a, b;
public P(int a, int b) { this.a = a;this.b = b; }
}

String

1
2

replace(oldS, newS); // 非正则表达式的全部替换
特别字符说明
$匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或‘\r’。要匹配 $ 字符本身,请使用 $。
( )标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用 ( 和 )。
*匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 *。
+匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 +。
.匹配除换行符 \n之外的任何单字符。要匹配 .,请使用 \。
[ ]标记一个中括号表达式的开始。要匹配 [,请使用 [。
?匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 ?。
\将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, ‘n’ 匹配字符 ‘n’。’\n’ 匹配换行符。序列 ‘\‘ 匹配 “\”,而 ‘(‘ 则匹配 “(”。
^匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 ^。
{ }标记限定符表达式的开始。要匹配 {,请使用 {。
|指明两项之间的一个选择。要匹配 |,请使用 |。
1
'单引号。要匹配’,请使用\'。      "双引号。要匹配“,请使用\"。

StringBuilder

Regex

java.util.regex.*中有正则表达式的方法,除此之外String自带方法也支持正则表达式。

正则表达式主要关注:匹配、替换、提取、切割。

转义

1
2
3
4
5
6
7
8
9
10
11
12
13
// 注意在java里匹配\需要输入\\\\,因为\\是java语言本身对\的转义,而到了正则引擎还会经过一层转义。 也可直接使用下面转义

Matcher.quoteReplacement("\\");

static String escapeExprSpecialWord(String keyword) {
final String[] fbsArr = { "\\", "$", "(", ")", "*", "+", ".", "[", "]", "?", "^", "{", "}", "|" };
for (String key : fbsArr) {
if (keyword.contains(key)) {
keyword = keyword.replace(key, "\\" + key);
}
}
return keyword;
}

匹配

1
2
String s = "189893";
if(s.matches("1.*?3")) System.out.println("yes");

切割

1
2
String s = "ads1dasd2dasd3dasd4as";
String[] ss = s.split("[0-9]");

替换

1
2
String s = "<html class=123> <a href=2333>";
String ss = s.replaceAll("<.*? ([^>]*)>", "<div $1>"); // 将标签都换为div

提取

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
26
String testStr = "Java is one of my favorite programming language!";
Pattern re = Pattern.compile("[a-zA-Z]+");
Matcher m = re.matcher(testStr);
while(m.find())
System.out.println("testStr["+m.start()+", "+m.end()+"]: "+m.group());
// 输出各个单词

String s = "<html class=123> <a href=2333> <div sss=asd>";
Pattern re = Pattern.compile("<.*? [^>]*=([^>]*)>");
Matcher m = re.matcher(s);
while(m.find())
{
System.out.println("匹配串:" + m.group(0));
System.out.print("匹配部分:");
for(int i=1;i<=m.groupCount();i++)
System.out.print(m.group(i) + " ");
System.out.println("");
}
/* 输出标签参数内容:
匹配串:<html class=123>
匹配部分:123
匹配串:<a href=2333>
匹配部分:2333
匹配串:<div sss=asd>
匹配部分:asd
*/

Eclipse使用

1
2
3
4
5
6
7
8
0.【主题更改】:
Window->Preferences->General->Appearance
1.【代码提示功能强化】:
Window->Preferences->Java->Editor->Content Assist->[Auto Activation].[Auto activation triggers for Java],把".abcd...xyz"全按上,意思是是按哪些键自动触发代码提示功能。
2.【代码提示功能之防空格防等号补全】:
Window->Preferences->Java->Editor->Content Assist->[Disable insertion triggers except'Enter']√
3.【字体大小】
Ctrl+加号/减号

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×