第八章容器(collection 又叫集合)【重点】
-
数组和集合的比较:
- 数组就是一种容器,可以在其中放置对象或是基本数据类型 数组中能存储一切类型
- 数组的优势是是一种简单的线性序列,可以快速的访问数组元素,效率高,如果从效率和检查类型的角度来讲,数组是一种很好的选择
- 常见的集合:
- list 有顺序可以重复
- set 没有顺序不可以重复
-
泛型(帮助我们建立安全的集合)
- 泛型的本质是将:数据类型的参数化,我们可以吧泛型理解为数据类型的一个占位符(形式参数),是告诉编译器,在调用泛型是必须传入实际类型
public class TestGeneric { public static void main(String[] args) { MyCollection<String> mc = new MyCollection<String>(); mc.set("王", 0); //mc.set(2,1); //没有添加泛型 //Integer a = (Integer)mc.get(1); //String b = (String)mc.get(0); 传入的是String类型 就不用强制转型了 String b = mc.get(0);//不用强制转型 System.out.println(b); List list = new ArrayList(); Map map = new HashMap(); } } class MyCollection<E>{//简单的容器 添加泛型使用 T E V Object[] objs = new Object[5]; public void set (E e ,int index) { objs[index] = e; } public E get(int index) { return (E)objs[index]; } }
-
Collection接口
- list是有序的可以重复的容器
- 有序就是list的每个元素有索引标记,可以根据元素的索引标记(在list中的位置)访问元素,从而精确地控制这些元素,
- 可重复 list中允许加入重复元素,list通常允许满足e1.equals(e2)的重复元素加入容器
- list是有序的可以重复的容器
-
List 接口
-
常用实现类有有三种 ArrayList LinkedList Vector
Vector(线程安全但是不常用)
- ArrayList 底层使用数组实现的储存, 特点是查询效率高,增删效率低,线程不安全,(一般使用它一般是使用查询而不是增删)
- 数组的长度是有限的 且固定 这是使用数组的缺点 但是使用集合就能实现动态的扩容
- ArrayList是可以存放任意数量的对象长度不受限制(是使用动态数组 扩容实现的其实定义一个更长的数组 将以前的数组中的元素拷进来新数组的长度是10+ 10/2右移一位)
public class TestList { public static void main(String[] args) { test03(); } public static void test01() { Collection<String> c = new ArrayList<String>(); Set set; System.out.println(c.size());//返回collection中的elements 的个数 System.out.println(c.isEmpty());//判断是否为空 c.add("王"); c.add("213"); System.out.println(c);//默认调用toString方法 System.out.println(c.toString()); c.remove("王");//移除 元素是移除但是不是删除 容器中存的是对象的引用地址 //使用remove方法移除的是对象的引用地址 而不是删除对象 c.clear();//清空 c.add("王"); Object[] objs=c.toArray();//转出来一个Object数组 System.out.println(objs);//需要使用obj数组接收 System.out.println(c); System.out.println(c.contains("王"));//是否包含某个元素 } public static void test02() { Collection<String> c1 = new ArrayList<>(); c1.add("10"); c1.add("wang"); c1.add("王"); c1.add("wang01"); Collection<String> c2 = new ArrayList<>(); c2.add("10"); c2.add("wang"); c2.add("101"); c2.add("1022"); System.out.println("list01"+c1); System.out.println("list02"+c2); //c1.addAll(c2);//将c2 中的元素添加到出c1 中 //c1.removeAll(c2);//将c1中与c2 相同的元素删除 //c1.retainAll(c2);//取两个容器中的交集 System.out.println(c1.containsAll(c2));//c1中是否是包含了c2 中的所有元素 System.out.println(c1); } public static void test03() { List<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); System.out.println(list); list.add(2, "e");//是重载的方法 在指定的索引处插入指定的元素 System.out.println(list); list.remove(2);//将指定索引的元素删除 System.out.println(list); list.set(2, "e");//将指定的索引的元素替换 System.out.println(list); System.out.println(list.get(2));//获得指定的索引处的 元素 System.out.println(list.indexOf("c"));//返回指定的元素第一次出现时的索引 元素不存在就返回-1 System.out.println(list.lastIndexOf("b")); new ArrayList(); } }
-
Linkedlist 是使用双向链表实现的 特点是查询效率低 增删效率高 线程不安全。
//手动实现LinkList public class TestLinkedList03<E> { private Node first; private Node last; private int size; private void checkRange(int index) { if(index<0||index>size-1) { throw new RuntimeException("索引数字不合法!"+index); } } public void add(E element) {//alt+shift+r Node node = new Node(element); if(first == null) { node.previous = null; node.next = null; first =node; last = node; }else { node.previous = last; node.next = null; last.next =node; last = node; } size++; } @Override public String toString() { StringBuilder sb = new StringBuilder("["); Node temp = first; while(temp != null) { //System.out.println(temp.element); sb.append(temp.element+","); temp = temp.next; } sb.setCharAt(sb.length()-1, ']'); return sb.toString(); } public E get(int index) {//get方法效率低 checkRange(index); Node temp = getNode(index); return temp!=null?(E)temp.element:null; } private Node getNode(int index) { checkRange(index); Node temp = null; if(index<=(size>>1)) { temp = first; for(int i=0;i<index;i++) { temp = temp.next; } }else{ temp = last; for(int i = size-1;i>index;i--) { temp = temp.previous; } } return temp; } public void remove(int index) { checkRange(index); Node temp = getNode(index); if(temp!=null) { Node up = temp.previous; Node down = temp.next; if(up!=null) { up.next = down; } if(down!=null) { down.previous = up; } if(index ==0) { first = down; } if(index ==size-1){ last = up; } } size--; } public void add(int index, E element) { checkRange(index); Node newNode = new Node(element); Node temp = getNode(index); if(temp!=null) { Node up = temp.previous; up.next = newNode; newNode.previous = up; newNode.next = temp; temp.previous = newNode; } } public static void main(String[] args) { TestLinkedList03<String> list = new TestLinkedList03<>(); list.add("a"); list.add("b"); list.add("c"); list.add("c"); System.out.println(list); System.out.println(list.get(1)); System.out.println(list.get(2)); list.remove(1); System.out.println(list); list.add(2, "G"); System.out.println(list); } }
- 结点类 node/entry
- 定义结点
//使用java定义双向链表定义结点node public class Node { Node previous;//上一个结点 Node next;//下一个结点 Object element;//元素数据 public Node(Node previous, Node next, Object element) { super(); this.previous = previous; this.next = next; this.element = element; } public Node(Object element) { super(); this.element = element; } }
- 定义结点
- 结点类 node/entry
-
Vector向量 线程安全但是效率低 同样是使用数组实现的 但是相关方法加上了同步检查
-
三种list集合的使用方法
- 需要线程安全时 使用Vector (但是在多线程的环境下使用集合的情况非常的少)
- 不需要线程安全时,且检查较多时多用Ayyaylist
- 不存在线程安全时,增加或是删除元素较多时多用LinkedList
-
-
Map接口
- Map 就是使用键值对(key and value)来进行存储的,map类中的存储的键值对是通过键进行标识,所以键值对是不可以重复的
public class TestMap { public static void main(String[] args) { Map<Integer, String> a = new HashMap<>(); a.put(1, "one"); a.put(2, "two"); a.put(3, "three"); a.put(4, "four"); a.put(5, "fife"); System.out.println(a.size()); System.out.println(a.isEmpty()); System.out.println(a.get(1)); System.out.println(a.containsKey(3)); System.out.println(a.containsValue("three")); Map<Integer, String> b= new HashMap<>(); b.put(6, "一"); b.put(7,"二"); b.put(7,"san");//map 中键是不能重复的如果重复了就会覆盖以前的值!!! //是否重复是使用equals方法来进行判断的 a.putAll(b); System.out.println(a); } }
public class TestMap02 { public static void main(String[] args) { Employee e1 = new Employee(1, "wang", 5000); Employee e2 = new Employee(1, "wanger", 5000); Employee e3 = new Employee(1, "wangsan", 5000); Map<Integer, Employee> map = new HashMap<>(); map.put(1001, e1); map.put(1002, e2); map.put(1003, e3); Employee emp = map.get(1001); System.out.println(emp.getName()); System.out.println(map); } } //雇员信息 class Employee{ private int id; private String name; private double salary; public Employee(int id, String name, double salary) { super(); this.id = id; this.name = name; this.salary = salary; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } @Override public String toString() { return " id:"+id+" name:"+name+" salary:"+salary; } }
- Map接口的实现类有四种HashMap TreeMap HashTable Properties
- HashMap 底层使用的是哈希表, 这是一种很重要的数据结构
- 哈希表是由数组和链表来实现对数据的存储
- 数组占用的连续的空间,查询较快,但是增删较慢
- 链表 占用地址不连续 查询较慢 但是增删效率高
- 哈希表的使用就是结合了链表和数组的优点,查询快 增删的效率也高单向链表
调用put方法 key对象会先调用hashcode方法
算出哈希码 第二步取余数(有许多的对应方式 现在只讲取余数) hasmap中的数组的length为16 计算出hash值 放入Entry数组中(尽量散列的存入 取余数只是一种简单的方法) - 【注意】
- Jdk1.8中当链表的长度大于8时,链表就转换成红黑树,这样会大大的提高查找的效率
- hashcode与计算出的hash值是不同的
- Get方法 首先获得key的hashcode,然后通过hash散列算法得到hash值,进而定位到数组的位置 然后再使用equals方法进行比较最后取出value对象[java中规定,两个相同的对象(equals()为true)必须具有相等的hashCode
- 扩容问题 HashMap中的位桶数组,初始值是16.但是值是可变的,如果位桶数组中达到了(0.75*length),就重新调整成原数组的两倍大小 与list中相同扩容是很耗时的,扩容的本质是重新定义新的更大的数组,然后将数组的内容挨个拷贝到新数组中
- HashMap 底层使用的是哈希表, 这是一种很重要的数据结构
- 手动实现Map
public class TestHashMap03<K,V> { Node3[] table; //位桶数组 bucket array int size; //存放的键值对的数量 public TestHashMap03() { table = new Node3[16];//长度一般定义成2的幂次方 } public void put(K key, V value) { //定义了一个新的结点对象 Node3 newNode = new Node3(); newNode.hash = myHash(key.hashCode(), table.length); newNode.key = key; newNode.value = value; Node3 temp = table[newNode.hash]; Node3 iterLast = null;//正在遍历的最后一个元素 boolean keyRepeat = false; if(temp ==null) { table[newNode.hash] = newNode;//此处数组元素为空 就将新节点放进去 size++; }else {//如果此处数组 元素不为空 ,则遍历对应链表 while(temp!=null) { //判断key如果重复,则覆盖 if(temp.key.equals(key)) { keyRepeat = true; System.err.println("key重复了!"); temp.value = value;//将传入的值覆盖 break; }else { //key不重复 iterLast = temp;//??????? temp = temp.next; } } if(!keyRepeat) {//如果没有发生key重复的情况 就添加到链表的最后 iterLast.next = newNode; size++; } } } public V get(K key) {//get 方法与put方法相同 int hash = myHash(key.hashCode(),table.length); V value = null; if(table[hash]!=null) { Node3 temp = table[hash]; while(temp!=null) { if(temp.key.equals(key)) { value = (V)temp.value; break; }else { temp = temp.next; } } } return value; } public static int myHash(int v, int length) { //System.out.println( v&(length -1));//通过位运算 效率高 但是需要length是2的整数次幂 //System.out.println(v%(length -1));//通过取余 效率低 两种方法目的一样但是 值不一定一样 return v&(length -1); } @Override public String toString() { StringBuilder sb = new StringBuilder("{"); for(int i=0;i<table.length;i++) { Node3 temp = table[i]; while(temp!=null) { sb.append(temp.key+":"+temp.value+","); temp = temp.next; } } sb.setCharAt(sb.length()-1, '}'); return sb.toString(); } public static void main(String[] args) { TestHashMap03<Integer,String> m1 = new TestHashMap03<>(); m1.put(110, "aa"); m1.put(120, "bb"); m1.put(130, "cc"); System.out.println(m1); System.out.println(m1.get(110)); } }
- Treemap Treemap是使用红黑树来实现的里面存储了数据,左结点、右结点、父节点 以及结点颜色
- HashMap的效率高于TreeMap,在需要排序时才选用TreeMap
public class TestTreeMap { public static void main(String[] args) { Map<Integer, String> treemap1= new TreeMap<>(); treemap1.put(20, "aa"); treemap1.put(30, "bb"); treemap1.put(40, "cc"); treemap1.put(50, "dd"); //TreeMap会按照key递增的方式排序 HashMap不会 for(Integer key:treemap1.keySet()) { System.out.println(key+"--"+treemap1.get(key)); } Map<Emp, String> treemap02 = new TreeMap<>(); treemap02.put(new Emp(100,"张三",50000), "张三是"); treemap02.put(new Emp(101,"张四",5000), "张四是"); treemap02.put(new Emp(102,"张五",500), "张五是"); treemap02.put(new Emp(103,"张六",50), "张六是"); for(Emp key:treemap02.keySet()) { System.out.println(key+"--"+treemap02.get(key)); } } } class Emp implements Comparable<Emp>{ int id; String name; double salary; public Emp(int id, String name, double salary) { super(); this.id = id; this.name = name; this.salary = salary; } @Override public String toString() { return "id:"+id+" name"+name+" salary"+salary; } @Override public int compareTo(Emp o) {/*TreeMap可以自己比较integer 与String 如果要比较 自定义的对象 需要继承 Comparable 接口 正数 大于 ;负数 小于;零是相等 */ if(this.salary>o.salary){ return 1; }else if(this.salary<o.salary){ return -1; }else { if(this.id>o.id) { return 1; }else if(this.id<o.id){ return -1; }else { return 0; } } } }
- HashTable 线程安全,效率低 不允许key或是value为null
- HashMap 线程不安全 效率高 允许 key或是value 为null
- Map 就是使用键值对(key and value)来进行存储的,map类中的存储的键值对是通过键进行标识,所以键值对是不可以重复的
-
Set接口
- set与list接口是继承了collection接口set 中有collection中的一切方法但是set中没有顺序也不能重复
- TreeSet的底层是由TreeMap来实现的,通过key来存储Set的元素,TreeSet中需要对存储的元素进行排序,所以我们需要实现Comparable接口【集合中自定义的比较】
public class TestTreeSet { public static void main(String[] args) { Set<Integer> set = new TreeSet<>(); set.add(200); set.add(100); set.add(300); for(Integer key:set) {//使用增强for循环直接遍历set System.out.println(key); } Set<Emp2> set02 = new TreeSet<>(); set02.add(new Emp2(100,"王",5000)); set02.add(new Emp2(200,"王1",50001)); set02.add(new Emp2(300,"王2",50002)); for(Emp2 e:set02) {//使用增强for循环直接遍历set System.out.println(e); } } } class Emp2 implements Comparable<Emp2 >{ int id; String name; double salary; public Emp2(int id, String name, double salary) { super(); this.id = id; this.name = name; this.salary = salary; } @Override public String toString() { return "id:"+id+" name"+name+" salary"+salary; } @Override public int compareTo(Emp2 o) {/*TreeMap可以自己比较integer 与String 如果要比较 自定义的对象 需要继承 Comparable 接口 正数 大于 ;负数 小于;零是相等 */ if(this.salary>o.salary){ return 1; }else if(this.salary<o.salary){ return -1; }else { if(this.id>o.id) { return 1; }else if(this.id<o.id){ return -1; }else { return 0; } } } }
-
使用迭代器遍历list set map(iterator)
public class TestIteartor { public static void main(String[] args) { testIteratorMap(); } public static void testInteratorList() { List<String> list = new ArrayList<>(); list.add("aa"); list.add("bb"); list.add("cc"); //使用iterator遍历List for(Iterator<String>iter = list.iterator();iter.hasNext();) { //String temp = iter.next(); //System.out.println(temp); System.out.println(iter.next()); } } public static void testInteratorSet() { Set<String> set = new HashSet<>(); set.add("aa"); set.add("bb"); set.add("cc"); //使用iterator遍历Set for(Iterator<String>iter = set.iterator();iter.hasNext();) { //String temp = iter.next(); //System.out.println(temp); System.out.println(iter.next()); } } public static void testIteratorMap() { Map<Integer, String >map = new HashMap<>(); map.put(100, "aa"); map.put(200, "bb"); map.put(300, "cc"); //使用iterator遍历map1 Set<Entry<Integer, String>>ss = map.entrySet();//将map中的结点放到set中循环遍历set for(Iterator<Entry<Integer, String>>iter = ss.iterator();iter.hasNext();) { Entry<Integer, String> temp = iter.next(); System.out.println(temp.getKey()+"--"+temp.getValue()); //System.out.println(iter.next().getKey()+"--"+iter.next().getValue());不能这样写使用了两次next()方法 } //使用iterator遍历map2 Set<Integer> key = map.keySet(); for(Iterator<Integer>iter = key.iterator();iter.hasNext();) { Integer key1 = iter.next(); System.out.println(key1+"--"+map.get(key1)); } } }
-
遍历集合的方法总结
- 遍历list四种方式
- 普通循环
- 增强for循环
- 使用iterator迭代器for循环
- 使用iterator循环 while循环
- 遍历set 两种方式
- 使用增强for循环
- 使用iterator迭代器
- 遍历map两种方式(实际上是遍历set 先是通过方法获取set 在进行set 的遍历)
- 根据key获得keyset 遍历keyset 获取value
- 使用entryset 直接获得键值对的集合
- 遍历list四种方式
-
Collections工具类
//测试Collections工具类的使用 public class TestCollections { public static void main(String[] args) { List<String> list = new ArrayList<>(); for(int i=0;i<10;i++) { list.add("aa"+i); } System.out.println(list); Collections.shuffle(list);//随机排列list中的元素 System.out.println(list); Collections.reverse(list);//倒序排列 System.out.println(list); Collections.sort(list);//按照递增的顺序排列 自定义的类就使用Comparable接口进行比较 System.out.println(list); Collections.binarySearch(list,"aa");//二分法查找list中的元素,也叫折半查找 找到就返回索引 } }
-
使用map与list存储表格数据 ORM思想 (对象关系映射)
- 每一行数据使用一个map存储 整个表格使用一个list存储
- 每一行数据使用一个javabean对象, 整个表格使用一个map对象
public class TestStoreData { public static void main(String[] args) { Map<String, Object> row1 = new HashMap<>(); row1.put("id",1001 ); row1.put("name","wang1" ); row1.put("薪水",20000 ); row1.put("入职日期","2008.5.5" ); Map<String, Object> row2 = new HashMap<>(); row2.put("id",1002 ); row2.put("name","wang2" ); row2.put("薪水",30000 ); row2.put("入职日期","2018.5.5" ); Map<String, Object> row3 = new HashMap<>(); row3.put("id",1003 ); row3.put("name","wang3" ); row3.put("薪水",3000 ); row3.put("入职日期","2020.5.5" ); //遍历其中的数据 List<Map<String, Object>> table1 = new ArrayList<>(); table1.add(row1); table1.add(row3); table1.add(row2); for(Map<String, Object>row :table1) { //先遍历List 获得Map Set<String>keyset = row.keySet();//通过KeySet();方法 获得keySet for(String key: keyset) {//通过keySet遍历出值 System.out.print(key+":"+row.get(key)+"\t"); } System.out.println(); } } }
public class TestStoreData02 { public static void main(String[] args) { User user1 = new User(1001, "张三", 20000, "2018.5.5"); User user2 = new User(1002, "张四", 2000, "2028.5.5"); User user3 = new User(1003, "张五", 200, "2038.5.5"); List<User>list = new ArrayList<>(); list.add(user1); list.add(user2); list.add(user3); for(User e: list) { System.out.println(e); } Map<Integer, User> map = new HashMap<>(); map.put(1001, user1); map.put(1002, user2); map.put(1003, user3); Set<Integer>keyset = map.keySet(); for(Integer key: keyset) { System.out.println(key+"---"+map.get(key)); } } } class User{ private int id; private String name; private double salary; private String hiredate; //一个完整的Javabean对象需要完整的set get 方法 也需要无参的空的构造器 public User(){ } public User(int id, String name, double salary, String hiredate) { super(); this.id = id; this.name = name; this.salary = salary; this.hiredate = hiredate; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double isSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public String getHiredate() { return hiredate; } public void setHiredate(String hiredate) { this.hiredate = hiredate; } @Override public String toString() { return "id:"+id+" name:"+name+" salary"+salary+" date"+hiredate; } }