java集合基础

来源:互联网 发布:java培训班达内免费 编辑:程序博客网 时间:2024/06/16 05:13

一.理解集合

  • Collection:List、Set
List:ArraryList、LinkedList、Vector

  • Map:Hashtable、HashMap、WeakHashMap
集合类存放于Java.util包中。集合类存放的都是对象的引用,而非对象本身。通俗的说,集合就是一个放数据对象引用的容器 

集合类型主要有3种:set(集)、list(列表)和map(映射)。

1.collection接口

Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。JDK提供的类都是继承自Collection的“子接口”(List和Set)。
遍历Collection中的每一个元素:使用iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。

Iterator it = collection.iterator(); // 获得一个迭代子    while(it.hasNext()) {      Object obj = it.next(); // 得到下一个元素    }


collection方法boolean add(E o)确保此 collection 包含指定的元素(可选操作)。boolean addAll(Collection<? extends E> c)将指定 collection 中的所有元素都添加到此 collection 中(可选操作void clear() 移除此 collection 中的所有元素(可选操作)。
boolean contains(Object o) 如果此 collection 包含指定的元素,则返回 trueboolean containsAll(Collection<?> c) 如果此 collection 包含指定 collection 中的所有元素,则返回trueboolean equals(Object o) 比较此 collection 与指定对象是否相等。int hashCode() 返回此 collection 的哈希码值。boolean isEmpty() 如果此 collection 不包含元素,则返回trueIterator<Eiterator() 返回在此 collection 的元素上进行迭代的迭代器。boolean remove(Object o) 从此 collection 中移除指定元素的单个实例,如果存在的话(可选操作)。
boolean removeAll(Collection<?> c)移除此 collection 中包含在指定 collection 中的所有元素(可选操作)。
boolean retainAll(Collection<?> c)保留此 collection 中包含在指定 collection 的元素(可选操作)。
int size()返回此 collection 中的元素数。
Object[] toArray()返回包含此 collection 中所有元素的数组。
<T> T[] toArray(T[] a)返回包含此 collection 中所有元素的数组;返回数组的运行时类型与指定数组的运行时类型相同


1.1 List接口

List 接口继承了 Collection 接口以定义一个允许重复项的有序集合。

list的特殊方法List subList(int fromIndex, int toIndex)处理subList()时,位于fromIndex 的元素在子列表中,而位于toIndex 的元素不是。

Iterator:只能正向遍历集合,适用于获取移除元素。ListIerator:继承Iterator,可以双向列表的遍历,同样支持元素的修改。

list方法举例:

import java.util.Collections;import java.util.LinkedList;import java.util.List;public class TestSet {    public static void main(String[] args) {        List<Integer> list=new LinkedList<Integer>();       for(int i=0;i<=9;i++){           list.add(i);       }        Collections.shuffle(list);//随机排序        System.out.println("shuffle:"+list);        Collections.reverse(list);        System.out.println("reverse:"+list);//原来list序列基础上逆序输出        Collections.sort(list);        System.out.println("sort:"+list);//正序输出        //折半查找        System.out.println(Collections.binarySearch(list, 4));    }}
输出结果:

shuffle:[6, 2, 7, 3, 8, 4, 9, 0, 5, 1]reverse:[1, 5, 0, 9, 4, 8, 3, 7, 2, 6]sort:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]4

list接口扩容机制

集合是我们在Java编程中使用非常广泛的,容器的量变得非常大的时候,它的初始容量就会显得很重要了,因为扩容是需要消耗内存的。同样的道理,Collection的初始容量也显得异常重要。所以:对于已知的情景,请为集合指定初始容量。

举例:

import java.util.ArrayList;import java.util.List;public class User {    private String userName;        private Integer age;        public User(Integer age,String userName){        this.age=age;        this.userName=userName;    }    public String getUserName() {        return userName;    }        public void setUserName(String userName) {        this.userName = userName;    }        public static void main(String[] args) {            User user = null;              long begin1 = System.currentTimeMillis();              List<User> list1 = new ArrayList<User>();//不指定初始容量              for(int i = 0 ; i < 100000; i++){                  user = new User(i,"caka"+i);                  list1.add(user);              }              long end1 = System.currentTimeMillis();              System.out.println("list1 time:" + (end1 - begin1));                            long begin2 = System.currentTimeMillis();              List<User> list2 = new ArrayList<User>(100000);  //指定初始容量            for(int i = 0 ; i < 100000; i++){                  user = new User(i,"caka"+i);                  list2.add(user);              }              long end2 = System.currentTimeMillis();              System.out.println("list2 time:" + (end2 - begin2));      }}
输出结果:

list1 time:43list2 time:16
 从上面的运行结果可以看出ArrayList的扩容机制是比较消耗资源的

ArrayList每次新增一个元素,就会检测ArrayList的当前容量是否已经到达临界点,如果到达临界点则会扩容1.5倍。ArrayList不是线程安全的,只能用在单线程环境下。

线程不安全验证举例:

add方法的底层实现

  public boolean add(E e) {            ensureCapacity(size + 1);             elementData[size++] = e;            return true;        }          

size++这边,主要分为两个步骤:1)将add的元素放到size位置;2)将size加1

假设size=10.若线程A在10位置存放了值caka,获得size=10,但还没来得及将size加1写入主存。此时线程B在也在10位置存放了值july,也获得size=10,而后A、B分别将size加1后写入主存,size=11,即两个线程执行两次add()后size只加了1。

举例如下:

import java.util.ArrayList;public class User {      static ArrayList<String> list = new ArrayList<String>();      public static void main(String[] args) {         for (int i = 0; i < 20; i++) {              new Thread(new Runnable() {                  @Override                  public void run() {                      list.add("caka");                  }             }).start();         }         while (true) {             try {                 Thread.sleep(1000);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println("size:"+list.size());         }     } }

输出结果应该是20,但是实际却可能小于20.ArryList在多线程环境下不安全

参考资料说要想实现线程安全需要这样写:List<String> list = Collections.synchronizedList(new ArrayList<String>())

对于List中常用的ArrayList的总结:

  • 无参构造方法构造的ArrayList的容量默认为10
  • ArrayList在每次增加元素,当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍加1。如果设置后的新容量还不够,则直接新容量设置为传入的参数,再用Arrays.copyof()方法将元素拷贝到新的数组。

从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则使用LinkedList。

  • 在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。
List总结:
  • LinkedList类允许为空,是非同步的;如果要实现同步,需要自己在创建List的时候构造一个同步List。(List list = Collections.synchronizedList(new LinkedList(....)));
  • ArraryList实现了可变大小的数组。允许所有元素,包括null,是非同步的。每个ArraryList实例都有一个容量,用于存储元素数组的大小。该容量可随着不断增加新元素而自动增加。每次增加为原来的一半。
  • Vector类类似与ArraryList类。但Vetor是同步的。所以当一个Iterator被创建而且被使用的时候,另一个线程改变了Vetor的状态。比如增加或者删除元素。这时条用Iterator的方法就会抛出ConcurrentModificationException. 需要捕获该异常。Vector数据增长每次增长为原来的一倍。

元素。

 1.2 set接口

set接口不允许集合中存在重复项。具体的Set 实现类依赖添加的对象的 equals()方法来检查等同性“集合框架”支持 Set 接口两种普通的实现:HashSet 和TreeSet添加到 HashSet 的对象需要采用恰当分配散列码的方式来实现hashCode() 方法。添加到TreeSet 的元素必须是可排序的,必须实现Comparable

set集合实例:

import java.util.HashSet;import java.util.Set;import java.util.TreeSet;public class TestSet {    public static void main(String[] args) {        Set<String> set = new HashSet<String>();        set.add("旺仔牛奶");        set.add("旺仔牛奶");//重复性验证,结果只输出一次        set.add("b旺仔牛奶");        set.add("a旺仔牛奶");        set.add("c旺仔牛奶");        set.add("d旺仔牛奶");        System.out.println(set);        Set<String> sortedSet = new TreeSet<String>(set);        System.out.println(sortedSet);//排序输出    }}
输出结果:

[b旺仔牛奶, 旺仔牛奶, d旺仔牛奶, c旺仔牛奶, a旺仔牛奶][a旺仔牛奶, b旺仔牛奶, c旺仔牛奶, d旺仔牛奶, 旺仔牛奶]

1.3 queue接口

Queue继承了Collection接口。LinkedList实现了Queue接 口。Queue用于模拟队列。通常是指“先进先出(FIFO)”。新元素插入到队列的尾部,取出元素会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。

Queue接口中定义了如下的几个方法:

add           增加一个元素                                    如果队列已满,则抛出一个IIIegaISlabEepeplian异常

put            添加一个元素                                    如果队列满,则阻塞

offer         添加一个元素并返回true                  如果队列已满,则返回false

remove      移除并返回队列头部的元素              如果队列为空,则抛出一个NoSuchElementException异常

poll           移除并返问队列头部的元素               如果队列为空,则返回null

take          移除并返回队列头部的元素               如果队列为空,则阻塞

element    返回队列头部的元素                        如果队列为空,则抛出一个NoSuchElementException异常

peek          返回队列头部的元素                        如果队列为空,则返回null

阻塞队列

java.ulil.concurrent包有阻塞队列的4个变种。

LinkedBlockingQueue:LinkedBlockingQueue的容量默认情况下为Integer.MAX_VALUE,但是也可以选择指定其最大容量,它是基于链表的队列,此队列按 FIFO(先进先出)排序元素。
ArrayBlockingQueue 在构造时需要指定容量, 并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理。


PriorityBlockingQueue是一个带优先级的 队列,而不是先进先出队列。元素按优先级顺序被移除,PriorityQueue保存队列元素的顺序不是按照元素添加的顺序来保存的,而是在添加元素的时候对元素的大小排序后再保存的。因此在PriorityQueue中使用peek()或pool()取出队列中头部的元素,取出的不是最先添加的元素,而是最小的元素。该队列也没有上限有容量限制的,与ArrayList一样,所以在优先阻塞 队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError,但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。

package example;import java.util.PriorityQueue;public class PriorityQueueTest {    public static void main(String[] args){        PriorityQueue priorityQueue = new PriorityQueue();        priorityQueue.offer(6);        priorityQueue.add(-3);        priorityQueue.add(20);        priorityQueue.offer(18);        System.out.println(priorityQueue);    }}

输出结果:

[-3, 6, 20, 18]

DelayQueue(基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。放入DelayQueue的元素还将要实现compareTo方法,DelayQueue使用这个来为元素排序。

public interface Delayed extends Comparable<Delayed> {               long getDelay(TimeUnit unit);          }  

2.map接口

该接口描述了从不重复的键到值的映射。键和值都可以为 null。但是,不能把Map 作为一个键或值添加给自身。

相关方法:

Object put(Object key, Object value)返回值是被替换的值Object remove(Object key)
void putAll(Map mapping)
void clear()
Object get(Object key)返回指定键关联的值
boolean containsKey(Object key)包含指定键的映射返回true
boolean containsValue(Object value)此 Map 将一个或多个键映射到指定值,返回 true
int size()
boolean isEmpty()

集合框架提供两种常规的Map实现HashMapTreeMap。Map中插入、删除和定位元素,HashMap是最好的选择,(使用HashMap要求添加的键类明确定义了hashCode()实现)如果要按顺序遍历键,那么TreeMap会更好。根据集合大小,先把元素添加到HashMap,再把这种映射转换成一个用于有序键遍历的TreeMap 。

map对象遍历的三种方法:

1.使用entries来遍历(如果遍历的是一个空的map对象,for-each循环将抛出NullPointerException,因此在遍历前你总是应该检查空引用。

Map<Integer, Integer> map = new HashMap<Integer, Integer>();    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {        System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());    }  
2.通过keySet或values来实现遍历
Map<Integer, Integer> map = new HashMap<Integer, Integer>();  //遍历map中的键   for (Integer key : map.keySet()) {      System.out.println("Key = " + key);    }  //遍历map中的值  for (Integer value : map.values()) {      System.out.println("Value = " + value);  }  
3.使用Iterator遍历
Map<Integer, Integer> map = new HashMap<Integer, Integer>();    Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();    while (entries.hasNext()) {        Map.Entry<Integer, Integer> entry = entries.next();        System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());    }  

补充一种:通过Map.value()遍历所有value,但不能遍历Key.

for(Integer v : map.values()) {    System.out.println("value =" + v);}

注意点:

由于作为Key的对象将通过计算其散列函数来确定与之相对应的value的位置。所以作为Key的对象都必须实现hashCode和equals方法。这两种方法继承自根类Object,如果需要自定义的类当作key的话,散列函数的定义是:如果两个对象相同,比如object1.equals(object2)= true; 则他们的hashCode必须相同。但如果两个对象不同,他们的hashCode不一定不同。

如果相同的对象有不同的hashCode,哈希表的操作会出现get方法返回null.所以一定要同时复写equals和hashCode方法。

hashCode()缺省情况下返回的是对象的内存地址。所以每个java对象都可以生成hashCode;equals是根类的方法,比较两个对象地址是否相同。

HashMap类:

HashMap允许为null。是非同步的,HashMap的迭代器(Iterator)是fail-fast迭代器。如果某个集合对象创建了Iterator或者ListIterator,然后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。HashMap使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当给put()方法传递键和值时,先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。hashMap使用containsKey和containsValue判断是否包含key值或者value值。Java 5提供了ConcurrentHashMap,线程安全。

HashTable类:

HashTable不允许null值。是同步的。使用Enumeration作为迭代器。速度更慢。

在哈希表中添加一个key/键值对:HashtableObject.Add(key,);
在哈希表中去除某个key/键值对:HashtableObject.Remove(key);
从哈希表中移除所有元素: HashtableObject.Clear();
判断哈希表是否包含特定键key: HashtableObject.Contains(k





原创粉丝点击