集合
面向对象对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储。另一方面,使用Array存储对象方面具有一些弊端,而Java集合就像是一种容器,可以动态的把多个对象的引用放入容器中。
数组在内存存储方面的特点:
数组初始化后,长度就确定了。
数组声明的类型,就决定了进行元素初始化时的类型。
数组在内存存储方面的弊端:
数组初始化以后,长度不可变,不便于扩展。
数组提供的属性和方法少,不便于进行添加,删除,插入等操作,且效率不高。同时无法直接获取存储元素的个数。
数组存储的数据是有序的,可以重复的。
Java集合类可以用于存储数量不等的多个对象,还可以用于保存具有映射关系的关联数组。
Java集合可分为Collection和Map两种体系
Collection接口:单列数据,定义了存取一组对象的方法的集合
List:元素有序,可重复的集合 "动态数组"
Set:元素无序,不可重复的集合
Map接口:双列数据,保存具有映射关系“ key-value对 ”的集合
Collection接口继承树
Collection接口的常用方法
public class CollectionTest {
@Test
public void test() {
Collection coll = new ArrayList();
//将元素添加到动态数组中 要求传入的obj需要重写equals方法。
coll.add("Aa");
coll.add(123);//自动装箱
coll.add(LocalDateTime.now());
coll.add(new int[5]);
//获取添加的元素个数
System.out.println(coll.size());
//将另一个集合的元素都添加到当前集合中
Collection coll1 = new ArrayList();
coll1.add("dasd");
coll1.add(123456);
coll.addAll(coll1);
System.out.println(coll.size());
System.out.println(coll);
//清空集合元素
coll.clear();
//判断当前集合是否为空
System.out.println(coll.isEmpty());
//判断当前集合是否包含传入的obj
System.out.println(coll1.contains(123456));
//比较的是值 如果是测试是否包含自定义类的对象,需要重写类的equals方法
System.out.println(coll1.contains(new String("dasd")));
//判断当前集合是否包含传入的所有obj
Collection coll2 = new ArrayList();
coll2.add(123456);
coll2.add("dasd");
coll2.add(123456);
//coll2.add(new Date());
System.out.println(coll1.containsAll(coll2));
//移除集合中某个元素
coll2.remove(123456);
System.out.println(coll2);
//移除集合与指定集合的交集 相当于求差集
Collection coll3 = Arrays.asList(123456, "dasd", "hh", 20.0, 111);
coll2.removeAll(coll3);
System.out.println(coll2);
//求交集
//Arrays.asList返回的是Arrays的内部类ArrayList和直接new出来不一样
//所以用它调用Collection的方法会报错
//Collection coll4 = Arrays.asList(123456, 5.0, "hh");
Collection coll4 = new ArrayList();
coll4.add(5.0);
coll4.add("hh");
coll4.add(123456);
coll4.retainAll(coll3);
System.out.println(coll4);
//判断集合是否相等
Collection coll5 = new ArrayList();
coll5.add("hh");
coll5.add(123456);
System.out.println(coll5.equals(coll4));
//当前对象的哈希值
System.out.println(coll5.hashCode());
//集合 -> 数组
Object[] arr = coll5.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
//扩展 数组 -> 集合 可变形参相当于Object[]
List<Object> list = Arrays.asList("123456", 123, 123.0);
System.out.println(list.getClass());//不是实现Collection接口的ArrayList
}
}
Iterator接口
Iterator对象称为迭代器(设计模式的一种),主要用于遍历Collection集合中的元素。
GOF给迭代器模式的定义为:提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节。
Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,所有实现Collection接口的集合类都有一个iterator方法,用以返回一个实现了Iterator接口的对象,每次返回都是新的
public class CollectionTest {
@Test
public void test() {
Collection coll = new ArrayList();
coll.add("Aa");
coll.add(123);
coll.add("bb");
coll.add(123.123);
coll.add('z');
coll.add(123);
//迭代器接口
Iterator iterator = coll.iterator();
//取数据
// System.out.println(iterator.next());
// System.out.println(iterator.next());
// System.out.println(iterator.next());
//普通for
// for (int i = 0; i < coll.size(); i++) {
// System.out.println(iterator.next());
// }
//hasNext()
// while (iterator.hasNext()) {
// System.out.println(iterator.next());
// }
//remove() 删除集合中指定元素如果有的话(只能在next后调用一次)
// while (iterator.hasNext()) {
// Object obj = iterator.next();
// if ("bb".equals(obj)) {
// iterator.remove();
// }
// }
//遍历
// Iterator iterator1 = coll.iterator();
// while (iterator1.hasNext()) {
// System.out.println(iterator1.next());
// }
//foreach循环遍历 增强for
for (Object o : coll) {
System.out.print(o + "\t");
}
}
}
迭代器的执行原理
Collection子接口:List接口
List集合类中元素有序,且可重复。 "动态数组"
常用实现类有ArrayList,LinkedList,Vector。
ArrayList,LinkedList,Vector的异同
同:三个类都实现了List接口,存储数据的特点相同:有序,可重复
不同:ArrayList:(List接口的主要实现类),线程不安全,效率高。底层使用Object[ ]存储
LinkedList:对于频繁的插入,删除操作,此类效率比ArrayList高,底层使用双向链表存储
Vector:(很古老的实现类)线程安全的,效率低。底层使用Object[ ]存储
ArrayList源码分析
jdk 7
类似单例模式饿汉式
capacity:容量
jdk 8
类似单例模式懒汉式,延迟数组的创建,节省内存
结论:在开发中建议使用带参数的构造器:ArrayList list = new ArrayList(int capacity);
LinkedList源码分析
↓默认值为null↓
向LinkedList中添加时(add()),调用了linkLast方法
图中last为当前最后一个元素,如果是首次调用add方法,则为null
然后新建一个Node将last作为它的前一个元素,并将null作为它的后一个元素
将新建Node赋值给last,作为最后一个元素
判断原来的最后一个元素是否为空,如果为空则新添加的元素也为第一个元素
否则将原来的最后一个元素的下一个赋值为新建元素
添加完成LinkedList的size++。
Vector源码分析
通过构造器创建对象时,底层都创建了长度为10的数组
扩容方面默认扩容为原来的数组长度的2倍
List接口中的常用方法
public class CollectionTest {
@Test
public void test() {
ArrayList arrayList = new ArrayList();
arrayList.add(123456);
arrayList.add("gyq");
arrayList.add(18.0);
ArrayList arrayList1 = new ArrayList();
arrayList1.add(10086);
arrayList1.add("gyq");
//在指定索引位置插入元素
arrayList1.add(1, 18.0);
System.out.println(arrayList1);
//在指定索引位置插入指定集合所有元素
arrayList.addAll(1, arrayList1);
System.out.println(arrayList);
//获取指定索引的元素
System.out.println(arrayList.get(3));
//判断参数在集合中首次出现的索引位置 不存在返回-1
System.out.println(arrayList.indexOf("gyq"));
//判断参数在集合中末次出现的索引位置 不存在返回-1
System.out.println(arrayList.lastIndexOf("gyq"));
//移除指定索引位置的元素 并返回被删除的元素
Object removed = arrayList.remove(1);
System.out.println(removed);
System.out.println(arrayList);
//设置指定索引的元素 并返回被改变的元素
Object changed = arrayList.set(0, "first");
System.out.println(changed);
System.out.println(arrayList);
//返回当前集合范围在[开始索引,结束索引)内的子集合
List subList = arrayList.subList(1, arrayList.size() - 1);
System.out.println(subList);
System.out.println(arrayList);
//用迭代器遍历 也可以普通for 增强for
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
Collection子接口:Set接口
存储无序,不可重复的数据
Set接口的实现类有:
HashSet:作为Set接口的主要实现类,线程不安全的,可以存储null值
LinkedHashSet:(继承与HashSet)遍历其内部数据时,可以按照添加的顺序遍历
对于频繁的遍历操作。效率高于HashSet。
TreeSet:可确保集合元素处于排序状态(可以按照对象(类型要一致)的指定属性排序)
无序性和不可重复性的理解
无序性:不等于随机性,存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值。
不可重复性:保证添加的元素按照equals()方法判断时,不能返回true,即相同的元素只能添加一个。(不准确,详见下方添加元素过程代码)
通过HashSet添加元素的过程理解不可重复性
public class SetTest {
@Test
public void test() {
HashSet hashSet = new HashSet();
hashSet.add(456);
hashSet.add("aa");
hashSet.add(new User("gyq", 18));
hashSet.add(new Person("gyq", 18));
hashSet.add(new User("gyq", 18));
hashSet.add(123);
hashSet.add(666);
hashSet.add(666);
hashSet.add(555.05);
// 无序性
//以下代码每次运行输出结果顺序相同,只是不和添加的顺序一样
//LinkedList()的遍历结果和加入元素顺序一致
//所以无序性不等于随机性
for (Object o : hashSet) {
System.out.print(o + "\t");
}
// 添加元素过程
// 不可重复性
//如果User没有重写equals()和hashCode()
//遍历时则会有两个属性相同的User实例化对象元素
// 不可重复性的理解
//在添加元素时是根据hash值通过某个算法进行计算的结果确定位置的
//先判断元素的hash值的计算结果与已有元素的hash值的计算结果是否相同
//如果hash值的计算结果不同则直接进行添加,不会调用equals()
//此例中User如果没重写hashCode方法则会有两个属性相同的User对象
//如果hash值的计算结果相同则放置的位置相同
//此时判断hash值是否相同,如果相同则调用equals方法
//不同则以链表方式直接添加(将原来位置上的元素指向新添加的元素)
//而此例中的属性相同的User和Person对象就是hash值相同调Person对象的equals(User对象)方法
//如果结果为true则不添加,否则同样以链表方式添加
}
}
class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
System.out.println("执行User的equals方法!");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
System.out.println("执行Person的equals方法!");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
结论:HashSet底层是数组和链表结合实现的;
向HashSet中添加的数据,其所在类一定要重写hashCode方法和equals方法;
这两个方法尽可能保持一致性:相同的对象具有相同的散列码(hashcode一样equals也为true)
hashCode()和equals()的重写
Object类的hashCode方法返回的是随机值。
上方代码中Person和User不重写hashCode方法则其在集合中放置位置不同会直接被添加到集合中。
Idea自动重写的hashcode方法调用的方法
开发中直接使用自动生成即可
TreeSet
向TreeSet中添加的数据,要求是相同类的对象
采用红黑树存储结构,特点:有序,查询速度比List快。
两种排序方式:自然排序和定制排序
自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()
定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()
自然排序
public class SetTest {
@Test
public void test() {
TreeSet treeSet = new TreeSet();
treeSet.add(123);
treeSet.add(120);
treeSet.add(180);
treeSet.add(250);
treeSet.add(999);
treeSet.add(22);
//遍历集合 是自然排序
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
//当集合元素是自定义类时
//需要实现Comparable接口,重写ComoparaTo方法
//ComparaTo方法需要对所有属性进行比较
//否则会导致只比较一个属性就认为两者是同一元素不在添加。
TreeSet treeSet1 = new TreeSet();
treeSet1.add(new Person("GYQ", 19));
treeSet1.add(new Person("GYQ", 19));
treeSet1.add(new Person("ABC", 20));
treeSet1.add(new Person("ABC", 10));
treeSet1.add(new Person("DHJ", 99));
treeSet1.add(new Person("BDD", 15));
treeSet1.add(new Person("FAK", 60));
Iterator iterator1 = treeSet1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
}
class Person implements Comparable {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//按照姓名按照英文字母排,再按照年龄从小到大
@Override
public int compareTo(Object o) {
if (o instanceof Person) {
Person person = (Person) o;
if (this.name.compareTo(person.name) == 0) {
return Integer.compare(this.age, person.age);
} else {
return this.name.compareTo(person.name);
}
}
throw new RuntimeException("输入类型不匹配");
}
}
}
定制排序
public class SetTest {
@Test
public void test() {
//当集合元素是自定义类时
//需要使用Comparator接口,重写Comopara方法
Comparator comparator = new Comparator() {
//按照年龄从大到小排序,年龄一样认为是一样的,不添加
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Person && o2 instanceof Person) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
return Integer.compare(p1.age, p2.age);
}
throw new RuntimeException("比较类型不匹配!");
}
};
TreeSet treeSet1 = new TreeSet(comparator);
treeSet1.add(new Person("GYQ", 19));
treeSet1.add(new Person("ABC", 20));
treeSet1.add(new Person("ABC", 10));
treeSet1.add(new Person("SSS", 10));
treeSet1.add(new Person("DHJ", 99));
treeSet1.add(new Person("BDD", 15));
treeSet1.add(new Person("FAK", 60));
Iterator iterator1 = treeSet1.iterator();
while (iterator1.hasNext()) {
System.out.println(iterator1.next());
}
}
class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
Map接口
Map接口继承树
HashMap:Map的主要实现类,线程不安全的,效率高,可以存储null的key和value
LinkedHashMap:保证在遍历元素时,是按照添加时的顺序,在HashMap的基础上添加了一对指针,指向了前一个和后一个元素,对于频繁的遍历操作,效率高于HashMap。
TreeMap:保证按照添加的key-value的key对进行排序,实现排序遍历。自然排序或定制排序
底层使用红黑树
HashTable:比较古老的实现类,线程安全的,效率低,不可以存储null的key和value
Properties:常用来处理配置文件,key和value都是String类型
Map结构的理解
Map中的key:无序的,不可重复的,使用Set存储所有的key key所在的类要重写equals()和hashCode()
Map中的value:无序的,可重复的,使用Collection存储所有的value value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象
Map中的entry:无序的,不可重复的,使用Set存储所有的entry
HashMap的底层实现原理
jdk 7
jdk 8
HashMap的源码分析
见视频
LinkedHashMap的底层实现
Map的常用方法
public class MapTest {
@Test
public void test() {
Map hashMap = new HashMap();
//添加元素
hashMap.put(1, "gyq");
hashMap.put(2, "gzz");
hashMap.put(3, "uzi");
hashMap.put(4, "clv");
hashMap.put(5, "god");
//key相同时覆盖前面的内容
hashMap.put(4, "ggg");
System.out.println(hashMap);
//将另一个map的元素全部放入
Map hashMap1 = new HashMap();
hashMap1.put(0, "zzz");
hashMap1.put(7, "hhh");
hashMap.putAll(hashMap1);
System.out.println(hashMap);
//删除集合中指定key的键值对,并返回对应value,若不存在返回null
Object removed = hashMap.remove(7);
System.out.println(removed);
//清空map
//hashMap.clear();
System.out.println(hashMap.size());
//判断map是否为空
System.out.println(hashMap.isEmpty());
//查询指定key的值
System.out.println(hashMap.get(1));
//判断map中是否包含指定key/value
System.out.println(hashMap.containsKey(0));
System.out.println(hashMap.containsValue("摆烂"));
//判断当前map和指定map是否相等
System.out.println(hashMap.equals(hashMap1));
//获取map中key的集合
Set keySet = hashMap.keySet();
//遍历map
for (Object o : keySet) {
System.out.println(o + "-" + hashMap.get(o));
}
//获取value组成的集合
Collection values = hashMap.values();
System.out.println(values);
//获取所有键值对组成的集合
Set entrySet = hashMap.entrySet();
Iterator iterator = entrySet.iterator();
while (iterator.hasNext()) {
Object nextObj = iterator.next();
Map.Entry nextEntry = (Map.Entry) nextObj;
System.out.println(nextEntry.getKey() + "--" + nextEntry.getValue());
}
}
}
TreeMap两种添加方式
向TreeMap中添加数据,要求key必须是由同一个类创建的对象
因为要按照Key进行排序,自然排序,定制排序
自然排序
要求key所在类继承Comparable接口,重写CompareTo方法(决定排序方式)
public class MapTest {
@Test
public void test() {
TreeMap treeMap = new TreeMap();
treeMap.put(new User("gyq", 24), "999999");
treeMap.put(new User("gyq", 12), "123456");
treeMap.put(new User("asd", 12), "111111");
treeMap.put(new User("zzz", 100), "888888");
treeMap.put(new User("clv", 90), "777777");
treeMap.put(new User("uzi", 99), "666666");
treeMap.put(new User("add", 22), "541688");
Set entrySet = treeMap.entrySet();
for (Object o : entrySet) {
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey().toString() + "-" + entry.getValue());
}
}
}
class User implements Comparable {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
//先根据年龄从小到大排,再根据姓名按字母顺序排
@Override
public int compareTo(Object o) {
if (o instanceof User) {
User anotherUser = (User) o;
if (Integer.compare(this.age, anotherUser.age) == 0) {
return this.name.compareTo(anotherUser.name);
} else
return Integer.compare(this.age, anotherUser.age);
}
throw new RuntimeException("类型错误");
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
定制排序
要使用Comparator接口
public class MapTest {
@Test
public void test() {
//定制排序 按名字排,名字一样按年龄从小到大排
TreeMap treeMap = new TreeMap(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
System.out.println("调用了Compare");
if (o1 instanceof User && o2 instanceof User) {
User user1 = (User) o1;
User user2 = (User) o2;
if (user1.getName().compareTo(user2.getName()) == 0) {
return Integer.compare(user1.getAge(), user2.getAge());
} else return user1.getName().compareTo(user2.getName());
}
throw new RuntimeException("比较类型错误");
}
});
treeMap.put(new User("gyq", 24), "999999");
treeMap.put(new User("gyq", 12), "123456");
treeMap.put(new User("asd", 12), "111111");
treeMap.put(new User("zzz", 100), "888888");
treeMap.put(new User("clv", 90), "777777");
treeMap.put(new User("uzi", 99), "666666");
treeMap.put(new User("add", 22), "541688");
Set entrySet = treeMap.entrySet();
for (Object o : entrySet) {
Map.Entry entry = (Map.Entry) o;
System.out.println(entry.getKey().toString() + "-" + entry.getValue());
}
}
}
class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Properties处理属性文件
新建test.properties文件,里面内容如下
处理
public class PropertiesTest {
@Test
public void test() throws IOException {
Properties prpts = new Properties();
FileInputStream fis = new FileInputStream("test.properties");
prpts.load(fis);
String name = prpts.getProperty("name");
String pwd = prpts.getProperty("password");
System.out.println(name);
System.out.println(pwd);
}
}
如果出现读取乱码问题,在File -> Setting -> File Encodings中
三个选项都选为UTF-8 并将下面的对勾打上
Collections工具类
是一个操作Set,List和Map等集合的工具类
Collections提供了一系列静态方法对集合元素进行排序,查询和修改等操作,还提供了对集合元素对象设置不可变,对集合对象实现同步控制等方法
public class CollectionsTest {
@Test
public void test() {
List list = new ArrayList();
list.add(99);
list.add(909);
list.add(999);
list.add(-900);
list.add(888);
System.out.println(list);
//反转list中元素的顺序
// Collections.reverse(list);
//随机排序
// Collections.shuffle(list);
//根据元素的自然顺序对list集合元素升序排序
// Collections.sort(list);
//根据Comparater的指定顺序
// Collections.sort(list, new Comparator<Integer>() {
// @Override
// public int compare(Integer o1, Integer o2) {
// return -Integer.compare(o1, o2);
// }
// });
//交换list的指定位置上的元素
// Collections.swap(list, 0, 3);
System.out.println(list);
//获取Collection中最大值
// System.out.println(Collections.max(list));
//获取Collection中最大值(按照自定义排序规则)
// Integer max = Collections.max(list, new Comparator<Integer>() {
// @Override
// public int compare(Integer o1, Integer o2) {
// return -Integer.compare(o1, o2);
// }
// });
// System.out.println(max);
//获取Collection中最小值的两种方式同上
//返回指定集合中指定元素出现的次数
// int times = Collections.frequency(list, 888);
// System.out.println(times);
//将src中的内容复制到dest中 注意:dest集合的size()必须大于src的size()
// List dest = Arrays.asList(new Object[list.size()]);
// Collections.copy(dest, list);
// System.out.println(dest);
//将集合包装为线程安全的 Collection和Map接口的相关实现类都有相关方法
// List synchronizedList = Collections.synchronizedList(list);
// System.out.println(synchronizedList);
}
}