Java学习系列(7)Java面向对象之集合框架详解(上)
Java集合
有时也将集合称为容器类,它的作用就是用来“装对象”的。这里要注意的是集合也可以是对象。下面先看一张图:
HashSet:底层用一个数组存元素 --而且这个数组的长度永远是2的N次方。
HashSet底层就是HashMap实现的。
HashSet的构造器:HashSet(int initialCapacity, float loadFactor)
--initialCapacity:控制底层数组的长度。
如果传入数组长度不是2的N次方,HashSet会自动扩展到2的N次方。
--loadFactory:当HashSet感觉到底层数组快满时,它会再次创建一个长度是原来数组长度2倍的数组。原有的数组就变成垃圾,并且要把原有数组中的元素复制到新数组中,这个过程也叫“重hash”。loadFactory越小,越耗内存;loadFactory越大,性能越低。
举例说明1:
public class Test { public static void main(String[] args) { //没有泛型限制的集合 Collection c1 = new HashSet(); c1.add(1);//本来1不是对象,无法装入集合,但由于jdk提供的自动装箱功能,它会将1包装成对象 c1.add(new Date()); //加入泛型限制的集合,意味着该集合只能装“指定类型”的对象。 //jdk1.7可使用"菱形语法"将new HashSet<String>换成new HashSet<> Collection<String> c2 = new HashSet<String>(); //c2.add(1);错误:只能装String类型的对象 c2.add("张三"); c2.add("李四"); c2.add("王五"); //判断集合是否包含指定元素 System.out.println(c2.contains("张三")); System.out.println(c2.contains("赵六")); //遍历Set集合,有两种方式:1)foreach循环,2)迭代器 System.out.print("使用foreach循环遍历:"); for(String e : c2){ System.out.print(e+" "); } Iterator<String> it = c2.iterator(); System.out.print("\n使用迭代器遍历:"); while(it.hasNext()){ System.out.print(it.next()+" "); } System.out.println(); Collection<String> c3 = new HashSet<String>(); c3.add("张三"); c3.add("王五"); c3.add("赵六"); //求差集c2-c3 // c2.removeAll(c3); // System.out.println("c2与c3差集:"+c2); //求并集 // c2.addAll(c3); // System.out.println("c2与c3并集"+c2); //求交集 c2.retainAll(c3); System.out.println("c2与c3交集:"+c2); } }
举例说明2:
public class TestHashSet { public static void main(String[] args) { HashSet<String> hs = new HashSet<String>(3);//底层数组容量会自动会展到4(注意:2的N次方) hs.add("1"); hs.add("2"); hs.add("3"); hs.add("4"); hs.add("5"); //当HashSet感觉到底层数组快满时,它会再次创建一个长度是原来数组长度2倍的数组,此时新的数组长度为8 System.out.println(hs); } }HashSet存入机制:
1)当有元素加进来时,HashSet会调用该对象的hashCode()方法,得到一个int值;
2)根据hashCode()返回的int值,计算出它在HashSet的存储位置(数组中的索引);
3)如果要加入的位置是空的,直接方法即可。如果要加入的位置已经有元素,此处就会形成“链表”,数组越满,就越有可能出现链表。
HashSet取元素机制:
1)当有元素加进来时,HashSet会调用该对象的hashCode()方法,得到一个int值;
2)根据hashCode()返回的int值,计算出它在HashSet的存储位置(数组中的索引);
3)如果该位置恰好是要找的元素,直接取出即可。如果该位置有“链表”,HashSet要“挨个”地搜索链表里的元素。
HashSet怎么才会认为两个对象是相等的?(要求自定义类的hashCode()和equals()方法是一致的,即方法中所用到的关键属性要一致)
1、两个对象的hashCode()返回值相等;
2、两个对象通过equals比较返回true。
LinkedHashSet(HashSet的一个子类):它与HashSet的存储机制相似,但LinkedHashSet额外地有一个链表,这个链表可以保证LinkedHashSet能记住元素的添加顺序。
TreeSet(sortedset接口的实现类):它保证Set里添加的元素后是“大小排序”的。底层用一个“红黑树”存放元素。
举例说明(使用TreeSet要求集合元素必须是可以比较大小的):
public class TestTreeSet1 { public static void main(String[] args) { TreeSet<Integer> ts = new TreeSet<Integer>(); ts.add(3); ts.add(1); ts.add(2); ts.add(5); ts.add(4); System.out.println(ts); } }
public class TestTreeSet2 { public static void main(String[] args) { TreeSet<String> ts = new TreeSet<String>(); ts.add("t"); ts.add("r"); ts.add("e"); ts.add("e"); System.out.println(ts);//字符串的大小 } }TreeSet元素存入、检索的性能也比较好。
Java的比较大小有两种方式:
A. --自然排序。所有集合元素要实现Comparable接口。
B. --定制排序。要求创建TreeSet时,提供一个Comparator对象(负责比较元素大小)。
举例说明:
class Apple implements Comparable<Apple> { private double weight;// 规定:苹果重量大的苹果越大 public Apple(double weight) { this.weight = weight; } @Override //自然排序 public int compareTo(Apple obj) { return this.weight > obj.weight ? 1 : this.weight < obj.weight ? -1 : 0; } @Override public String toString() { return "Apple[weight=" + this.weight + "]"; } } public class TreeSetTest { public static void main(String[] args) { TreeSet<Apple> set = new TreeSet<Apple>(); set.add(new Apple(2.3)); set.add(new Apple(1.2)); set.add(new Apple(3.5)); for (Apple ele : set) { System.out.println(ele); } } }
class Bird { private String name; public String getName() { return name; } public Bird(String name) { this.name = name; } @Override public String toString() { return "Bird[name=" + name + "]"; } } public class TreeSetTest2 { public static void main(String[] args) { // 如果集合元素本身没有实现Comparable接口 // 那就要求创建TreeSet时传入一个Comparator对象 TreeSet<Bird> set = new TreeSet<Bird>(new Comparator<Bird>() { @Override public int compare(Bird o1, Bird o2) { if (o1.getName().compareTo(o2.getName()) > 0) { return -1; } else if (o1.getName().compareTo(o2.getName()) < 0) { return 1; } else { return 0; } } }); set.add(new Bird("aabc")); set.add(new Bird("abc")); set.add(new Bird("Z")); set.add(new Bird("dx")); System.out.println(set); } }ArrayList(JDK1.2)与Vector(JDK1.0,已经过时)都是基于数组实现的,它们的区别如下:
ArrayList(可根据索引来存取元素)是线程不安全的,Vector是线程安全的。
--ArrayList的性能比Vector要好,即使在多线程环境下,可以使用Collections集合工具类的synchronizedXxx方法把ArrayList包装成线程安全的。
而LinkedList底层是基于链表实现的,通常认为它的性能比不上ArrayList,它们的区别如下:
ArrayList:由于可以根据底层数组的索引存取元素,所以性能非常快,但当插入元素、删除元素时性能低。
LinkedList:由于底层采用了链表来存储元素,因此根据索引存取元素性能较慢,但当插入、删除元素时性能非常快。
举例说明:
public class Test { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("2"); list.add("1"); list.add("5"); list.add("3"); System.out.println(list); list.add(2,"在索引2处插入元素"); System.out.println(list); list.set(2,"在索引2处替换的元素"); System.out.println(list); list.remove(2); System.out.println(list); //遍历list(类似数组) for(int i =0;i<list.size();i++){ System.out.print(list.get(i)+" "); } } }Deque集合:功能被限制的线性表。
//把Deque当成栈用 public class Test { public static void main(String[] args) { //把Deque当成栈用 Deque<String> dq = new ArrayDeque<String>(); //进栈(压栈):后进先出 dq.push("a"); dq.push("b"); dq.push("c"); dq.push("d"); //打印首先出栈的元素 System.out.println(dq.pop()); //打印访问栈顶的元素 System.out.println(dq.peek()); } }
//把Deque当成队列用:先进先出 public class Test2 { public static void main(String[] args) { //把Deque当成队列用:先进先出 Deque<String> dq = new ArrayDeque<String>(); //从队列尾部入队 dq.offer("a"); dq.offer("b"); dq.offer("c"); dq.offer("d"); //从队列头部出队 System.out.println(dq.poll()); //打印访问队头元素 System.out.println(dq.peek()); } }Collections集合工具类:
public class TestCollections { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); System.out.println(list); //对集合进行反转 Collections.reverse(list); System.out.println(list); //把b, c位置进行交换 Collections.swap(list, 1, 2); System.out.println(list); //将list集合元素进行随机排列 Collections.shuffle(list); System.out.println(list); } }
今天内容比较多,而且有些和数据结构相关,所以理解起来可能会有些困难。编程我觉得还是要多练,只要你肯多练,就不会那么难了。今天就讲到这,明天开始讲Map。