集合
集合和数组的异同
同:都是用来存储数据的
异:数组长度不可变,且要求存储同一类型的数据,集合长度可变,可以存放不同类型的数据
集合的框架
java集合框架分为两个接口Collection(单列集合)和Map(双列集合)
二者的区别Collection和Map接口之间的主要区别在于:Collection中存储了一组对象,而Map存储关键字/值对。 在Map对象中,每一个关键字最多有一个关联的值。 Map:不能包括两个相同的键,一个键最多能绑定一个值。 null可以作为键,这样的键只有一个;可以有一个或多个键所对应的 值为null。 当get ()方法返回null值时,即可以表示Map中没有该键,也可以表示该键所对应的值为null。 因此,在Map中不能由get ()方法来判断Map中是否存在某个键,而应该用containsKey ()方法来判断。 HashTable:Dictionary的子类,确省是线程同步的。 不允许关键字或值为nul
Collection接口
Collection 接口是 List、Set 和 Queue 接口的父接口,通常情况下不被直接使用。Collection 接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作。定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。
// Collection的API常用方法 abstract boolean add(E object) abstract boolean addAll(Collection<? extends E> collection) abstract void clear() abstract boolean contains(Object object) abstract boolean containsAll(Collection<?> collection) abstract boolean equals(Object object) abstract int hashCode() abstract boolean isEmpty() abstract Iterator<E> iterator() abstract boolean remove(Object object) abstract boolean removeAll(Collection<?> collection) abstract boolean retainAll(Collection<?> collection) abstract int size() abstract <T> T[] toArray(T[] array) abstract Object[] toArray() /** * Collection方法声明对象的使用 * 向Collection对象中添加数据用add()方法,可以调用各种类型的数据 * contrain方法判断元素是否存在对象中,返回的是true或false */ @SuppressWarnings("all") public class CollectionDemo01 { @Test // 集合的判断操作 public void test1(){ Collection col1 = new ArrayList<>(); col1.add(123); col1.add(456); col1.add("A"); col1.add("B"); col1.add("fvhhb"); col1.add(new String("jie")); col1.add(new Demo01("tianliang",22)); // contains方法,判断元素是否存在集合中,返回的是true或false System.out.println(col1.contains(new String("jie"))); System.out.println(col1.contains(new Demo01("tianliang",22))); } @Test // 集合的添加和移除 public void test2(){ Collection col1 = new ArrayList<>(); col1.add(123); col1.add(456); col1.add("A"); col1.add("B"); col1.add("fvhhb"); col1.add(new String("jie")); col1.add(new Demo01("tianliang",22)); col1.remove(456); System.out.println(col1); col1.remove(new Demo01("tianliang",22)); System.out.println(col1); } @Test // 集合的移除操作和求两个集合的交集 public void test3(){ Collection col1 = new ArrayList<>(); col1.add(123); col1.add(456); col1.add("A"); col1.add("B"); col1.add("fvhhb"); col1.add(new String("jie")); col1.add(new Demo01("tianliang",22)); col1.remove(456); Collection col11 = Arrays.asList(123,456,896); col1.retainAll(col11); System.out.println(col1); } @Test // 集合与数组之间的转换 public void test4(){ Collection col1 = new ArrayList<>(); col1.add(123); col1.add(456); col1.add("A"); col1.add("B"); col1.add("fvhhb"); // 将集合转换为数组 Object[] arr = col1.toArray(); for (Object o:arr){ System.out.println(o); } // 将数组转换为集合 List<String> list = Arrays.asList(new String[]{"a", "b", "c"}); List list1 = Arrays.asList(new Integer[]{123,456}); System.out.println(list1); } }
List子接口
List集合是一组有序可重复集合,元素都有索引,按照添加顺序排列,可以通过索引(LinkedList使用链表)快速查找,其中包含了三个常用的类,分别是ArrayList,Vector,LinkedList。
ArrayList实现类
-
底层使用的是数组;
-
根据数组索引查找元素,所以查询快;增删因为要移动元素位置,所以增删慢;
-
元素按照插入的顺序排列,元素可以重复;
-
非线程安全
ArrayList底层实现原理是数组,但jdk版本不一样其原理也有所不同
在jdk7中是先定义好一个长度为10的数组,而在jdk8中先不定义,在调用add()方法后再定义一个长度为10的数组
//jdk7: ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementDate list.add(123);//elementDate[] = new Integer(123); ... list.add(11);//如果此次的添加导致底层elementDate数组容量不够,则扩容 //1.默认扩容为原来的1.5倍,同时将原来的数组元素复制到新的数组中。 //2.结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity) //jdk8: ArrayList list = new ArrayList();//底层Object[]数组elementDate初始化为{},并没有创建长度为10的数组 list.add(123);//第一次调用add()时,底层才创建了长度为10的数组,并将数据123添加到elementDate中 ... list.add(11);//如果此次的添加导致底层elementDate数组容量不够,则扩容 //1.默认扩容为原来的1.5倍,同时将原来的数组元素复制到新的数组中。 //2.结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
ArrayList常用方法
/** * list常用方法 * 总结:常用方法 * 增:add(Object obj) * 删:remove(int index)或remove(Object obj) * 改:set(int index,Object ele) * 查:get(int index) * 插:add(int index,Object ele) * 长度:size() * 遍历:1.Iterator 2.增强for循环 */ public class ListTest { @Test public void Test(){ ArrayList arrayList = new ArrayList<>(); arrayList.add(123); arrayList.add("r"); arrayList.add("liqi"); arrayList.add(85); arrayList.add(new Demo01("tianliang",22)); // ArrayList插入操作 void add(index,Object ele),在索引位置插入元素 System.out.println(arrayList); arrayList.add(2,"liang"); System.out.println(arrayList); // boolean addall(int index,Collection eles),在索引位置开始插入集合 List a = Arrays.asList("a", 123); arrayList.addAll(2,a); System.out.println(arrayList); // Object get(int index)通过索引获得元素 System.out.println(arrayList.get(5)); // int indexof(Object obj),返回某元素第一个出现的索引位置 System.out.println(arrayList.indexOf(123)); // int lastindexof(Object obj),返回某元素最后一次出现的索引位置 System.out.println(arrayList.lastIndexOf(123)); // Object remove(int index)移除指定索引位置的元素并返回该元素 System.out.println(arrayList.remove(6)); // Object set(int index,Object ele),修改某索引位置的元素并返回修改前的元素。 System.out.println(arrayList.set(5,"wei")); // list sublist(int index,int index),列表切片,返回的是一个列表 // 其中索引区间为左闭右开 System.out.println(arrayList.subList(1,5)); } }
ArrayList结构图
LinkedList实现类
LinkList是一个继承于AbstractSequentialList的双向链表。它可以被当做堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能进行队列操作。
LinkedList 实现Deque接口,即能将LinkedList当作双端队列使用。
import org.junit.Test; import java.util.LinkedList; /* LinkedList实现类相关操作 */ public class LinkListTest { @Test public void test() { LinkedList obj1 = new LinkedList<>(); // 向Linklist中添加元素 obj1.add(152); obj1.add("zeng"); obj1.add(42); obj1.add("A"); // 将元素放入队列的尾部 obj1.offer("c");// 输出[152,zeng,42,A,c] // 将元素放入顶部 obj1.push("f");// 输出[f,152,zeng,42,A,c] // 访问但不删除队列头部元素 System.out.println(obj1.peekFirst()); // 弹出队列顶部元素 obj1.pop(); System.out.println(obj1);// 输出[152,zeng,42,A,c] } }
HashSet
HashSet是Set子接口的一个实现类,它只允许放入同种类型的数据,且数据是无序的和不能重复的。HashSet的底层通过HashMap实现的,而HashMap在1.7之前使用的是数组+链表实现,在1.8+使用的数组+链表+红黑树实现。其实也可以这样理解,HashSet的底层实现和HashMap使用的是相同的方式,因为Map是无序的,因此HashSet也无法保证顺序。HashSet的方法也是借助HashMap的方法来实现的。
/* hashset实现类,当定义一个类时,所要有方法有无参构造方法和有参构造方法,tostring和set、get方法 以及hashcode和equals方法,当判断添加的元素是否重复时,先判断hashcode方法值,再判断eauals方法,当两个方法hashcode返回值相等,且equals方法返回true时,才能判断两个值是重复的 */ @SuppressWarnings("all") public class LinkListTest { @Test public void test02(){ Set set = new HashSet<>(); set.add(new Person("tianliang",29)); set.add(new Person("tianliang",29)); System.out.println(set); } } // 定义一个Person类,有私有对象name和age class Person{ private String name; private int age; // 无参构造器 public Person() { } // 有参构造器 public Person(String name, int age) { this.name = name; this.age = age; } // get()和set()方法 public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } // 重写父类的打印方法 @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } //重写equals方法 @Override public boolean equals(Object o) { if (!(o instanceof Person)){ return false;} Person person = (Person)o; if (this.name.equals(((Person) o).name)&&this.age==((Person) o).age){return true;} return false; } // 重写hashcode()方法 @Override public int hashCode() { return Objects.hash(name, age); } }
Map接口
-
实现类:
-
HashMap,其子类:
-
LinkedHashMap
-
-
SortedMap,其子类:
-
TreeMap
-
-
Hashtable,其子类:
-
Properties
-
-
-
Map接口:双列数据,存储key-value对的数据 ----类似于高中的函数:y = f(x) HashMap底层结构:jdk8中:数组+链表+红黑树 HashMap:作为Map主要的实现类:线程不安全,效率高;存储null的key和value,扩容为原来的两倍 子类:LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历 原因:在原有的HashMap底层结构基础上,添加一对指针,指向前一个和后一个 对于频繁的遍历操作,此类执行效率高于HashMap TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。 此时考虑key的自然排序或定制排序。 底层结构:使用红黑树 Hashtable:作为古老的实现类:线程安全,效率低;不能存储null的key和value 子类:Properties:常用来处理配置文件。key和value都是String类型
HashMap的底层实现原理
-
jdk7中:
-
HashMap map = new HashMap(); 1.实例化后底层创建了长度是16的一维数组Entry[] table。 ..可能已经执行过多次put... 2.map.put(key1,value1): 首先调用key1所在类的hashCode()计算key1哈希值,此哈希值经过某种算法计算得到Entry数组中的存放位置。 如果此位置上数据为空,此时key1,value1添加成功 如果此位置上有数据,比较key1和已经存在的一个或多个数据的哈希值(以链表形式存在): 如果哈希值都不相同,key1-value1添加成功 如果key1哈希值和某一个数据(key2-value2)的哈希值相同,调用key1所在类的equals(key2)方法比较: 如果equals()返回false,key1-value1添加成功 如果equals()返回ture,使用value1替换value2.
-
jdk8中: 1.new HashMap():底层没有创建一个长度为16的数组 2.jdk8底层的数组不是Entry[],而是Node[] 3.首次调用put()方法时,底层创建长度为16的数组 4。底层结构:数组+链表+红黑树, 当数组某一个索引位置上的元素以链表形式存在的数据个数>8,且当前数组的长度>64, 此索引位置上所有数据改为红黑树存储。
@SuppressWarnings("all") /* 练习题:创建一个hashmap<Interge,Demo01>,Demo01是自定义类,里面有属性name和age.试将hashmap中的元素按照 姓名从大到小进行排序,要求不可拆分键值对。 分析:由于hashmap不可进行排序,我们可以将其转换为list(其中的元素对象为hashmap键值对entry),然后再使用Collections 工具类中的sort方法对list进行排序,然后遍历list通过ListHashMap进行有序的添加数据 */ public class HashMapTest01 { public static void main(String[] args) { // 创建hashmap对象,键类型为int,值类型为Demo01对象 HashMap<Integer, Demo01> a1 = new HashMap<>(); a1.put(1, new Demo01("赵六", 18)); a1.put(2, new Demo01("李四", 23)); a1.put(3, new Demo01("王五", 17)); a1.put(4, new Demo01("张三", 20)); // 调用sort方法对值中的name属性进行排序 HashMap<Integer, Demo01> a2 = sort(a1); // 输出排序前后的数据变化 System.out.println("排序前:"+a1); System.out.println("排序后:"+a2); } public static HashMap<Integer,Demo01> sort(HashMap<Integer,Demo01> hashmap){ // 获取hashmap键值对 Set<Map.Entry<Integer, Demo01>> entry = hashmap.entrySet(); // 将键值对转换为list,以便进行排序 ArrayList<Map.Entry<Integer, Demo01>> list1 = new ArrayList<>(entry); // 调用Collections下的sort方法,对list进行排序 Collections.sort(list1, new Comparator<Map.Entry<Integer, Demo01>>() { @Override public int compare(Map.Entry<Integer, Demo01> o1, Map.Entry<Integer, Demo01> o2) { // 创建Collator对象com,对字符进行排序 Collator com = Collator.getInstance(Locale.CHINA); return com.compare(o2.getValue().getName(),o1.getValue().getName()); } }); LinkedHashMap<Integer, Demo01> hashmap1 = new LinkedHashMap<>(); for (Map.Entry<Integer,Demo01> entry1 : list1){ hashmap1.put(entry1.getKey(),entry1.getValue()); } return hashmap1; } }