黑马程序员_java集合类

来源:互联网 发布:windows硬盘分区 编辑:程序博客网 时间:2024/06/13 02:37

集合类中的接口主要有:
Collection  ----->  Set (不能包含重复的元素,无序)  ----->  SortedSet
Collection  ----->  List (可以包含重复的元素,是一个有序集合,可以存放空元素)
Map (包含了Key-Value对,不能有重复的Key)  ----->  SortedMap

Set常用的实现类有:
HashSet: 此集合是基于哈希算法实现的,用数组保存数据。其中元素不能重复,查找效率相当高,缺省负载因子是0.75,负载因子通常不需要修改;依靠HaspMap来实现。性能比TreeSet要好,
         存放到此集合中的对象应该重写hashCode()和equals()。我们通常应该使用HashSet,除非我们需要对存放的元素进行排序,那就用TreeSet。
TreeSet: 实现了SortedSet接口,依靠TreeMap来实现。是一个有序集合,其中元素按照升序排列,缺省是按照自然顺序排列,这就意味着存放到此集合中
         的元素需要实现Comparable接口,我们可以在构造TreeSet对象的时候指定一个实现了Comparator接口的比较器对象。
LinkedHashSet: 用链表保存数据。具有可预知迭代顺序的Set接口的哈希表和链接列表实现。
        此实现与HashSet的不同之外在于后者维护着一个运行于所有条目的双重链接列表。
        此链接列表定义了迭代顺序,即按照将元素插入到set中的顺序(插入顺序)进行迭代。
        注意,插入顺序不受在set中重新插入的元素的影响。

List常用的实现类有:
       ArrayList: 是一种可增长数组的实现,底层采用数组实现。使用ArrayList的优点在于对get和set的调用花费常数时间。
    其缺点是新项的插入和现有项的删除代价昂贵,除非变动是在ArrayList的末端进行。
    我们通常应该使用此集合替代Vector,除非确实需要同步列表。但是我们也可以使用Collections中的静态方法
           synchronizedList(List<T> list)来得到一个线程安全的ArrayList对象。
    Collections还提供了相应的方法来得到线程安全的Collection, List, Set, Map对象。
LinkedList: 底层采用一般的双向链表(double-linked list)完成,其中每个对象元素除了数据本身以外,还有两个分别指向前一个元素和后一个元素的引用。
       如果我们需要经常在List的开始处增加元素,或者在List中进行插入和删除操作,我们应该使用此集合。否则使用ArrayList性能会好一些。
     其优点是新项的插入和现有项的删除均开销很小(这里假设变动项的位置是已知的)。
     其缺点是它不容易作索引,因此对get的调用是昂贵的,除非调用非常接近表的端点(如果对get的调用是接近表后部的项进行,
     那么搜索的进行可以从表的后部开始)。
Vector: 线程安全,底层采用数组实现,此类中的所有方法都是同步的,效率较低。但是比用synchronizedList(List<T> list)方法来得到的List对象性能稍微要好一些,
       但是Vector有一些继承的操作使用起来要格外小心。如果不用考虑线程安全,我们应该用ArrayList集合。
Stack(栈): 此类从Vector继承而来,所以有了一些栈不应该有的特性,比如从Vector继承而来的ElementAt()方法等,
       如果需要使用栈,我们可以自己去编写,可由LinkedList去实现一个栈。

Map常用的实现类有:
HashMap: 用数组保存数据。对Key进行散列,其中不能有重复的键,方法keySet()返回Set存放着Key中的元素, 方法values()返回Collection存放着Value中的元素,
       方法entrySet()返回Set存放Map.Entry类型的元素,一个Map.Entry对象表示一个Key-Value对。Map.Entry接口中主要提供了方法getKey()和getValue()。
       此集合性能比TreeMap性能要好。我们通常应该使用HashMap,除非我们需要对存放的元素进行排序,那就用TreeMap。
       Hashtable: 线程安全,此类中的所有方法都是同步的,效率较低。如果不用考虑线程安全,我们应该用HaspMap。
           其中不能有重复的键,如果试图添加的键对象与原来的键对象重复,那么就将此键原来所对应的值替换为后来添加的值。
           如:ht.put("str", "hello"); ht.put("str", "world"); 那么最终str对应的值是world。
           用作HashMap和Hashtable键对象的类,都应该覆盖Object.hashCode()和Object.equals();
TreeMap: 实现了SortedMap接口,按照Key进行升序排列,缺省是按照自然顺序排列,这就意味着用作Key的元素需要实现Comparable接口,
         我们可以在构造TreeSet对象的时候指定一个实现了Comparator接口的比较器对象。
LinkedHashMap: 用链表保存数据。Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。
        此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。
        注意,如果在映射中重新插入键,则插入顺序不受影响。       

(如果在调用m.put(k, v)前m.containsKey(k)返回了true,则调用时会将键 k 重新插入到映射m中。) 
Properties: 从Hashtable继承而来,表示了一个持久的属性集。也是存储Key-Value对,只是此类可以从输入流填充Key-Value对。
            属性列表中每个键及其对应值都是一个字符串。

链表有单向链表,循环链表,双向链表和双向循环链表。

我们可以用LinkedList实现栈数据结构。

 

Collection中的toArray()方法可以将集合转换成Object数组,<T> T[] toArray(T[] a)可以将集合转换成相应类型的数组。
注意,toArray(new Object[0]) 和 toArray() 在功能上是相同的。

Arrays.asList(T... a)可以将数组转换为集合类List。返回的List集合是个固定尺寸大小的集合,不能往里面添加元素和删除元素,只能修改元素的内容。

Arrays.asList(T... a)方法和Collection.toArray()方法一起,充当了基于数组的 API 与基于 collection 的 API 之间的桥梁。

Collection接口提供了一个方法iterator(),返回一个可迭代的Iterator对象。

Iterator迭代器给我们提供了一种通用的方法来访问集合中的元素,Iterator接口中只有三个方法:hasNext(), next(), remove();

Collections 类可以对集合类进行很多的操作,里面的方法全部是静态方法,其中的一个方法是sort(List<T> list) 可以对List类型的集合进行排序。
但是在List中的元素类型要实现了Comparable接口。


System类常用的方法有:
arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
exit(int status) 终止当前正在运行的 Java 虚拟机。
currentTimeMillis() 返回以毫秒为单位的当前时间。
setProperties(Properties props) 将系统属性设置为 Properties 参数。
setProperty(String key, String value) 设置指定键指示的系统属性。
getProperties() 确定当前的系统属性。
getProperty(String key) 获取指定键指示的系统属性。


       Runtime类是应用程序和系统环境之间的接口。
       Runtime类封装了java命令本身所启动的实例进程。也就是封装了java虚拟机进程,一个java虚拟机对应一个Runtime实例对象。
       Runtime实例对象只能用Runtime.getRuntime()来获得Runtime实例对象的引用。

我们可以通过Runtime对象获取当前的空闲内存和总内存等。

由于java虚拟机本身就是windows操作系统上的一个进程,在这个进程中可以启动其他windows程序的运行实例,
通过这种方式启动的其他应用程序的运行实例就称为子进程。
Runtime.getRuntime().exec() 就可以启动子进程。他的返回值就是代表一个子进程的Process类对象。
例如:windows下 Runtime.getRuntime().exec("notepad.exe a.txt"); 就会启动记事本程序,打开a.txt这个文件.

与时间和日期相关的常用类有Date,Calendar,DateFormat,SimpleDateFormat。
(Calendar类几乎替代了Date类,除非某些方法接受的参数只能是Date类型参数,那就只能用Date类了)

一个例子:
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日");
Date d = sdf1.parse("2011-03-01");
sdf2.format(d);  //这里返回String类型的日期字符串“2011年03月01日”

java.util.Timer主要用来实现定期执行任务的程序。
java.util.Timer里面的主要方法有schedule(TimerTask task, long delay)。
java.util.TimerTask 实现了Runnable接口,要执行的任务由他里面实现的run方法来完成。

来看如下例子:
class MyTimerTask extends TimerTask{

 private Timer timer;

 public MyTimerTask(Timer timer) {
  this.timer = timer;
 }

 public void run() {
  System.out.println("helloworld!");
  try {
   Runtime.getRuntime().exec("calc.exe");
  } catch(Exception e) {
   e.printStackTrace();
  }
  timer.cancel();     //结束任务线程
 }
}

Timer timer = new Timer();
timer.schedule(new MyTimerTask(timer), 3000);

 


       ArrayList与HashSet的比较
       ArrayList里面存储的元素是按照我们添加元素的顺序有序的排列,其中的元素可以是重复的对象。
       HashSet里面存储的元素并不是按照我们添加元素的顺序进行排列的,而是按照哈希值分别存储到不同的区域,其中的元素不能是重复的对象。


       有一种用哈希算法来提高从集合中查找元素的效率的方式,这种方式将集合分成若干个存储区域,每一个要存进来的对象可以算出一个值(哈希码值),然后将哈希码分组,每组分别对应某一个存储区域,然后根据一个对象的哈希码就可以确定该对象应该存储在哪个区域。

HashSet就是采用哈希算法来存取对象的集合,它内部采用对某个数字n进行取余的方式对哈希码进行分组和划分对象的存储区域,Object类中定义了一个hashCode()方法,
来返回每个java对象的哈希码,当从HashSet中查找某个对象时,java系统首先调用对象的hashCode()方法,获得该对象的哈希码,然后根据哈希码找到相应的存储区域,
最后取出该存储区域内的每个元素与该对象进行equals()方法比较,这样不用遍历集合中的所有元素就可以得到结论。

总的来说hashCode()的返回值是用来确定存取对象所在的区域,而真正比较两个对象在业务逻辑上是否等同的方法还是equals()方法。

可见HashSet集合具有很好的对象检索性能,但是HashSet存储对象的效率相对要低些。

所以说为什么存储在采用哈希算法来存取对象的集合里面的对象要同时重写equals()方法和hashCode()方法,因为如果两个对象采用equals()方法比较是否等同,
但是如果他们返回的哈希值不同,就很有可能分别存储在不同的存储区域,那么这种情况下两个相同的对象就被存储到这个集合里面了。
所以只有同时重写equals()方法和hashcode()方法,让用equals()方法比较是等同的两个对象返回相同的哈希值,才能保证HashSet集合里面存储对象的唯一性。

hashCode()方法返回一个哈希值,一般对象的哈希值是用内存地址算出来的,除非我们重写了hashCode()方法,
注意:hashCode()方法只有在采用哈希算法来存取对象的集合中才有价值。

有一个问题得注意:
       当一个对象被存储进HashSet集合之后,就不能修改这个对象中那些参与哈希值计算的字段,否则,对象修改后的哈希值与最初存储进HashSet时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象也将找不到对象,因为参与哈希值计算的字段被修改了,那么这个对象就有可能存储在另外的区域,这时还在原来的哪个区域找这个对象,肯定就无法找到了,这将导致无法从HashSet集合中单独删除当前对象,从而造成内存泄漏。

下面是关于hashCode()和equals()方法重写的例子:
package com.heima.exam;

public class PointHashCode {

 private int x;
 private int y;
 
 
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + x;
  result = prime * result + y;
  return result;
 }


 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  PointHashCode other = (PointHashCode) obj;
  if (x != other.x)
   return false;
  if (y != other.y)
   return false;
  return true;
 }

}

0 0
原创粉丝点击