本笔记基于黑马程序员java教程整理,仅供参考
目录
1.认识集合
集合类似于数组,但是集合相较于数组更有优势,因为集合可以自适应大小,大小可变,但是数组大小固定
集合分为两大类:Collection(单列集合)和Map(双列集合)

2.Collection(单列集合)
Collection<E>是一个接口,包含两个分支接口List和Set,它们是继承关系,其它下面的都是各自的实现类

Collection接口常用功能:
增加数据:add
删单个数据:remove 删全部数据:clear
返回集合中元素个数: size
判断集合是否为空:isEmpty
判断集合中是否有给定的对象:contains(Object o)//Object代表任意类型
把集合转化为数组:toArray
public class Demo {
public static void main(String[] args) {
Collection<String> list = new ArrayList<>(); //多态
//add方法,添加元素
list.add("hello");
list.add("world");
list.add("小明");
list.add("hello");
System.out.println(list); //[hello, world, 小明, hello] 有序,可重复
//size方法,获取集合长度
System.out.println(list.size());
//删除元素
list.remove("hello");
//判断集合是否为空
System.out.println(list.isEmpty()); //返回值是boolean类型,true为空,false不为空
//判断集合中是否包含某个元素
System.out.println(list.contains("world"));
//清空集合
list.clear();
//把集合转换为数组
Object[] arr = list.toArray();
System.out.println(Arrays.toString(arr)); //Arrays.toString()方法,把数组直接打印输出,不用再写for循环了
}
}
集合的三种遍历方式:
public class Demo {
public static void main(String[] args) {
Collection<String> list = new ArrayList<>(); //多态
//add方法,添加元素
list.add("hello");
list.add("world");
list.add("小明");
list.add("hello");
//遍历方式一:迭代器遍历集合
Iterator<String> it = list.iterator(); //it指向集合的第一个元素
while (it.hasNext()) { //判断集合中是否还有元素,即it指的位置是否有元素,如果有返回true,如果没有返回false
String s = it.next(); //先获取集合中的元素再移动指针到下一个元素
System.out.println(s);
}
//遍历方式二:增强for循环遍历集合
for (String s : list) { //创建一个String类型的变量s,并把list集合中的元素赋值给s
System.out.println(s);
}
//遍历方式三:lambda 表达式
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//简化:
list.forEach(s -> System.out.println(s));
}
}
三种遍历的区别:
首先我们需要认识并发修改异常问题:在遍历集合的同时进行增删操作时可能出现异常
public class Demo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(); //因为ArrayList有索引,所以可以用get方法获取元素,之后会讲
//add方法,添加元素
list.add("hello");
list.add("world");
list.add("wava");
list.add("小明");
list.add("hello");
//我现在有一个需求,要求删除集合中包含w的元素
// for (int i = 0; i < list.size(); i++) {
// if (list.get(i).contains("w")) {
// list.remove(i);
// }
// }
// for (String s : list) {
// System.out.println(s);
// }
// //输出结果为:hello wava 小明 hello,我们发现wava并没有被删除,这是为什么呢?
// //因为当集合中有多个元素时,如果删除一个元素,那么删除的元素后面的元素都会向前移动一位,
// //所以当删除了world时,world后面的元素被向前移动了,所以world被删除了,world之前的位置被wava占据了
// // 即所有的元素都会向前移动一位,但是下标不会改变,所以下一次循环wava就相当于被跳过了
//解决方法
for (int i = 0; i < list.size(); i++) {
if (list.get(i).contains("w")) {
list.remove(i);
i--; //让下标前移一位
}
}
}
}
我们发现,要想解决这个异常我们需要有索引来获取元素判断
那么如Collection等没有索引的集合怎么办呢
结论:迭代器循环可以用迭代器本身的方法解决,但是增强for和lambda都没办法解决
迭代器循环解决方案如下:
public class Demo {
public static void main(String[] args) {
Collection<String> list = new ArrayList<>(); //多态
//add方法,添加元素
list.add("hello");
list.add("world");
list.add("wava");
list.add("小明");
list.add("hello");
//我现在有一个需求,要求删除集合中包含w的元素
//创建一个迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.contains("w")) {
// list.remove(s); //会报并发修改异常
it.remove(); //调用迭代器方法,相当于删除并且i--
}
}
for (String s : list) {
System.out.println(s);
}
}
}
2.1 List
ArrayList和LinkedList
特点:两个实现类添加的元素都有序,可重复,有索引
因为有索引,所有会多出一些与索引相关的功能
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<>(); //多态
//add方法,添加元素
list.add("hello");
list.add("world");
list.add("wava");
list.add("小明");
list.add("hello");
//根据索引在集合中插入元素
list.add(2,"java");
//根据索引获取集合中的元素
String s = list.get(2);
//根据索引修改集合中的元素
list.set(2,"javaee");
//根据索引删除集合中的元素
list.remove(2);
//for 循环遍历集合
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
ArrayList和LinkedList的区别:
ArrayList基于数组存储数据
优点:根据索引查询快
缺点:增删数据效率低,因为需要扩容或前移
LinkedList基于双链表存储数据
优点:增删数据效率高,尤其是首尾操作,给定了方法
public class Demo {
public static void main(String[] args) {
List<String> list = new LinkedList<>(); //多态
//add方法,添加元素
list.add("hello");
list.add("world");
list.add("wava");
list.add("小明");
list.add("hello");
//增加元素到第一个
list.addFirst("java");
//增加元素到最后一个
list.addLast("java");
//获取第一个元素
System.out.println(list.getFirst());
//获取最后一个元素
System.out.println(list.getLast());
//删除第一个元素
list.removeFirst();
//删除最后一个元素
list.removeLast();
}
}
可以通过这个特点来实现队列和栈
队列:先进先出,如排队
栈:先进后出,如瓶子里放东西
public class Demo {
public static void main(String[] args) {
//创建队列
LinkedList<String> queue = new LinkedList<>();
//入队
queue.addLast("张三");
queue.addLast("李四");
queue.addLast("王五");
//出队
String first = queue.removeFirst();
System.out.println(first);
}
}
//栈的创建就类比队列即可
缺点:查询慢,每次都必须从头开始查
2.2 Set
HasSet(主要)
特点:添加的元素无序(输入输出不一定顺序一致),不重复,无索引
LinkedHasSet(了解)
特点:添加的元素有序,不重复,无索引
TreeSet(了解)
特点:排序(默认会将集合内的数据大小升序排序打印),不重复,无索引
HasSet(无索引,没有与索引相关的功能)
public class demo {
public static void main(String[] args) {
Set<String> set = new HashSet<String>(); // 创建集合
set.add("张三");
set.add("李四");
set.add("java");
set.add("ak47");
set.add("java");
System.out.println(set); //打印的结果无序,并且不重复,有可能输出:[ak47, java, 张三, 李四]
}
}
HasSet底层原理:
哈希值:可以理解成一个int类型的值,每个java对象都有一个哈希值
同一个对象的哈希值是固定的,但不同对象的哈希值大概率不相等,但是也有可能相等,因为哈希值是有范围的(int类型),当范围超了int类型后,后续定义的对象的哈希值就会与之前的对象相等
通过对象.hashcode()方法获取对象的哈希值
HashSet基于哈希表存储数据:
哈希表是一种增删改查性能都很好的数据结构
JDK8之前:哈希表=数组+链表
JDK8之后:哈希表=数组+链表+红黑树
原理:当哈希表第一次加入数据时,会自动创建一个长度为16的数组,默认加载因子为0.75
然后获取对象的哈希值,通过哈希值与数组长度进行求余运算(不准确,可以这样理解),求余的结果确定可以将数据存在数组的哪个位置,当然,在存入之前,会先判断数组的这个位置是否为空,如果是空,就正常存入,如果已经有数据存在了这个位置,会先通过equals方法判断我现在要存入的数据是否与之前的数据内容相同(HasSet不重复),如果相同就不存,不同的话(JDK8之前)就新元素占老元素的位置,老元素通过链表链接在新元素后面,(JDK8之后)新元素直接链接在老元素后面。
但是这样会存在问题,就是数组只有16位,但是当16位上都存了数据并且还要继续存数据时每个位置上的链表的长度可能会一直增加,定位数据的话性能就会降低,因此引入了数组扩容的概念,即当存入的数据超过了数组长度*加载因子(即16*0.75=12)时,数组长度就会扩容为原来的两倍。
然而,这样还有一点问题,就是如果所有存入的数据都存在了数组的一个位置,以链表的形式链接,这样在查数据时也会导致性能下降,为了解决这个问题,我们引入了红黑树的概念,JDK8开始,当某个链表长度超过8,并且数组长度扩容超过64时,自动将链表转换成红黑树

但是二叉查找树也可能出现如下问题:还是需要全部查找才能锁定目标数据

因此我们采用平衡二叉树的方法加以改进

而红黑树,就是可以自平衡的二叉树

3.Map(双列集合)
数据以键值对形式存在
使用场景:需要存一 一对应的数据时可以使用Map集合

Map<K,V>是接口,K是键,V是值,键不能重复,值可以重复
下面的是实现接口的实现类,HasMap重点
3.1HasMap
特点:无序,不重复,无索引
与HasSet相似,只是多了一个附加的值
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>(); // 创建集合,K,V类型为String,Integer
// 添加元素
map.put("张三", 18);
map.put("李四", 20);
//根据键删除元素
map.remove("张三");
//根据键修改元素
map.put("李四", 30);
//根据键查找元素
System.out.println(map.get("李四"));
//获取集合大小
System.out.println(map.size());
//判断是否包含某个键
System.out.println(map.containsKey("李四"));
//判断是否包含某个值
System.out.println(map.containsValue(30));
//获取全部键的集合,即将所有键放到一个集合中,这个集合的类型是Set
Set<String> keys = map.keySet(); //不需要强制转换,因为keySet()方法返回的是Set类型
//获取Map集合的全部值
Collection<Integer> values = (Set<Integer>) map.values(); //强制转换的原因是values()方法返回的是Collection类型,因为值是可以重复的
//清空集合
map.clear();
}
}
遍历集合:
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>(); // 创建集合,K,V类型为String,Integer
// 添加元素
map.put("张三", 18);
map.put("李四", 20);
map.put("王五", 22);
map.put("赵六", 24);
map.put("赵六", 26);
//遍历集合方式一:先获取键集合,再通过键集合获取值
Set<String> keySet = map.keySet();
for (String key : keySet) {
Integer value = map.get(key);
System.out.println(key + "=" + value);
}
//遍历集合方式二:通过键值对获取键和值,难度高
Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); //entrySet方法将键值对包装成一个整体给Set集合
for (Map.Entry<String, Integer> entry : entrySet) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + "=" + value);
}
// 遍历集合方式三:通过lambda遍历,非常简单
//简化前
// map.forEach(new BiConsumer<String, Integer>() { //BiConsumer是jdk8新特性,是一个接口,我们可以实现这个接口,然后重写方法
// @Override
// public void accept(String key, Integer value) {
// System.out.println(key + "=" + value);
// }
// });
//简化后
map.forEach((key,value)->{ //forEach方法遍历集合,lambda表达式获取键和值
System.out.println(key + "=" + value);
});
}
}

1029

被折叠的 条评论
为什么被折叠?



