集合
文章目录
- 集合
- 一、集合的概念
- 二、Collection父接口
- 三、List子接口及其实现类
- 3.1 ArrayList`[重点]`
- 3.2 Vector
- 3.3 LinkedList
- 四、泛型
- 五、Collections工具类
- 六、Map接口及其实现类
- 6.1 HashMap`[重点]`
- 6.2 Hashtable
- 6.3 Properties
- 6.4 有序、无序和排序
- 6.4 TreeMap
- 七、Set接口及其实现类
- 7.1 HashSet`[重点]`
- 7.2 LinkedHashSet
- 7.3 TreeSet
一、集合的概念
集合是指存储多个对象,比数组结构复杂,功能更加强大的结构。
二、Collection父接口
具备有基本的对集合元素增删改查的方法。
三、List子接口及其实现类
List子接口具备有以下特点:
有序
有下标
元素可以重复
3.1 ArrayList[重点]
ArrayList是List接口最常用的一个实现类。特点是底层以数组的方式实现。
特点是:可以使用下标访问,遍历速度快,添加,删除元素性能较差,需要扩容,复制。
非线程安全
简单使用:
public static void main(String[] args) {
ArrayList list = new ArrayList<>(); // 创建一个集合
list.add("hello"); // 添加元素
list.add(2); // 实际添加的是包装类的对象,而不是int,因为集合不能添加基本数据类型
Student stu = new Student("1", "张三", 20);
list.add(stu);
Integer in = (Integer)list.get(1); // 根据下标获取数据
System.out.println(in);
System.out.println(list.get(2));
System.out.println("集合元素个数:" + list.size());
// 删除元素
list.remove(0); // 根据下标删除
System.out.println("集合元素个数:" + list.size());
// 直接覆盖某个下标上的元素
list.set(1, 3);
// 遍历元素
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 判断集合是否为空
System.out.println(list.isEmpty());
// 判断集合是否包含元素
System.out.println(list.contains(3));
// 创建集合
ArrayList list1 = new ArrayList<>();
// 创建一个新的对象,此对象与上面的stu内容一致
Student stu1 = new Student("1", "张三", 20);
list1.add(stu1); // 添加元素stu1
//判断集合是否包含元素stu,通过该对象的equals方法判断
System.out.println("是否包含stu:" + list1.contains(stu));
// 清空集合
list1.clear();
// 通过截取一个集合的部分元素形成一个新的集合
List list2 = list.subList(0, 1); // 截取0号元素到1号元素
for (int i = 0; i < list2.size(); i++) {
System.out.println(list2.get(i));
}
}
原理部分:
1、创建对象时,创建长度为多少的数组?
如果通过无参构造方法创建,则创建长度为0的数组,如果通过有参构造方法,int类型的参数的大小即为创建数组的长度。
transient Object[] elementData; // ArrayList中存放对象的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 空数组
private static final Object[] EMPTY_ELEMENTDATA = {}; // 空数组
// 无参构造方法,指定当前数组为空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 有参构造方法指定当前数组长度为传入的长度
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
2、无参构造创建的空数组,第一次添加元素,会如何扩容?
直接扩容为10个大小。
如果添加元素到达数组能存放的最大大小时,如何扩容?(扩容因子为达到上限。)
扩容1/2,最终大小为原来的1.5倍
private int size; // 当前数组中存放的元素个数
private static final int DEFAULT_CAPACITY = 10; // 默认指定的大小
public int size() {
return size;
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 判断是否扩容
elementData[size++] = e; // 将元素放入到数组中
return true;
}
private void ensureCapacityInternal(int minCapacity) {
// 如果是通过无参构造方法创建的集合,并且第一次添加元素,直接指定大小默认大小(10个)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 扩容方法
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0) // 判断指定的大小是否比数组大小大
grow(minCapacity); // 空间扩容
}
/*
扩容并复制元素
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 每次扩容扩大1/2,即最终大小为原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 如果超过保留大小,则直接指定为int的最大值
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity); // 复制元素
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
3.2 Vector
使用与ArrayList基本一样。线程安全,效率低。现在基本被废弃。
3.3 LinkedList
数组和链表:
数组特点是遍历性能好,添加,删除性能差。
而链表结构与数组相反,添加、删除性能好,但是遍历性能相对差。(单向和双向)
基本用法:
public static void main(String[] args) {
LinkedList list = new LinkedList();
// 链表结构向头尾添加性能最优
list.add("hello"); // 直接使用add会自动添加到尾部,相当于addLast
list.addLast("world");// 向尾部添加
list.getLast(); // 得到尾部的元素
list.getFirst(); // 得到头部的元素
// 通过下标查找,性能不好,需要循环遍历
// 代码有相应的优化,即如果该下标小于中间值,则从头开始向下找
// 如果该下标大于中间值,则从尾部开始向上找
list.get(1); // 得到第一号元素
list.remove(1); // 根据下标删除
list.removeLast();// 删除最后一个
}
总结:
LinkedList会有一个first,即头节点,last即尾节点。
每次向头部添加元素,只需要构造一个节点对象,并将该节点的next指向原list的first,并将list的first指向当前节点即可。
每次向尾部添加元素,只需要构造一个节点对象,并将原list的last节点的next指向该节点,并将该节点的prev指向原list的last,并将list的last指向当前节点即可。
四、泛型
泛型的作用,主要是限定在使用某些类或者方法时,对应类型。
例如,泛型集合。在使用泛型创建的集合中,该集合必须使用该泛型类型的元素,获取时不需要强制转换。
自定义泛型:
public class MyList<E> {
public void m(E e) {
}
}
集合泛型的使用:
public static void main(String[] args) {
// 当没有使用泛型时,向集合中添加元素时,可以添加任意类型
// 当从集合中取出元素时,需要转换成相应的类型
ArrayList list = new ArrayList();
list.add("hello");
list.add(2); // Integer
Integer in = (Integer) list.get(1);
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
}
// 对集合使用泛型
// 通过泛型要求该集合只能使用String类型
// 添加了泛型后获取元素不用强制转换
ArrayList<String> list1 = new ArrayList<>();
list1.add("hello");
list1.add("world");
String str = list1.get(1);
System.out.println(str);
// foreach循环
for (String string : list1) {
System.out.println(string);
}
MyList<String> my = new MyList<>();
my.m("hello");
}
注意:Java的泛型为假泛型
Java中的泛型主要是编译前的代码检查和赋值时类型的改变,在实际运行时并没有泛型。
五、Collections工具类
Collections工具类是针对集合的操作编写的工具类。可以对集合进行排序、倒置、打乱等操作。
注意:如果集合元素是自定义的元素,排序之前需要实现java.lang.Comparable接口。
public static void main(String[] args) {
// 直接将几个元素一次性放入一个集合中
List<String> list = Arrays.asList("hello", "world", "test", "aaa");
// 倒置
Collections.reverse(list);
System.out.println(list);
// 随机打乱
Collections.shuffle(list);
System.out.println(list);
// 排序
Collections.sort(list);
System.out.println(list);
List list1 = Arrays.asList(
new Product(5, "牙膏"),
new Product(4, "牙刷"),
new Product(1, "毛巾"),
new Product(2, "杯子"),
new Product(3, "衣架")
);
// 排序
// 如果集合中是自定义元素,需要实现java.lang.Comparable接口
Collections.sort(list1);
System.out.println(list1);
}
public class Product implements Comparable<Product>{
private int id;
private String name;
// 省略getter、setter、构造、等方法的重写
@Override
public int compareTo(Product o) {
if(o != null) {
if(this.id > o.id) {
return 1;
}else if(this.id < o.id){
return -1;
}else {
return 0;
}
}
return 0;
}
}
六、Map接口及其实现类
map接口使用key-value方式来保存数据。
常见方法:put(key, value)
get(key)
keySet()
values()
6.1 HashMap[重点]
使用key-value方式存储。线程不安全。
public static void main(String[] args) {
// 使用key-value的形式存储
// key键 value值
// key不能重复,如果重复,值覆盖
// key可以是任意类型,但是推荐使用字符串
// key可以为null,但是只能有一个,value可以是null
HashMap<String, Song> map = new HashMap<>();
// 添加元素
map.put("a", new Song("如烟", "五月天", 200));
// 取值,如果通过一个没有的名称获取值,返回null
Song s = map.get("a");
System.out.println(s.play());
// 尝试key重复的情况,会覆盖
map.put("a", new Song("知足", "五月天", 200));
// 取值
Song s1 = map.get("a");
System.out.println(s1.play());
// keySet 键集(所有的键的集合)
Set<String> keys = map.keySet();
// 借助键集来循环
for (String string : keys) {
System.out.println("key:" + string + ", value:" + map.get(string).play());
}
// 值集
Collection<Song> values = map.values();
// 循环值集
for (Song song : values) {
System.out.println(song.play());
}
// 大小
int size = map.size();
// 循环键值(使用entrySet)
for (Entry<String, Song> entry : map.entrySet()) {
System.out.println("key:" + entry.getKey() + ", value:" + entry.getValue().play());
}
// 删除,如果删除一个没有的名称,对应原集合没有任何影响
map.remove("b");
// 是否包含key
map.containsKey("a");
// 是否包含值
Song song = new Song("", "", 100);
map.containsValue(song); // 是通过equals来判断
}
原理:
以数组+链表结构组合而成。既有数组的优点,又有链表的优点。
添加元素时,根据key的hash值,来确定添加到哪一个数组的元素节点后。
查询元素时,根据key的hash值,来提升查找的性能。
添加元素时,当元素的总个数大于64,且单条链表上的元素超过8个时,会将该条链表转换成红黑树(JDK1.8),以进一步提升查找的性能。
扩容因子:0.75
扩容容量x2
即当元素的个数达到容量的0.75时,进行扩容,扩容大小为原来的两倍。
6.2 Hashtable
用法基本与HashMap相似,但是是线程安全的,key和value都不能为null。性能较差,已基本不用。
6.3 Properties
继承自Hashtable,但是添加限制键值类型为String的操作方法。
setProperty()
getProperty()
作用是为了操作配置文件.properties文件而存在。
config.properties
username=zhangsan
password=1234567890
public static void main(String[] args) throws IOException {
// 存放配置信息
Properties prop = new Properties();
prop.setProperty("path", "c:/users/");
prop.setProperty("username", "root");
prop.setProperty("password", "root");
System.out.println(prop.getProperty("password"));
// 很多时候配置信息是写在工程的某一个文件中
// 读取properties文件信息
Properties prop1 = new Properties();
// 读取配置文件并加载文件中的信息
InputStream is = MyPropertiesDemo.class.getResourceAsStream("/config.properties");
prop1.load(is); // 加载信息
System.out.println(prop1.getProperty("username"));
System.out.println(prop1.getProperty("password"));
}
6.4 有序、无序和排序
有序是指会按照添加的顺序规律来保存数据。循环输出时会体现该规律。反之则是无序。
会将添加的元素按照指定的排序规则进行排序,取值或循环显示时会按照排序后的结果显示。
6.4 TreeMap
实现了SortedMap接口,要求所有的key应该实现java.lang.Comparable接口,会在添加元素时按照指定的排序规则进行排序。
public static void main(String[] args) {
// 添加元素时,会按照指定排序规则将添加的元素key进行排序
TreeMap<Song, String> map = new TreeMap<>();
map.put(new Song("", "", 200), "zhangsan");
map.put(new Song("", "", 220), "lisi");
map.put(new Song("", "", 120), "wangwu");
map.put(new Song("", "", 60), "zhaoliu");
Set<Song> keySet = map.keySet();
for (Song song : keySet) {
System.out.println(song.play());
}
}
七、Set接口及其实现类
无序、无下标、元素不重复
7.1 HashSet[重点]
元素不重复是使用hashCode来计算的,所以要实现该功能,需要元素对应的类型重写equals和hashCode方法。
添加相同元素,后面添加的会无法添加,保留之前的元素,不会覆盖。
基本用法:
public static void main(String[] args) {
// 无序、元素不能重复
HashSet<String> set = new HashSet<>();
set.add("f");
set.add("a");
set.add("g");
set.add("c");
set.add("c");
for (String string : set) {
System.out.println(string);
}
// 小技巧:如果遇到需要去重功能,可以将元素放入set中
// 注意元素是否重复是根据hashCode和equals来判断,需要重写这些方法
HashSet<Student> set1 = new HashSet<>();
set1.add(new Student(1, "zhangsan"));
set1.add(new Student(1, "zhangsan"));
set1.add(new Student(1, "zhangsan"));
for (Student student : set1) {
System.out.println(student);
}
System.out.println("=====去重前======");
// 去重功能
ArrayList<Student> list = new ArrayList<>();
list.add(new Student(1, "zhangsan"));
list.add(new Student(1, "zhangsan"));
list.add(new Student(2, "lisi"));
list.add(new Student(2, "lisi"));
list.add(new Student(3, "wangwu"));
list.add(new Student(3, "wangwu"));
for (Student student : list) {
System.out.println(student);
}
System.out.println("=====去重后======");
HashSet<Student> set2 = new HashSet<>(list);
for (Student student : set2) {
System.out.println(student);
}
}
原理:使用HashMap的key作为set的元素存储空间。
7.2 LinkedHashSet
有序。(添加元素的顺序,使用链表实现<双向>)
LinkedHashSet继承自HashSet,所以具备有HashSet的基本使用方式。
不同的是,实现来自于LinkedHashMap,LinkedHashMap继承自HashMap,加了一个链表来实现了添加有序功能。
public static void main(String[] args) {
// 有序(插入顺序)、元素不能重复
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("f");
set.add("a");
set.add("g");
set.add("c");
set.add("c");
for (String string : set) {
System.out.println(string);
}
// 小技巧:如果遇到需要去重功能,可以将元素放入set中
// 注意元素是否重复是根据hashCode和equals来判断,需要重写这些方法
LinkedHashSet<Student> set1 = new LinkedHashSet<>();
set1.add(new Student(1, "zhangsan"));
set1.add(new Student(1, "zhangsan"));
set1.add(new Student(1, "zhangsan"));
for (Student student : set1) {
System.out.println(student);
}
System.out.println("=====去重前======");
// 去重功能
ArrayList<Student> list = new ArrayList<>();
list.add(new Student(1, "zhangsan"));
list.add(new Student(1, "zhangsan"));
list.add(new Student(2, "lisi1"));
list.add(new Student(2, "lisi2"));
list.add(new Student(3, "wangwu"));
list.add(new Student(3, "wangwu"));
for (Student student : list) {
System.out.println(student);
}
System.out.println("=====去重后======");
LinkedHashSet<Student> set2 = new LinkedHashSet<>(list);
for (Student student : set2) {
System.out.println(student);
}
}
7.3 TreeSet
元素需要实现java.lang.Comparable接口,会自动将元素排序的集合。
底层是使用TreeMap来实现。
基本使用方法跟HashSet基本一样。
public static void main(String[] args) {
// 排序、元素不能重复
TreeSet<String> set = new TreeSet<>();
set.add("f");
set.add("a");
set.add("g");
set.add("c");
set.add("c");
for (String string : set) {
System.out.println(string);
}
// 小技巧:如果遇到需要去重功能,可以将元素放入set中
// 注意元素是否重复是根据hashCode和equals来判断,需要重写这些方法
TreeSet<Student> set1 = new TreeSet<>();
set1.add(new Student(1, "zhangsan"));
set1.add(new Student(1, "zhangsan"));
set1.add(new Student(1, "zhangsan"));
for (Student student : set1) {
System.out.println(student);
}
System.out.println("=====去重前======");
// 去重功能
ArrayList<Student> list = new ArrayList<>();
list.add(new Student(3, "wangwu"));
list.add(new Student(3, "wangwu"));
list.add(new Student(1, "zhangsan"));
list.add(new Student(1, "zhangsan"));
list.add(new Student(2, "lisi1"));
list.add(new Student(2, "lisi2"));
for (Student student : list) {
System.out.println(student);
}
System.out.println("=====去重后======");
TreeSet<Student> set2 = new TreeSet<>(list);
for (Student student : set2) {
System.out.println(student);
}
}