数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其它类型的数据。
1.2 集合有什么用?集合为什么说在开发使用较多?
集合是一个容器,是一个载体,可以一次容纳多个对象。在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条数据查询出来,在java程序中会将10条数据封装成10个java对象(new出来的对象),然后将10个对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。
集合不能直接存储基本数据类型,另外集合也不是直接存储java对象。集合当中存休的都是java对象的内存地址,(或者说集合中存储的是引用)。
list.add(100);//自动装箱Integer注意:
- 集合在java中本身也是一个容器,是一个对象。集合中任何时候存储的都是"引用"。
1.4 不同的集合对应不同的数据结构集合可以存储集合,集合存储的是对象的地址值
在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等同于将数据放到不同的数据结构当中。什么是数据结构?数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。
例如:数组、二叉树、链表、哈希表…。这些都是常见的数据结构。
你往集合c1中放数据。可能是放到了数组上了。
你往集合c2中放数据,可能是放到了二叉树上了。
new ArrayList(); 创建一个集合对象,底层是数据。
new TreeSet(); 创建一个集合对象,底层是二叉树。
在java中集合分为两大类:
一类是单个方式存储元素:
单个方式存储元素,这一类集合中的超级父接口:java.util.Collection;
一类是以键值对的方式存储元素:
以键值对的方式存储元素,这一类集合中的超级父接口:java.util.Map
集合继承结构图
List接口的实现类
ArrayList集合
底层采用了数组这种数据结构。Array集合是非线程安全的。
linkedList集合
底层采用了双向链表的数据结构。
Vector集合
底层采用了数组这种数据结构。Vector集合是线程安全的。Vector所有的方法都是有synchronized关键字 修饰,所以线程安全,但是效率低,现在有别的方案,所以Vector使用较少了。
Set接口的实现类
HashSet集合
实际上HashSet集合在new的时候,底层实际上new了一个HashMap集合。想HashSet集合中存储元素,实际上是存储到HashMap集合中了。HashMap集合是一个哈希表数据结构。
SortedSet接口继承Set接口,实现类是TreeSet
SortedSet集合存储元素的特点:由于继承了Set集合,所以它的特点是无序不可重复(没有下标),但是放在SortedSet集合中元素可以自动排序。我们称为可排序集合,放到该集合中的元素是自动按照大小顺序排序的。
TreeSet集合底层实际上是TreeMap,new TreeSet集合的时候,底层实际上new了一个TreeMap集合。往TreeSet集合中存储数据的时候,实际上是往TreeMap集合中存储数据。TreeMap集合底层采用了二叉树的数据结构。
1.6 Map集合继承结构图总结(所有的实现类):
ArrayList:底层是数组。linkedList:底层是双向链表。Vector:底层是数组,线程安全的,效率低,使用较少。HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到了HashMap集合key部分了。TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到了TreeMap集合key部分了。HashMap:底层是哈希表。Hashtable:底层是哈希表,线程安全,效率低,使用较少。Properties:是线程安全的,并且key和value只能存户字符串String。TreeMap:底层是二叉树。TreeMap集合的key可以自定安装大小顺序排序。
List集合存储元素的特点:
有序可重复的
有序:存储进去的顺序和取出的顺序是一样的,List集合中的元素有下标,怎么进去就怎么出来。重复:存进去1,可以在存储一个1。
Set(Map)集合存储元素的特点:
无序不可重复
无序:存进去的顺序和取出来的顺序不一定相同,Set集合红的元素没有下标。不可重复:存进去1,就不可在存储1了。
SortedSet(SortedMap)集合存储元素的特点:
无序不可重复
无序:存进去的顺序和取出来的顺序不一定相同,Set集合红的元素没有下标。不可重复:存进去1,就不可在存储1了。
可排序
排序:可以按照大小顺序排列。
Map集合的key,就是以Set集合。往Set集合中方数据,实际上放到了Map集合的key部分。
关于Collection的常用方法
package com.ccy.java.List.Collection; import java.util.ArrayList; import java.util.Collection; public class Collection01 { public static void main(String[] args) { //创建一个集合对象 //Collection collection = new Collection();//接口是抽象的,无法实例化 //多态 Collection al = new ArrayList(); //测试Collection接口中的常用方法 al.add(1200);//自定装箱(java5的新特性)实际上是放进去了一个对象的内存地址。Integer x = new Integer(1200); al.add(new Object()); al.add(true);//自动装箱 //获取集合红元素的个数 System.out.println("集合中元素的个数:"+al.size()); //3 //清空集合中的元素 System.out.println("清空集合中的元素,结果如下显示:"); al.clear(); System.out.println("集合中元素的个数:"+al.size()); //0 //再向集合中添加元素 al.add("nihao");//“nihao”对象的内存地址放到了集合中 al.add("word"); System.out.println("集合中元素的个数:"+al.size()); //2 //判断集合中是否包含指定的元素 boolean word = al.contains("word"); boolean word2 = al.contains("word2"); System.out.println(word);//true System.out.println(word2);//false //删除集合中的某个元素 System.out.println("删除集合中的某个元素,结果如下:"); al.remove("word"); System.out.println("集合中元素的个数:"+al.size()); //1 //判断集合是否为空 boolean empty = al.isEmpty(); System.out.println(empty);//false //将集合转换成数组(了解) System.out.println("遍历数组,结果如下:"); al.add(100); al.add("abcd"); Object[] objects = al.toArray(); for(int i = 0;i结果显示
集合中元素的个数:3 清空集合中的元素,结果如下显示: 集合中元素的个数:0 集合中元素的个数:2 true false 删除集合中的某个元素,结果如下: 集合中元素的个数:1 false 遍历数组,结果如下: nihao 100 abcd2.2 Iterator关于集合的迭代/遍历
package com.ccy.java.List.Collection; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; public class Collection02 { public static void main(String[] args) { //以下遍历/迭代的方式,是所有Collection通用的一种方式(包括子类),但在Map集合中不能使用 //创建一个ArrayList集合对象 System.out.println("ArrayList集合:"); Collection c = new ArrayList(); //向集合中添加元素 c.add(100); c.add("hello"); c.add("nihao"); c.add(new Student()); //第一步:获取集合对象的迭代器对象Iterator Iterator it = c.iterator(); //取出集合中的数据,返回的Object对象 while (it.hasNext()){ //存进去什么类型,取出来还是什么类型 Object next = it.next(); //只是在打印时,prinlt会将其他类型转换成字符串类型打印输出 System.out.println(next); } System.out.println(); System.out.println("HashSet集合:"); //创建一个HashSet集合对象 Collection hs = new HashSet();//无序不可重复 //向HashSet集合中添加元素 hs.add(10); hs.add(30); hs.add(1); hs.add(100); hs.add(10); //获取迭代器 Iterator i = hs.iterator(); //迭代数据 while(i.hasNext()){ System.out.println(i.next()); } } } class Student{ }显示结果
ArrayList集合: 100 hello nihao com.ccy.java.List.Collection.Student@4554617c HashSet集合: 1 100 10 302.3 contains原理package com.ccy.java.List.Collection; import java.util.ArrayList; import java.util.Collection; public class Contains03 { public static void main(String[] args) { Collection c = new ArrayList(); String str1 = new String("abc"); c.add(str1); String str2 = new String("ccy"); c.add(str1); String str3 = new String("abc"); //str3没有添加到集合中 //c.contains(str3)在底层是这么写的:str3.equals(str1) System.out.println(c.contains(str3));//true } }2.4 contains和remove原理测试测试contains方法
结论:存放在一个集合中的类型,一定要重写equals方法,如果不是自己new出来的对象就不用重写equals方法
package com.ccy.java.List.Collection; import java.util.ArrayList; import java.util.Collection; public class contains04 { public static void main(String[] args) { //创建ArrayList集合对象 Collection c = new ArrayList(); //实例化User对象 User ccy1 = new User("ccy"); //向集合中添加元素 c.add(ccy1); //实例化User对象 User ccy2 = new User("ccy"); //自己new出来的对象,没有重写equals方法,则会使用Object中的equals方法,比较的是地址值 //System.out.println(c.contains(ccy2));//false //User类重写了equals方法,比较的是内容值 System.out.println(c.contains(ccy2));//true //删除ccy2在集合中的元素 c.remove(ccy2);//使用remove方法,底层会调用equals方法,删除ccy2就相当于删除了ccy1 System.out.println(c.size());//0 } } class User{ private String name; public User() { } public User(String name) { this.name = name; } //重写equals方法 //如果调用了当前类,也会调用equals方法,但是一定调用的是重写过的方法 //这个equals方法的比较原理:只要内容一样,则返回true @Override public boolean equals(Object o){ //判断集合中为空或者是传进来的o对象不是当前的类。则返回false if(o == null || !(o instanceof User)) return false; //判断传进来的o对象的地址值和当前类的地址值是否是同一个地址值,是则返回true if(o == this) return true; //将传进来的o对象强转为当前类 User u = (User)o; //重写过的equals的方法,比较的是内容,而不是比较地址值了 return u.name == this.name; } }关于remove方法
当集合的结构发生改变时,迭代器必须重新获取,如果不刷新则会出现异常:java.util.ConcurrentModificationException
在迭代集合元素的过程中,不能调用集合对象的remove方法删除元素(c.remove()),因为使用了会报错:java.util.ConcurrentModificationException
在迭代的过程中,一定要使用迭代器Iterator的remove方法删除元素,不要使用集合自带的remove方法删除元素,会报错:java.util.ConcurrentModificationException
3.List接口 3.1 和list接口特有的方法void add(int index, E element) 将指定元素插入此列表中的指定位置(可选 *** 作)。
E get(int index) 返回此列表中指定位置的元素,也可是遍历是的一种方式。
int indexOf(Object o) 返回此列表中第一次出现的指定元素的索引,如果此列表不包含该元素,则返回-1。
int lastIndexOf(Object o) 返回此列表中指定元素最后一次出现的索引,如果此列表不包含该元素,则返回-1。
E remove(int index) 删除此列表中指定位置的元素(可选 *** 作)。
E set(int index, E element) 用指定的元素替换此列表中指定位置的元素(可选 *** 作)。
3.2 ArrayList源码分析3.3 linkedList集合
默认初始化容量10(底层先创建一个长度为0的数组,当添加一个元素的时候,初始化才会变为10)
集合底层是一个Object[]数组
构造方法
new ArrayList(); 默认容量是10new ArrayList(20);new ArrayList(); 参数可以是一个集合
//默认容量是10 List list1 = new ArrayList(); //容量初始化20 List list2 = new ArrayList(20); //构造方法的参数可以是一个集合 Collection ha = new HashSet(); ha.add("nihao"); List list3 = new ArrayList(ha); for (int i = 0; i < ha.size(); i++) { System.out.println(list.get(i));//nihao }ArrayList集合的扩容
增长到原容量的1.5倍
ArrayList集合底层是数组,怎么优化?
尽可能减少扩容,因为数组扩容了效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给 定一个初始化容量
数组的优点:检索效率比较高(每个元素占位空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算元素的内存地址,所有检索效率高)
数组的缺点:
随机增删元素效率低数组无法存储大数据容量,很难找到一块巨大的连续的内存空间
向数组末尾添加元素,效率效率还是挺高的
单向链表结构图
单向链表的基本单位是节点,节点有两个属:数据、下一个节点对象的内存地址
链表的优点:
由于链表上的元素的内存地址是不连续的,所以随机增删的时候不会有大量高的元素位移,因此增删效率较高,以后在开发中,如果遇到增删集合中的元素,就可以使用linkedList集合。
链表的缺点:
不能通过数学表达式计算出被查找元素的内存地址,每一次查找都是从头节点一个一个的遍历,直到找到为止,所有linkedList集合的检索/查找的效率不高。
注意:
ArrayList集合的检索效率高,不是因为他有下标,而是他底层是一个数组
linkedList集合照样有下标,但是他底层是双向链表,只能从头节点一个一个遍历双向链表结构图
linkedList集合内存结构图(通过源码分析而来)
、
注意:
//linkedList集合有初始化容量吗?没有 //最初这个链表中没有任何元素,first和last引用都是null //不管是ArrayList集合还是linkedList集合,以后写代码时不需要关心具体是哪个集合的,因为我们面向的是接口编程,调用的都是接口中的方法 List list = new ArrayList();//这样写表示底层是数组 List link = new linkedList();//这样写表示底层是双向链表3.4 VectorVector底层是一个数组初始化容量是10扩展之后是原来的2倍 10—>20---->40…Vector是线程安全的,用的比较少java.utils.Collections是一个工具类,可以将非线程安全的转换成线程安全的,Collections.synchronizedList(传非线程的集合对象);
3.5 迭代的几种方式package com.ccy.java.List.Collection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Generic03 { public static void main(String[] args) { List4.Set接口 5.Map接口 6.Collection工具类 7.泛型(Generic) 7.1 初步了解泛型list = new ArrayList<>(); list.add("nihao"); list.add("hello"); list.add("123"); //迭代器 Iterator i = list.iterator(); while (i.hasNext()){ String next = i.next(); System.out.println(next); } //for循环 for (int j = 0; j < list.size(); j++) { String s = list.get(j);//get(index); 是List接口中的特有的方法 System.out.println(s); } //foreach //foreach的缺点:就是没有下标,全部都遍历出来 for (String s : list) { System.out.println(s); } } } JDK5.0之后推出的新特性:泛型泛型这种语法机制,只在编译阶段起作用,只是给编译器参考的。(运行阶段时没用)使用泛型有什么好处?
集合中存储的元素同一了。(被约束了)从集合中取出的数据类型是泛型指定的类型,不需要大量的向下转型 使用泛型的缺点?
导致集合中存储的元素没有多样化
package com.ccy.java.List.Collection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Generic01 { public static void main(String[] args) { //创建ArrayList集合对象 List list = new ArrayList(); //实例化对象 Cat cat = new Cat(); Bird bird = new Bird(); //向集合中添加数据 list.add(cat); list.add(bird); //迭代数据 //使用了泛型,只能存储指定的泛型类型的数据,存储其他的数据类型的就会报错 Iterator i = list.iterator(); while (i.hasNext()){ Animal next = i.next(); next.move(); //判断是否属于Cat类型的,然后进行强转 if(next instanceof Cat){ ((Cat) next).run(); } //判断是否属于Bird类型的,然后进行强转 if(next instanceof Bird){ ((Bird) next).fly(); } } } } class Animal{ public void move(){ System.out.println("动物在移动!"); } } class Cat extends Animal{ public void run(){ System.out.println("猫在运动!"); } } class Bird extends Animal{ public void fly(){ System.out.println("鸟儿在飞!"); } }显示结果
动物在移动! 猫在运动! 动物在移动! 鸟儿在飞!7.2 砖石表达式JDK之后引入了:自动类型推动机制(又称为砖石表达式)
package com.ccy.java.List.Collection; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Generic02 { public static void main(String[] args) { //ArrayList<>(这里的类型会自动推断);后面的泛型可以不用写,不会报错,前提是JDK8.0 Listlist = new ArrayList<>(); //只能添加String类型的数据 list.add("https://www.baidu.com"); list.add("https://www.bilibili.com"); list.add("https://www.132.com"); //迭代器使用了泛型,只能调用String类型的中的方法,调用其他类型中的方法会报错 Iterator i = list.iterator(); while (i.hasNext()){ String s = i.next(); //截取字符串 String substring = s.substring(8); System.out.println(substring); } } } 显示结果
www.baidu.com www.bilibili.com www.132.com7.3 自定义泛型package com.ccy.java.List.Collection; public class Generic03{ public void go(E o){ System.out.println(o); } public static void main(String[] args) { //new对象的时候泛型指定的是String类型 Generic03 g = new Generic03<>(); g.go("hello");//hello //类型不匹配,需要的类型是字符串不是数字 //g.go(100); } } 欢迎分享,转载请注明来源:内存溢出
评论列表(0条)