JAVA数据结构、集合操作及常用API_C++开发转JAVA
- 零、引言
- 一、JAVA数据结构基础
- 二、数组
- 三、 集合进阶&常用API
- 四、stream.filter collection【原理和进阶操作未完待续...】
- 五、Gson等java转换小技巧
- 六、JAVA输入(刷题使用)
- Final、参考
零、引言
日常JAVA开发过程中需要用到的JAVA数据结构基础概念。
进阶集合框架、collection操作、常用API。
JAVA Gson等常用转化技巧
一、JAVA数据结构基础
1.0 数据类型概述
Java的数据类型分为:基本类型、引用类型。
String是引用类型,大量使用字符串处理,避免使用String,应大量使用StringBuffer,因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。是lang包下的类。
1.1 基本数据类型
Java 中有 8 种基本数据类型。
4 种整数型:byte、short、int、long
2 种浮点型:float、double
1 种字符类型:char
1 种布尔型:boolean
Java数据类型所占存储空间大小与机器硬件结构无关,可移植性更高。
1.2 包装类
包装类将基本数据类型包装转化为类的形式,具有面向对象的特征。
Java集合中只能放入包装类型,而不支持基本类型。
Java类型间的转换可以通过包装类的方法实现,基本数据类型没有类的属性方法。
所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
对于 Integer var 在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生, 会复用已有对象,这个区间内的Integer 值可以直接使用==进行判断.
但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,使用 equals 方法进行判断。
包装类
- | 基本数据类型 | 字节数 | 包装类 |
---|---|---|---|
布尔型 | boolean | 1 | Boolean |
字符型 | char | 2 | Character |
整型 | byte | 1 | Byte |
整型 | short | 2 | Short |
整型 | int | 4 | Integer |
整型 | long | 8 | Long |
浮点型 | float | 4 | Float |
浮点型 | double | 8 | Double |
注意
由于JAVA中的基本数据类型具有:
- ①不具备对象特性;(属性和方法等)
- ②:无法进行对象化交互和操作——因此出现包装类
- 【注意下面的装箱和拆箱,以及数据类型转换!!!】
1.3 基本类型和包装类型的区别
默认值:包装类型不赋值就是 null ,基本类型有默认值且不是 null。
包装类型可用于泛型,而基本类型不可以。
包装类型属于对象类型,几乎所有对象实例都存在于堆中。
基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中。
基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。
1.4 包装类型的缓存机制
包装类型的大部分都用到了缓存机制来提升性能:节省创建对象的时间开销。对象可以自由共享。
Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据。
Character 创建了数值在 [0,127] 范围的缓存数据。
Boolean 直接返回 True or False。
Float,Double 并没有实现缓存机制。
如果超出对应范围会创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
包装器类提供了对象的缓存,String类提供了常量池,都有final修饰,对象一经创建后不可修改。
1.5 equals() 和 ==
==
- 对于基本数据类型来说,== 比较的是值。
- 对于引用数据类型来说,== 比较的是对象的内存地址。
- == 运行速度比equals()快,因为==只是比较引用。
- 注意String中被重写了,equals比较的是值而不是地址。(因为有享元模式共享字符串)
equals()
- equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。
- equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。
- 如果类没有重写 equals()方法:等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。是否为同一内存地址。
- 如果类重写了 equals()方法 :一般会重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
- String 中的 equals 方法是被重写过的,比较的是对象的值。
- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb);// true
System.out.println(a == b);// false
System.out.println(a.equals(b));// true
System.out.println(42 == 42.0);// true
1.6 自动装箱拆箱
装箱:将基本类型用它们对应的引用类型包装起来;【调用了包装类的XXX.valueOf()方法(其返回包装类,但是可自动拆箱)】
拆箱:将包装类型转换为基本数据类型;【调用了包装类的XXX.parseXXXX】或调用了 intValue()方法。
如果频繁拆装箱的话,也会严重影响系统的性能。应该尽量避免不必要的拆装箱操作。
public static void WrapTest1() {
// 自动装箱,等效于 Integer num = Integer.valueOf(100);
Integer num = 100;
// 自动拆箱,等效于 int num2 = num.intValue();
int num2 = num;
// valueOf() 方法返回对应包装类实例
System.out.println("-----valueOf()-----");
Integer a =Integer.valueOf(9); // 将int转为Integer包装类对象
Double b = Double.valueOf(5);
Float c = Float.valueOf("80"); // 将字符串转为Float包装类对象
Integer d = Integer.valueOf("444",16); // 使用 16 进制
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
结果:
-----valueOf()-----
9
5.0
80.0
1092
1.6.1 valueOf 和 parseXXX源码实例
Long.parseLong(s)
public static long parseLong(String s) throws NumberFormatException {
return parseLong(s, 10);
}
- Long.parseLong(s)返回的是基本数据类型long
- 若只需转换为基础类型,用 Long.parseLong 性能较好
Long.valueOf(s) 源码
public static Long valueOf(String s) throws NumberFormatException {
return Long.valueOf(parseLong(s, 10));
}
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return Long.LongCache.cache[(int)l + offset];
}
return new Long(l);
}
- Long.valueOf(s) 返回的是包装类Long
- 对 -128~127 的long类型的数据封装成静态对象数组,下次取的时候返回的是已缓存的对象的引用
- Long.valueOf(s)仍然调用的是Long.parseLong(“”)
1.7 浮点数精度丢失
浮点数运算的时候会有精度丢失的风险。计算机使用有限的宽度表示一个数字,无限循环的小数存储在计算机时,只能被截断。
BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。
BigInteger 内部使用 int[] 数组来存储任意大小的整形数据。
1.8 数值、字符转化、占位大小和缓冲池等代码解析
// 精度测试
public static void intTest() {
System.out.println(5/2);
double x = 1.0 / 10;
double y = 1 - 9.0 / 10;
System.out.println(x);
System.out.println(y);
// 四舍五入
double d = 2.6;
int n = (int) (d + 0.5);
System.out.println(n);
double e = 2.4;
int m = (int) (e + 0.5);
System.out.println(m);
char c3 = '\u0041';
System.out.println(c3);
double d = 12900000;
System.out.println(d);
double e = 3.1415926;
System.out.printf("%.2f\n", e);
System.out.printf("%.4f\n", e);
int n = 12345000;
System.out.printf("n=%d, hex=%08x \n", n, n);
System.out.printf("%%%d", n);
}
// 基本数据类型与字符串的转换
public static void WrapTest3() {
// 基本数据类型转换为String
String str1 = String.valueOf(111);
String str2 = 222 + "";
String str3 = Integer.toString(333);
// 字符串转换成基本数据类型
int a = Integer.parseInt("111");
// 字符串转换成包装类
Integer b = Integer.valueOf("222");
// 包装类型转换为字符串类型
String str4 = String.valueOf(Integer.valueOf(444));
}
1.9 try-catch
try-catch用来捕获代码段的异常并做出处理
try-catch代码块分两块,第一块是try{} ,第二块是catch(exception的引用){}。
try-catch一般放在循环放外。
try-catch,try即尝试,尝试能不能正常的走完整个作用域,如果不能则抛出一个异常。所以在try块里经常放上可能会抛出异常的程序段。而catch就是处理try里抛出来的异常,其中catch的参数列表接收的是一个异常的引用,是throw抛出来的异常的引用。
try-catch可以嵌套使用,以下是一个try-catch嵌套使用的例子:
public static void main(String[] args) {
try {
System.out.println("**********************外层try**********************");
errorMethod();
} catch (Exception e) {
System.out.println("**********************外层catch" + e + "**********************");
} finally {
System.out.println("**********************外层finally**********************");
}
}
private static void errorMethod() {
try {
System.out.println("**********************内层try**********************");
int i = 0;
int a = 100 / i;
} catch (Exception e) {
System.out.println("**********************内层catch" + e + "**********************");
} finally {
System.out.println("**********************内层finally**********************");
}
执行顺序:
- 内层A,E处抛出异常:由外层catch块捕获,并执行外层finally ;
- 内层B处抛出异常,且有一合适内层catch捕获在:执行内层finally,后执行E处 ;
- 内层B处抛出异常,但内层catch块没有合适处理程序:执行内层finally,搜索外层catch,找合适的,执行外层finally,此时不会执行E ;
- 内层C处抛出异常在:退出内层catch块,执行内层finally,搜索外层catch,找到合适,执行外层finally ;
- 内层D处抛出异常在:退出内层finally块,搜索外层catch,找到合适,执行外层finally
总结:
-
try-catch 嵌套内层catch 可以捕获异常时,外层catch不会执行,但finally (多用于IO关闭)都会执行。
-
try-catch一般用在最上层的程序里,可以配合throws和throw再将异常抛给用户,这种情况会使上层代码中断。也可以不选择抛出,这种上层代码会继续运行。
-
被调用的方法如果有异常的可能可以通过throws抛给上层处理,不加try catch的情况如下会自动往上抛,加了try catch需要如上通过throw抛给上层程序。
1.10 字符串
1 常用API
2 创建方法两种不同
1)直接赋值(String str = "hello")
:只开辟一块堆内存空间,并且会自动入池,不会产生垃圾。
2)构造方法(String str= new String("hello");
:会开辟两块堆内存空间,其中一块堆内存会变成垃圾被系统回收,而且不能够自动入池,需要通过public String intern();方法进行手工入池。
在开发的过程中不会采用构造方法进行字符串的实例化。
3 String不可变性
String是个常量,从一出生就注定不可变。
因为:String类被final修饰
[为什么final呢?分析]
因为String太过常用,JAVA类库的设计者在实现时做了个小小的变化,即采用了享元模式,每当生成一个新内容的字符串时,他们都被添加到一个共享池中,当第二次再次生成同样内容的字符串实例时,
就共享此对象,而不是创建一个新对象,但是这样的做法仅仅适合于通过=
符号进行的初始化。
需要说明一点的是,在object中,equals()是用来比较内存地址的,但是String重写了equals()方法,用来比较内容的,即使是不同地址,只要内容一致,也会返回true,这也就是为什么a.equals©返回true的原因了。
[String不可变的好处]
-
可以实现多个变量引用堆内存中的同一个字符串实例,避免创建的开销。
-
我们的程序中大量使用了String字符串,有可能是出于安全性考虑。
-
大家都知道HashMap中key为String类型,如果可变将变的多么可怕。
-
当我们在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,如果使用可变类的话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。
4 字符串和字节数组转换中 编码问题
在后面的输入输出流中我们会看到,数据在进行传输时是以二进制的格式进行的,所以会用到将字符串转换完字节数组的内
容,而在转换的时候可以指定编码格式。
这里说明一下字节,字节用byte类型表示,1个字节是8个二进制位,byte类型表示的范围是-128到127之间的整数。
从字符串与byte数组间相互转换的例题可以看出,如果编码是GBK格式,则2个byte类型的数据表示一个汉字,如果编码是
UTF-8格式,则3个byte类型的数据表示一个汉字。而字母和空格还是用一个字节表示。如:下面是使用UTF-8编码的情况
下,字符串“JAVA 编程 基础”对应的byte数据。
1.11 StringBuilder&StringBuffer(线程不安全与线程安全)
String和StringBuilder的区别:
- String 具有不可变性,
- 而 StringBuilder不具备
建议:
当频繁操作字符串时,使用 StringBuilder。
StringBuilder和StringBuffer区别:
- 二者基本相似
- StringBuilder 线程不安全,所以性能略高
- StringBuffer 线程安全的,
- 在执行速度方面的比较:StringBuilder > StringBuffer
StringBuilder类常用方法
二、数组
数组就是相同数据类型的元素按一定顺序排列的集合,就是把有限个类型相同的变量用一个名字命名,然后用编号区分他们的变量的集合,这个名字称为数组名,编号称为下标。
数组是引用类型:所以可以多维度;初始化时要声明长度;是在一串连续的空间内;可以是不同类型但一次只能一只类型。
2.1 初始化数组
动态初始化
int[] array = new int[3];
// 两种方式都可以
// int array[] = new int[3];
array[0] = 1;
array[1] = 2;
array[2] = 3;
System.out.println(array[0]);
静态初始化
int[] array = { 1, 2, 3, 4 };
int[] array1 = new int[] { 1, 2, 3, 4 };
System.out.println(array[2]);
System.out.println(array1[2]);
默认初始化
int[] array = new int[4];
System.out.println(array[2]);
2 获取数组的长度
int[] array = new int[10];
int length = array.length;
System.out.println("数组array的长度是:" + length);
3二维数组
二维数组初始化
同一维数组一样,共有4总不同形式的定义方法:
int[][] array1 = new int[10][10];
int array2[][] = new int[10][10];
int array3[][] = { { 1, 1, 1 }, { 2, 2, 2 } };
int array4[][] = new int[][] { { 1, 1, 1 }, { 2, 2, 2 } };
int[][] array = new int[3][];
array[0] = new int[1];
array[1] = new int[2];
array[2] = new int[3];
获取二维数组的长度
int length1 = array.length;
int length2 = array[0].length;
// 获取二维数组的第一维长度(3)
System.out.println(length1);
// 获取二维数组的第一维的第一个数组长度(1)
System.out.println(length2);
4数组遍历
传统方式遍历
int[] array = new int[] { 1, 2, 3 };
for (int i = 0; i < array.length; i++) {
System.out.println("array[" + i + "] = " + array[i]);
}
增强for循环方式遍历
int[] array = new int[] { 1, 2, 3 };
for (int i : array) {
System.out.println(i);
}
5数组排序
另:常用的排序方法有冒泡排序、快速排序、选择排序、插入排序、希尔(Shell)排序、堆排序。可参考Java 数组排序
6数组的复制、插入和合并
(1)在copy()方法中,运用System.arraycopy()
方法对数组进行复制。方法中声明两个数组,其中array数组在声明时初始化。
temp1数组的长度为array数组的长度以便进行复制。进行复制时arraycopy(array,0,temp1,1,array.length)
方法
表示将array数组从第一个数组从第一个元素开始复制到temp1数组下标从0到array.length的位置。
(2)在insert()方法中,运用循环对数组初始化并排好序。接受通过键盘输入的待插入的数值。循环线查找第一个大于要插入数组的位置,然后在运用循环为要插入的数组留出位置,将待插入的数放到该位置。
(3)在combine()方法中,声明一个新数组,长度为传入数组的长度之和。循环查看传入的两个数组元素,
将两个数组的元素依次进行大小比较,比较之后的元素放入新数组。通过arraycopy()方法将末放入新数组的元素复制进去,完成数组的合并与新数组的排序。
(4) 合并可以直接使用API:
int[] array1 = { 1, 2, 3, 4, 5 };
int[] array2 = { 6, 7, 8, 9, 10 };
int[] array = org.apache.commons.lang.ArrayUtils.addAll(array1, array2);
System.out.println(Arrays.toString(array));
//数组是一种重要的数据结构。一个数组是具有同一个数据类型的对象的集合。数据类型可以是简单类型,也可以是类。
//数组可以是一维的,也可以是多维的,通过其下标可以访问数组元素。数组提供了一种将有联系的信息进行分组的有效方法。
import java.util.Scanner;//导入类
/*本质上讲,数组是一组相关的存储单元,这些存储单元在逻辑上被看作是相互独立的若干元素,它们具有相同的名字和数据类型。
数组中的元素位置由它们的下标决定。对数组的操作包括数组的复制,数组的合并,插入以及搜索等。
*/
public class TextOperatorArray {//对数组元素进行各种操作的类
public static void copy() {//数组复制
int array[] = new int[]{1, 2, 3, 4};//声明数组并初始化
int temp1[] = new int[array.length];//声明数组长度为array数组长度
int temp2[] = new int[array.length];
System.arraycopy(array, 0, temp1, 0, array.length);//将数组array复制给数组temp1
System.out.print("复制后的数组结果:");
for (int i = 0; i < temp1.length; i++)//循环显示数组元素
System.out.print(temp1[i] + "");
System.out.println();
temp2 = array;//使用赋值将数组array复制给数组temp2
}
public static void insert() {//数组插入
int i, j;
int n = 5;
int num[] = new int[n + 1];
for (i = 0; i < num.length - 1; i++) {
num[i] = (i + 1) * 6;
}
int length = num.length;//获得数组长度
System.out.println("插入数字之前的数组为:");
for (i = 0; i < length; i++)//循环显示数组元素
if (num[i] == 0)
System.out.print("存储空间");
else
System.out.println();
System.out.println();
System.out.println("输出一个要插入的数:");
Scanner scan = new Scanner(System.in);//键盘输入一个数
int in = scan.nextInt();
for (i = 0; i < length - 1; i++) {//循环查找一个大于要插入的数的位置
if (num[i] > in)//找到大于要插入的数就跳出
break;
}
for (j = length - 1; j > i; j--) {//为要插入的数留出位置
num[j] = num[j - 1];
}
num[j] = in;//将要插入的数保存到该位置
for (i = 0; i < length; i++)//循环显示数组元素
System.out.print(num[i] + "");
System.out.println();
}
public static int[] combine(int[] a, int[] b) {//数组合并并排好序
int alen = a.length;//获得传入的数组的长度
int blen = b.length;
int length = alen + blen;
int i, j;
System.out.println("合并之间的两个数组:");
for (i = 0; i < alen; i++)//循环显示a数组元素
System.out.print(a[i] + "");
System.out.println();
for (i = 0; i < blen; i++)//循环显示b数组元素
System.out.print(b[i] + "");
System.out.println();
int[] c = new int[length];//声明数组长度为传入两个数组长度之和
for (i = 0, j = 0; i < alen && j < blen; ) {//循环查看元素进行比较
if (a[i] < b[j]) {//判断两个数组元素值的大小
c[i + j] = a[i];
i++;
} else {
c[i + j] = b[j];
j++;
}
}
if (i == alen)//将b数组从下标为i开始将值赋给c数组,放在从c数组的blen+i,alen-i之间
System.arraycopy(b, j, c, alen + j, blen - j);
if (j == blen)
System.arraycopy(a, i, c, blen + i, alen - i);
System.out.println("合并并排序之后的新数组:");
for (int k = 0; k < a.length + b.length; k++)//循环输出合并后的数组的元素
System.out.print(c[k] + "");
System.out.println();
return c;
}
public static void main(String[] args) {//Java程序的主入口处
System.out.println("1.数组复制:");
copy();
int a[] = {1, 2, 3, 12};//声明数组并初始化
int b[] = {5, 6, 7, 8};
System.out.println("2.数组合并:");
int c[] = combine(a, b);//调用数组合并方法
System.out.println("3.数组插入:");
insert();//调用数组插入方法
}
}
7Array 实用API
输出数组
int[] array = { 1, 2, 3 };
System.out.println(Arrays.toString(array));
数组转List
String[] array = { "a", "b", "c", "d", "e" };
List<String> list = new ArrayList<String>(Arrays.asList(array));
System.out.println(list);
数组转Set
String[] array = { "a", "b", "c", "d", "e" };
Set<String> set = new HashSet<String>(Arrays.asList(array));
System.out.println(set);
List转数组
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
String[] array = new String[list.size()];
list.toArray(array);
for (String s : array)
System.out.println(s);
数组中是否包含某个值
String[] array = { "a", "b", "c", "d", "e" };
boolean b = Arrays.asList(array).contains("a");
System.out.println(b);
数组合并
int[] array1 = { 1, 2, 3, 4, 5 };
int[] array2 = { 6, 7, 8, 9, 10 };
int[] array = org.apache.commons.lang.ArrayUtils.addAll(array1, array2);
System.out.println(Arrays.toString(array));
String数组转字符串(使用指定字符拼接)
String[] array = { "a", "b", "c" };
String str = org.apache.commons.lang.StringUtils.join(array, ", ");
System.out.println(str);
数组逆序
int[] array = { 1, 2, 3, 4, 5 };
org.apache.commons.lang.ArrayUtils.reverse(array);
System.out.println(Arrays.toString(array));
数组元素移除
int[] array = { 1, 2, 3, 4, 5 };
int[] removed = org.apache.commons.lang.ArrayUtils.removeElement(array, 3);
System.out.println(Arrays.toString(removed));
数组和集合的区别+场景
区别
适用场景:
数组适用于:长度固定且主要用于查询操作。
集合适用于:
三、 集合进阶&常用API
3.0 集合底层实现及其原理
Collection
|-----List 有序,可重复(存储顺序和取出顺序一致)
|–|----LinkedList 底层使用双向链表实现,查询慢,增删快。效率高
|–|----ArrayList 底层使用数组实现,查询快,增删慢。效率高。
| | 每次容量不足时,自增长度的一半,如下源码可知
| | int newCapacity = oldCapacity + (oldCapacity >> 1);
|–|----Vector 底层使用数组实现,线程安全,查询快,增删慢。效率低。
| | 每次容量不足时,默认自增长度的一倍(如果不指定增量的话),如下源码可知
| | int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
| | capacityIncrement : oldCapacity);
|-----Set 元素唯一(最多包含一个 null 元素),只能通过游标来取值,线程不安全
HashSet比TreeSet高效(尤其是查询、添加),LinkedHashSet比hash插入、删除慢,但是遍历快。
|–|–HashSet 底层是由HashMap实现的,线程非安全,通过对象的hashCode方法与equals方法来保证插入元素的唯一性,无序(存储顺序和取出顺序不一致)
|–|–|–LinkedHashSet 底层数据结构由哈希表和链表组成。哈希表保证元素的唯一性,链表保证元素有序。(存储和取出是一致)
|–|–TreeSet 基于 TreeMap 的 NavigableSet 实现。非同步,排序,元素唯一。 保持有序的set使用(使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序(红黑数维护次序)
—Map 是键值对集合,key 不允许重复,value 可以
|-----HashMap 基于链表和红黑树:hashMap用hash表实现的Map,就是利用对象的hashcode(hashcode()是Object的方法)进行快速散列查找。Null可以做主键,但只能有一个
底层数据结构是:
jdk1.8以下:(数组+单向链表)哈希表
jdk1.8+:(数组+[单向链表 / 红黑树])哈希表,根据情况会选择链表和红黑树之间进行转换
|-----TreeMap 基于红黑树
|-----LinkedHashMap HashMap+LinkedList
|-----HashTable 线程安全,不允许有null值的存在
java集合框架概括
只有Vector,HashTable是线程安全的
ArrayList,LinkedList,HashSet,TreeSet,HashMap,TreeMap都不是线程安全的。
如果没有必要,推荐代码只同List,Map接口打交道.
HashMap的输出顺序是随机的,TreeMap中的条目是按键值的升序排列的,LinkedHashMap是按元素最后一次被访问的时间从早到晚排序的
使用选择
是否是键值对象形式:
是:Map
键是否需要排序:
是:TreeMap
否:HashMap
不知道,就使用HashMap。
否:Collection
元素是否唯一:
是:Set
元素是否需要排序:
是:TreeSet
否:HashSet
不知道,就使用HashSet
否:List
要安全吗:
是:Vector
否:ArrayList或者LinkedList
增删多:LinkedList
查询多:ArrayList
不知道,就使用ArrayList
不知道,就使用ArrayList
集合的常见方法及遍历方式
Collection:
add()
remove()
contains()
iterator()
size()
遍历:
增强for
迭代器
|--List
get()
遍历:
普通for
|--Set
Map:
put()
remove()
containskey(),containsValue()
keySet()
get()
value()
entrySet()
size()
遍历:
根据键找值
根据键值对对象分别找键和值
3.1 集合框架图
从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
集合框架是一个用来代表和操纵集合的统一架构。所有的集合框架都包含如下内容:
-
接口:是代表集合的抽象数据类型。例如 Collection、List、Set、Map 等。之所以定义多个接口,是为了以不同的方式操作集合对象
-
实现(类):是集合接口的具体实现。从本质上讲,它们是可重复使用的数据结构,例如:ArrayList、LinkedList、HashSet、HashMap。
-
算法:是实现集合接口的对象里的方法执行的一些有用的计算,例如:搜索和排序,这些算法实现了多态,那是因为相同的方法可以在相似的接口上有着不同的实现。
除了集合,该框架也定义了几个 Map 接口和类。Map 里存储的是键/值对。尽管 Map 不是集合,但是它们完全整合在集合中。
Java 集合框架提供了一套性能优良,使用方便的接口和类,java集合框架位于java.util包中, 所以当使用集合框架的时候需要进行导包。
3.2 常用集合类和API
1:数组【不是集合】
定义
int len = 5;
int[] arr = new int[len];
赋值
for (int i = 0; i < arr.length; i++) {
Scanner scanner = new Scanner(System.in);
arr[i] = scanner.nextInt();
}
遍历输出
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
常用API之排序
Arrays.sort(arr);
2.List
ArrayList
常用方法列表如下:
方法 | 描述 |
---|---|
add() | 将元素插入到指定位置的 arraylist 中 |
addAll() | 添加集合中的所有元素到 arraylist 中 |
clear() | 删除 arraylist 中的所有元素 |
clone() | 复制一份 arraylist |
contains() | 判断元素是否在 arraylist |
get() | 通过索引值获取 arraylist 中的元素 |
indexOf() | 返回 arraylist 中元素的索引值 |
removeAll() | 删除存在于指定集合中的 arraylist 里的所有元素 |
remove() | 删除 arraylist 里的单个元素 |
size() | 返回 arraylist 里元素数量 |
isEmpty() | 判断 arraylist 是否为空 |
subList() | 截取部分 arraylist 的元素 |
set() | 替换 arraylist 中指定索引的元素 |
sort() | 对 arraylist 元素进行排序 |
toArray() | 将 arraylist 转换为数组 |
toString() | 将 arraylist 转换为字符串 |
ensureCapacity() | 设置指定容量大小的 arraylist |
lastIndexOf() | 返回指定元素在 arraylist 中最后一次出现的位置 |
retainAll() | 保留 arraylist 中在指定集合中也存在的那些元素 |
containsAll() | 查看 arraylist 是否包含指定集合中的所有元素 |
trimToSize() | 将 arraylist 中的容量调整为数组中的元素个数 |
removeRange() | 删除 arraylist 中指定索引之间存在的元素 |
replaceAll() | 将给定的操作内容替换掉数组中每一个元素 |
removeIf() | 删除所有满足特定条件的 arraylist 元素 |
forEach() | 遍历 arraylist 中每一个元素并执行特定操作 |
定义
List<Integer> list = new ArrayList<>();
赋值
for (int i = 0; i < 5; i++) {
int nums = in.nextInt();
list.add(nums);
}
遍历
遍历输出 它的大小是 size() 区别数组的 length 区别字符串的 length()
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+" ");
}
API调用之sort升序
注意o1.conpareTo(o2)
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);//这里还可以用 o1 - o2 这个是等价的
}
});
conpareTo的源码:
其实就是3目运算符
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
API调用之sort降序
** 注意o2.compareTo(o1)**
list.sort(new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);//这里还可以用 o2 - o1 这个是等价的
}
});
API调用之按照对象的某个字段排序
关于这个我们给出一个情景好记忆。
比如我们要给一个班级数学成绩排序,数学成绩相同的话,那么我们就按照名字的字典序进行排序。
输入:5
wyj 100
aaa 90
abc 100
bbb 70
ccc 30
输出: abc 100
wyj 100
aaa 90
bbb 70
ccc 30
public class textList {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
ArrayList<Student> list = new ArrayList<>();
System.out.print("请输入学上的个数:");
int studentNums = in.nextInt();
for (int i = 0; i < studentNums; i++) {
String sname = in.next();//nextLine()输入的是一行数据会包含空格 eg wyj 100
int sgrade = in.nextInt();
list.add(new Student(sname,sgrade));
}
list.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if (o1.getGrate() == o2.getGrate()) {
return o1.getName().compareTo(o2.getName());
}
return o2.getGrate()-(o1.getGrate());
}
});
for (int i = 0; i < studentNums; i++) {
System.out.println(list.get(i).toString());
}
}
}
class Student {
private String name;
private int grate;
public Student(String name, int grate) {
this.name = name;
this.grate = grate;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGrate() {
return grate;
}
public void setGrate(int grate) {
this.grate = grate;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", grate=" + grate +
'}';
}
}
LinkedList
常用方法
方法 | 描述 |
---|---|
public boolean add(E e) | 链表末尾添加元素,返回是否成功,成功为 true,失败为 false。 |
public void add(int index, E element) | 向指定位置插入元素。 |
public boolean addAll(Collection c) | 将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。 |
public boolean addAll(int index, Collection c) | 将一个集合的所有元素添加到链表的指定位置后面,返回是否成功,成功为 true,失败为 false。 |
public void addFirst(E e) | 元素添加到头部。 |
public void addLast(E e) | 元素添加到尾部。 |
public boolean offer(E e) | 向链表末尾添加元素,返回是否成功,成功为 true,失败为 false。 |
public boolean offerFirst(E e) | 头部插入元素,返回是否成功,成功为 true,失败为 false。 |
public boolean offerLast(E e) | 尾部插入元素,返回是否成功,成功为 true,失败为 false。 |
public void clear() | 清空链表。 |
public E removeFirst() | 删除并返回第一个元素。 |
public E removeLast() | 删除并返回最后一个元素。 |
public boolean remove(Object o) | 删除某一元素,返回是否成功,成功为 true,失败为 false。 |
public E remove(int index) | 删除指定位置的元素。 |
public E poll() | 删除并返回第一个元素。 |
public E remove() | 删除并返回第一个元素。 |
public boolean contains(Object o) | 判断是否含有某一元素。 |
public E get(int index) | 返回指定位置的元素。 |
public E getFirst() | 返回第一个元素。 |
public E getLast() | 返回最后一个元素。 |
public int indexOf(Object o) | 查找指定元素从前往后第一次出现的索引。 |
public int lastIndexOf(Object o) | 查找指定元素最后一次出现的索引。 |
public E peek() | 返回第一个元素。 |
public E element() | 返回第一个元素。 |
public E peekFirst() | 返回头部元素。 |
public E peekLast() | 返回尾部元素。 |
public E set(int index, E element) | 设置指定位置的元素。 |
public Object clone() | 克隆该列表。 |
public Iterator descendingIterator() | 返回倒序迭代器。 |
public int size() | 返回链表元素个数。 |
public ListIterator listIterator(int index) | 返回从指定位置开始到末尾的迭代器。 |
public Object[] toArray() | 返回一个由链表元素组成的数组。 |
public T[] toArray(T[] a) | 返回一个由链表元素转换类型而成的数组。 |
案例
package com.trs.collection;
import java.util.LinkedList;
/**
* LinkedList用法示例
* @author xiayunan
* @date 2018年7月8日
*
*/
public class LinkedListFeatures {
public static void main(String[] args) {
LinkedList<String> cityList = new LinkedList<String>();
cityList.add("合肥");
cityList.add("南京");
cityList.add("上海");
cityList.add("杭州");
cityList.add("芜湖");
cityList.add("武汉");
System.out.println(cityList);
System.out.println("cityList.getFirst():"+cityList.getFirst());
System.out.println("cityList.element():"+cityList.element());
System.out.println("cityList.peek():"+cityList.peek());
System.out.println("cityList.remove():"+cityList.remove());
System.out.println(cityList);
System.out.println("cityList.removeFirst():"+cityList.removeFirst());
System.out.println(cityList);
System.out.println("cityList.poll():"+cityList.poll());
System.out.println(cityList);
cityList.addFirst("长沙");
System.out.println("After addFirst():"+cityList);
cityList.offer("广州");
System.out.println("After offer():"+cityList);
cityList.add("深圳");
System.out.println("After add():"+cityList);
cityList.addLast("恩施");
System.out.println("After addLast():"+cityList);
System.out.println("Remove addLast():"+cityList.removeLast());
System.out.println(cityList);
}
}
运行结果为:
3:Map
HashMap 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类。
常用方法
方法 | 描述 |
---|---|
clear() | 删除 hashMap 中的所有键/值对 |
clone() | 复制一份 hashMap |
isEmpty() | 判断 hashMap 是否为空 |
size() | 计算 hashMap 中键/值对的数量 |
put() | 将键/值对添加到 hashMap 中 |
putAll() | 将所有键/值对添加到 hashMap 中 |
putIfAbsent() | 如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。 |
remove() | 删除 hashMap 中指定键 key 的映射关系 |
containsKey() | 检查 hashMap 中是否存在指定的 key 对应的映射关系。 |
containsValue() | 检查 hashMap 中是否存在指定的 value 对应的映射关系。 |
replace() | 替换 hashMap 中是指定的 key 对应的 value。 |
replaceAll() | 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。 |
get() | 获取指定 key 对应对 value |
getOrDefault() | 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值 |
forEach() | 对 hashMap 中的每个映射执行指定的操作。 |
entrySet() | 返回 hashMap 中所有映射项的集合集合视图。 |
keySet() | 返回 hashMap 中所有 key 组成的集合视图。 |
values() | 返回 hashMap 中存在的所有 value 值。 |
merge() | 添加键值对到 hashMap 中 |
compute() | 对 hashMap 中指定 key 的值进行重新计算 |
computeIfAbsent() | 对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中 |
computeIfPresent() | 对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。 |
定义
Map<String,Integer> map = new HashMap<>();
赋值
for (int i = 0; i < 3; i++) {
String name = in.next();
int grade = in.nextInt();
map.put(name,grade);
}
遍历
map提供的get方法是根据key值来获取 value 值
for (String s : map.keySet()) {
System.out.println(s + " " + map.get(s));
}
常用API之判断某个值是否存在
map.containsKey("wyj")
常用API之移除某个key
map.remove("wyj")
Entry
由于Map中存放的元素均为键值对,故每一个键值对必然存在一个映射关系。
Map中采用Entry内部类来表示一个映射项,映射项包含Key和Value (我们总说键值对键值对, 每一个键值对也就是一个Entry)
Map.Entry里面包含getKey()和getValue()方法
Iterator<Map.Entry<Integer, Integer>> it=map.entrySet().iterator();
while(it.hasNext()) {
Map.Entry<Integer,Integer> entry=it.next();
int key=entry.getKey();
int value=entry.getValue();
System.out.println(key+" "+value);
}
entrySet
entrySet是 java中 键-值 对的集合,Set里面的类型是Map.Entry,一般可以通过map.entrySet()得到。
entrySet实现了Set接口,里面存放的是键值对。一个K对应一个V。
用来遍历map的一种方法。
Set<Map.Entry<String, String>> entryseSet=map.entrySet();
for (Map.Entry<String, String> entry:entryseSet) {
System.out.println(entry.getKey()+","+entry.getValue());
}
4:Set
HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
HashSet 中的元素实际上是对象,一些常见的基本类型可以使用它的包装类。
定义
HashSet<Integer> set = new HashSet<>();
赋值
for (int i = 0; i < 3; i++) {
int nums = in.nextInt();
set.add(nums);
}
遍历
这里的遍历的话 是可以去重的和升序的
for (int nums:set) {
System.out.println(nums);
}
常用API之判断某个元素是否存在
set.contains(元素)
常用API之移除某个key
set.remove(1)
常用API之清空容器
set.clear();
5.栈
//这是一个双端队列
ArrayDeque<Character> deque = new ArrayDeque<>();
//在栈中push就是往容器尾部插入一个值
deque.push('a');
deque.push('b');
deque.push('c');
deque.push('d');
//访问栈顶元素
System.out.println(deque.peek());
//删除一个元素
deque.pop();
System.out.println(deque.peek());
6:队列
//这是一个双端队列
ArrayDeque<Integer> deque = new ArrayDeque<>();
//用add的话表示就是取元素顺序时候就是先进先出
deque.add(1);
deque.add(2);
deque.add(3);
//我们用 peek()进行访问 访问的是队列首部元素
System.out.println(deque.peek());
//我们用 pool()表示的就是移除队列首部元素
deque.poll();
System.out.println(deque.peek());
//访问队尾元素
System.out.println(deque.getLast());
//移除队尾元素
deque.removeLast();
System.out.println(deque.getLast());
3.3 迭代器
常用方法
返回值类型 | 方法名 | 功能 |
---|---|---|
boolean | hasNext() | 判断集合是否还有元素,如果返回 true 表示集合还有元素,返回 false 表示集合中没有元素;一般对集合的访问通过 while(hasNext()) 判断是否还需要遍历。 |
E | next() | 获取集合中遍历的当前元素 ;一般先调用 hasNext() 方法判断是否存在元素,再调用 next() 获取元素,需要进行循环交替遍历集合中的元素。 |
void | remove() | 删除集合中的元素。 |
使用
Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。
通过使用迭代器,我们可以逐个访问集合中的元素,而不需要使用传统的 for 循环或索引。这种方式更加简洁和灵活,并且适用于各种类型的集合。
获取一个迭代器
集合想获取一个迭代器可以使用 iterator() 方法:
实例
// 引入 ArrayList 和 Iterator 类
import java.util.ArrayList;
import java.util.Iterator;
public class RunoobTest {
public static void main(String[] args) {
// 创建集合
ArrayList<String> sites = new ArrayList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
sites.add("Zhihu");
// 获取迭代器
Iterator<String> it = sites.iterator();
// 输出集合中的第一个元素
System.out.println(it.next());
}
}
执行以上代码,输出结果如下:
Google
使用迭代器遍历集合时,如果在遍历过程中对集合进行了修改(例如添加或删除元素),可能会导致 ConcurrentModificationException 异常,为了避免这个问题,可以使用迭代器自身的 remove() 方法进行删除操作。
循环集合元素
让迭代器 it 逐个返回集合中所有元素最简单的方法是使用 while 循环:
while(it.hasNext()) {
System.out.println(it.next());
}
以下输出集合 sites 中的所有元素:
实例
// 引入 ArrayList 和 Iterator 类
import java.util.ArrayList;
import java.util.Iterator;
public class RunoobTest {
public static void main(String[] args) {
// 创建集合
ArrayList<String> sites = new ArrayList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
sites.add("Zhihu");
// 获取迭代器
Iterator<String> it = sites.iterator();
// 输出集合中的所有元素
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
执行以上代码,输出结果如下:
Google
Runoob
Taobao
Zhihu
删除元素
要删除集合中的元素可以使用 remove() 方法。
以下实例我们删除集合中小于 10 的元素:
实例
// 引入 ArrayList 和 Iterator 类
import java.util.ArrayList;
import java.util.Iterator;
public class RunoobTest {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(12);
numbers.add(8);
numbers.add(2);
numbers.add(23);
Iterator<Integer> it = numbers.iterator();
while(it.hasNext()) {
Integer i = it.next();
if(i < 10) {
it.remove(); // 删除小于 10 的元素
}
}
System.out.println(numbers);
}
}
执行以上代码,输出结果如下:
[12, 23]
注意:Java 迭代器是一种单向遍历机制,即只能从前往后遍历集合中的元素,不能往回遍历。同时,在使用迭代器遍历集合时,不能直接修改集合中的元素,而是需要使用迭代器的 remove() 方法来删除当前元素。
3.4 数据结构原理及适用场景
具体请参考:https://www.runoob.com/java/java-collections.html
四、stream.filter collection【原理和进阶操作未完待续…】
Java8及以上版本中,使用stream().filter()来过滤一个List对象,查找符合条件的对象集合。
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class TestStreamFilter {
public static void sorted() {
List<Integer> list = Arrays.asList(5, 4, 3, 2, 7, 1, 6, 9, 8);
//过滤出大于5的数值
List<Integer> filterList = list.stream().filter(x -> x > 5).collect(Collectors.toList());
System.out.println("原始数组:" + list);
System.out.println("过滤数组:" + filterList);
System.out.println();
List<Person> personList = new ArrayList<>();
personList.add(new Person("张三", 16, 181));
personList.add(new Person("李四", 17, 180));
personList.add(new Person("王五", 16, 179));
personList.add(new Person("张明", 18, 180));
personList.add(new Person("李威", 16, 190));
personList.add(new Person("王鹏", 15, 188));
personList.add(new Person("王莉", 17, 177));
personList.add(new Person("王鸥", 19, 180));
//年龄18岁及以上,身高180的人
List<Person> filterPList = personList.stream().filter(s -> s.getAge() >= 18 && s.getHeight() == 180).collect(Collectors.toList());
printPerson(filterPList);
}
public static void printPerson(List<Person> userList) {
System.out.println("【姓名】\t【年龄】\t【身高】");
System.out.println("-----------------------");
userList.forEach(s -> System.out.println(s.toString()));
System.out.println(" ");
}
public static void main(String[] args) {
sorted();
}
}
@Data
class Person {
private String name;
private Integer age;
private Integer height;
public Person(String name, Integer age, Integer height) {
this.name = name;
this.age = age;
this.height = height;
}
public String toString() {
String info = String.format("%s\t\t%s\t\t%s\t\t\t", this.name, this.age.toString(), this.height.toString());
return info;
}
}
对应输出:
原始数组:[5, 4, 3, 2, 7, 1, 6, 9, 8]
过滤数组:[7, 6, 9, 8]
【姓名】 【年龄】 【身高】
-----------------------
张明 18 180
王鸥 19 180
五、Gson等java转换小技巧
5.1 Gson Google 英文官网
5.2 json转换对象方法
Definitely the easiest way to do that is using Gson’s default parsing function fromJson().
There is an implementation of this function suitable for when you need to deserialize into any ParameterizedType (e.g., any List), which is fromJson(JsonElement json, Type typeOfT).
In your case, you just need to get the Type of a List and then parse the JSON array into that Type, like this:
import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;
JsonElement yourJson = mapping.get("servers");
Type listType = new TypeToken<List<String>>() {}.getType();
List<String> yourList = new Gson().fromJson(yourJson, listType);
In your case yourJson is a JsonElement, but it could also be a String, any Reader or a JsonReader.
You may want to take a look at Gson API documentation.
六、JAVA输入(刷题使用)
1: 常规的输入
Scanner in = new Scanner(System.in);
System.out.println("请输入你的年龄:");
int age = in.nextInt();//整数类型的输入方式
System.out.println("请输入你的身高:");
double height = in.nextDouble();//小数类型的输入方式
2:关于其他输入符在nextLine()之前用吃掉回车符的问题解决
当比如我们在输入的是时候前面用了 nextInt();后面的话用了nextLine()的话,其会吃掉回车符,导致我们无法正常输入
那么我们的解决办法有两种
将nextLine()改为next(),但是呢那么你就无法输入带空格的字符了
将nextLine()的前面再加上nextLine(),这样就可以帮助我们吃掉回车符,那么我们正常的输入就不会有问题了。
请看示例:
Scanner in = new Scanner(System.in);
System.out.println("请输入你的年龄:");
int age = in.nextInt();//整数类型的输入方式
System.out.println("请输入苹果的英文:");
in.nextLine();//nextLine()会吃掉回车符,这样的话 我们下面就可以正常输入了
String s1 = in.nextLine();//nextLine是可以输入带空格的数据的。
System.out.println("请输入你的身高:");
double height = in.nextDouble();//小数类型的输入方式
3: 常见输入之我们输入一串数到容器中
这个是我们常用的之一,当题目给出一串数的话,我们往往是要将其存放在容器当中的,那么的话我们就可以方便操作了
Scanner in = new Scanner(System.in);
int[] arr = new int[10];
for (int i = 0; i < 10; i++) {
arr[i] = in.nextInt();
}
for (int i : arr) {
System.out.print(i+ " ");
}