Java基础——集合

集合和数组对比

 1、存储的对象类型:
  ①数组:基本数据类型和引用数据类型
  ②集合:引用数据类型
 2、数组存储数据的弊端:
  ①长度一旦初始化以后,就不可变,不像集合有自动扩容机制
  ②给数组元素赋值没有现成的方法可用,集合拥有put()、add()等方法

集合的层次关系

 1、Arrays和Collections这两个工具类都直接继承于Object
在这里插入图片描述
 2、Collection体系整体架构
在这里插入图片描述
 3、Collection继承于Iterator接口,注意Map并不是Collection下的子接口
在这里插入图片描述
 4、Map并非继承于Collection,而是一个独立的接口
在这里插入图片描述

集合中的API

 Collection中定义的接口方法:

boolean add(Object o); //添加元素
boolean addAll(Collection c); //将集合中的所有元素添加至当前集合
boolean remove(Object o); //删除集合中的指定元素
boolean removeAll(Collection c); //将集合中的所有元素从当前集合中删除
boolean retainAll(Collection c); //删除当前集合中指定集合里不存在的元素,即将当前集合整合为与指定集合的交集
void clear(); //清空集合
int size(); //集合中有效元素的个数
Object[] toArray(); //将集合中的元素转换成Object类型数组
boolean isEmpty(); //判断集合是否为空
boolean contains(Object o); //判断是否包含指定元素
boolean containsAll(Collection c); //判断是否包含指定集合中的所有元素
Iterator<E> iterator();//集合迭代器

 Collection中的迭代器Iterator方法:

Iterator iterator(); //迭代器构造方法
boolean hasNext(); //判断是否有下一个元素
Object next(); //获取当前元素
void remove(); //删除通过next()获取的元素,在next()之后使用,不可以单独使用

 List中定义的接口方法:

void add(int index,Object ele);//在指定位置添加元素
boolean addAll(int index,Collection eles);//在指定位置添加集合中的所有元素,会扁平化处理
Object get(int index);//获取指定位置的元素
Object remove(int index);//删除指定位置的元素,并将该元素返回
Object set(int index,Object ele);//设置或更改指定位置的元素
int indexOf(Object obj);//返回对象obj在List中首次出现的位置,无则返回-1
int lastIndexOf(Object obj);//返回对象obj在List中最后一次出现的位置,无则返回-1
List subList(int fromIndex,int toIndex);//从当前List中截取fromIndex到toIndex的左闭右开的List

  List中添加的元素所在的类之所以需要重写equals方法是因为可能会调用父接口Collection中的方法,比如说remove(Object obj),要根据equals的结果确定删除哪一个(实际上存放在集合当中的对象所在的类都需要重写equals()方法,因为都可能会执行remove(Object obj)等操作),还有其他的方法,也可以根据这个思想判断其他的类在什么情况下需要重写equals()。在执行remove(Object obj)等跟equals()方法有关的操作时,只是删除最先找到的那一个,而不是删除集合中所有equals()时相等的对象,因此要保证hashCode()和equals()方法的一致。
 List中的迭代器 ListIterator方法:

ListIterator listIterator(); //迭代器构造函数
boolean hasNext(); //判断是否有下一个元素
boolean hasPrevious(); //判断是否有上一个元素 
Object next(); //获取当前元素
Object previous(); //获取上一个元素
void remove(); //删除通过next()或previous()获取的元素,在next()或previous()之后使用,不可以单独使用
add(Object o); //添加指定元素
set(Object o); //修改指定元素
int nextIndex(); //获取当前元素所在位置

  TIP
   1️⃣当 list.indexOf(obj) == list.lastIndexOf(obj) 的时候说明这个集合中只含有一个obj元素,或者一个都没有,因为没有的时候都返回-1,可结合contains()判断某个元素在List中是否唯一。
   2️⃣ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历。但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历,Iterator就不可以;ListIterator可以通过nextIndex()和previousIndex()定位当前的索引位置Iterator 没有此功能;ListIterator有add()方法,可以向List中插入对象,而Iterator不能;ListIterator和Iterator都可实现删除对象,但是ListIterator可以通过set()方法实现对对象的修改,Iterator仅能遍历,不能修改。
 ArrayList中特有的方法:底层为数组

void trimToSize(); //修改集合容量为当前存储的有效元素个数,可节省因自动扩容等导致的非有效内存空间

 LinkedList中特有的方法:底层为循环双向链表

E getFirst(); //获取集合中的第一个元素
E getLast(); //获取集合中的最后一个元素

void addFirst(Object o); //在集合的第一个位置添加指定元素
void addLast(Object o); //在集合的最后一个位置添加指定元素

E removeFirst(); //删除集合中的第一个元素
E removeLast(); //删除集合中的最后一个元素
ArrayList和LinkedList的选用

 ArrayList、Vector、LinkedList都是List接口的实现类,该怎么选用呢?
 从实现方式上来看,ArrayList和Vector使用了数组的实现,可以认为ArrayList或者Vector封装了对内部数组的操作;LinkedList使用了循环双向链表数据结构,与基于数组的ArrayList相比是截然不同的。
 LinkedList中的链表由一系列表项连接而成,一个表项总是包含3个部分:元素内容,前驱表和后驱表:
在这里插入图片描述
 在JDK的实现中,无论LinkedList是否为空,链表内部都有一个header表项,它既表示链表的开始,也表示链表的结尾。表项header的后驱表项便是链表中第一个元素,表项header的前驱表项便是链表中最后一个元素,下图是含有3个表项的LinkedList结构示意:
在这里插入图片描述
 实现方式的不同导致ArrayList和LinkedList的适用场景不同:
  1、增加元素到列表尾端:
   ArrayList的实现:

public boolean add(E e){
   ensureCapacity(size + 1);//确保内部数组有足够的空间,size是实际存储的元素的个数
   elementData[size++]=e;//将元素加入到数组的末尾,完成添加,elementData是List中存放数据的数组
   return true;      
} 

    ArrayList中add()方法的性能取决于ensureCapacity(),ensureCapacity()的实现如下:

public vod ensureCapacity(int minCapacity){
  modCount++;
  int oldCapacity = elementData.length;//原数组长度
  if(minCapacity > oldCapacity){    //如果数组容量不足,进行扩容
      Object[] oldData = elementData;//这句代码多余还占用内存,应该直接使用elementData
      int newCapacity = (oldCapacity*3)/2+1;  //扩容到原容量的1.5倍
      if(newCapacitty < minCapacity)   //如果扩容后的新容量还小于最小需要的容量,则使用最小需要的容量作为扩容后的容量
         newCapacity = minCapacity ; 
      elementData = Arrays.copyof(elementData,newCapacity);//进行数组复制
  }
}

   ArrayList扩容的思想:在向ArrayList中添加元素(一个或多个,也可能添加一个集合或数组到这个ArrayList中)时,会先判断当前的容量是否够用,若不够用则进行扩容,扩容时会先扩容至原数组长度的1.5倍,若还是不够则直接取原数组长度+要添加的元素个数的和作为扩容后的容量,如果有扩容则需要将数据从原数组中拷贝至扩容后的数组,如果不需要扩容则效率极高,只需要给对应位置赋值即可。因此在声明ArrayList时最好初始化一个合理的长度(默认为10),一来避免占用内存,二来尽可能避免扩容。
   在JDK8中的扩容代码发生了变化:但思想没有变

private void grow(int minCapacity) {
	// overflow-conscious code
	int oldCapacity = elementData.length;
	int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容至原容量的1.5倍
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;//扩容后依然小于需要容量,则直接赋值为需要容量
	if (newCapacity - MAX_ARRAY_SIZE > 0)//如果容量超大
		newCapacity = hugeCapacity(minCapacity);
	// minCapacity is usually close to size, so this is a win:
	elementData = Arrays.copyOf(elementData, newCapacity);//复制数据
}

   LinkedList添加元素到末尾的实现:在header表项前添加一个表项

public boolean add(E e){
   addBefore(e,header);//将元素增加到header的前面
   return true;
}

   addBefore的实现:在header表项前新建一个表项,并修改前后的链表指向

private Entry<E> addBefore(E e,Entry<E> entry){
     Entry<E> newEntry = new Entry<E>(e,entry,entry.previous);//创建一个表项
     newEntry.provious.next = newEntry;//修改新建表项的前一个表项的后表项为新建表项
     newEntry.next.previous = newEntry;//修改新建表项的后一个表项的前表项为新建表项
     size++;//实际存储量+1
     modCount++;
     return newEntry;
}

   可见LinkeList由于使用了链表的结构,因此不需要维护容量的大小,不存在扩容。添加单个元素至列表末尾时,在ArrayList不发生扩容的情况下,使用ArrayList要比LinkedList好很多,因为LinkedList每次插入元素都需要新建一个Entry对象,并进行更多的赋值操作
   由于LinkeList不存在扩容,也就没有容量的概念,因此LinkedList没有指定容量的构造器,不像ArrayList,在初始化时可以指定容量,以避免发生扩容或占用过多的内存:

List<String> list = new ArrayList<>(5);//初始化时指定容量为5
//LinkedList没有new LinkedList<>(n);构造器
List<String> linkList = new LinkedList<>(Arrays.asList("abc","bbc"));

  2、增加元素到列表任意位置
   ArrayList的实现:除必要时进行扩容外还需要将插入位置之后的所有元素均向后挪动一位(期间可能发生两次数据复制,一次扩容,一次挪动),因此在ArrayList中实际存储的数据越多且插入的位置越靠前的时候性能越低下

public void add(int index,E element){
   if(index > size || index < 0)
      throw new IndexOutOfBoundsException(
        "Index:"+index+",size: "+size);
         ensureCapacity(size+1);//必要时进行扩容
         System.arraycopy(elementData,index,elementData,index+1,size-index);//将该位置之后的数组的元素都向后挪一位
         elementData[index] = element;//给该位置赋值
         size++;
}  

   LinkedList的实现:仅仅需要在该位置创建一个表项

public void add(int index,E element){
   addBefore(element,(index == size ? header : entry(index)));
}

   可见在增加元素到列表任意位置时使用LinkedList要比ArrayList好的多,对于LinkedList来说在任意位置插入元素时的开销都是一样的,而ArrayList的开销则要大得多。

  3、删除任意位置元素
   ArrayList的实现:remove()方法和add()方法是雷同的,在任意位置移除元素后,需要将其后的元素前移一个位置,删除元素的位置越靠前开销越大

public E remove(int index){
   RangeCheck(index);
   modCount++;
   E oldValue = (E) elementData[index];
  int numMoved = size-index-1;
  if(numMoved > 0)
     System.arraycopy(elementData,index+1,elementData,index,numMoved);
     elementData[--size]=null;
     return oldValue;
}

   LinkedList的实现:先判断要删除元素的位置在链表的前半段还是后半段,在前半段则从前往后找,否则从后往前找,找到后将其从链表中删除,删除的操作和添加是一样的

public E remove(int index){
  return remove(entry(index));         
}
private Entry<E> entry(int index){
  if(index<0 || index>=size)
      throw new IndexOutBoundsException("Index:"+index+",size:"+size);
      Entry<E> e = header;
      if(index<(size>>1)){//要删除的元素位于前半段
         for(int i=0;i<=index;i++)
             e=e.next;
     }else{
         for(int i=size;i>index;i--)
             e=e.previous;
     }
         return e;
}

   可见删除元素时LinkedList也要略胜一筹。

  4、遍历列表
   通常而言,在遍历时采用ArrayList要比LinkedList速度快,因为ArrayList遍历到某个元素时仅需要在数组中找到对应位置的元素即可,而LinkedList则需要从header表项一直查找到对应位置的表项才行。
  5、开发中常见的场景:
   ①新建一个List,并向该List中添加元素,通常对位置没有要求,都是将新元素添加至末尾,因此适合选用ArrayList;
   ②将从数据库中查出的数据放入一个List中,然后遍历该List,此时并不会向这个List中添加或减少元素,而是遍历的过程中会操作其每个元素的某个属性(为某个元素的某个属性赋新值),属于频繁遍历的场景,因此更适合使用ArrayList。
  综上所述,在开发和生产中使用ArrayList的场景要更多
  以上分析参考:ArrayList和LinkedList的对比

Set接口

 Set接口中没有新定义方法,Set的方法都继承于Collection。

HashSet、TreeSet、LinkedHashSet的选用

 1、HashSet(散列存放)
  Java.util.HashSet类实现了Java.util.Set接口:
   ①它不允许出现重复元素;
   ②不保证集合中元素的顺序;
   ③允许包含值为null的元素,但最多只能有一个null元素;
 2、TreeSet(有序存放)
  TreeSet是Set的一种变体——可以实现排序等功能,TreeSet可以确保集合元素处于某种排序状态(自然排序或定制排序,定制排序优先于自然排序),它在将对象元素添加到集合中时会自动按照某种比较规则(存放对象的类实现Comparable或Comparator接口的方式)将其插入到有序的对象序列中(在向TreeSet中添加数据时,一旦通过Comparable的compareTo或者Comparator的compare方法比较的结果为0时,即认为这两个对象相同,则数据不能被添加至TreeSet中)。主要有add()、remove()以及contains()等方法,它们都是复杂度为O(log (n))的方法。
 3、LinkedHashSet
  LinkedHashSet正好介于HashSet和TreeSet之间,它也是一个hash表,但它同时维护了一个双链表来记录插入的顺序,基本方法的复杂度为O(1)。
 HashSet的存取速度是最快的。
哈希算法:若没有哈希算法,往Set中存储数据的时候要保证新添加的数据和原来的都不一样,需要跟之前Set中的所有元素都进行比较,若全都不一样才可以存储进来,这样每次存储数据都要和所有原有数据进行比较,效率极低,且越来越低。所以数据存储的时候先根据hash算法求出一个哈希值,不同的数据求出来的hash值是不同的,这个哈希值就相当于数据在Set中的座位编号,这样新添加的数据就凭借这个编号自动寻找自己的位置,若原来位置上已经有了一个数据,再调用equals()方法,检查两个数据是否一样,若一样,则说明这两个数据是一样的,就不让该数据存储,若不一样,则这两个数据会存储在同一个位置,这样既可以保证数据的不可重复性,又避免了繁琐的比较过程,大大提高了效率。
 HashSet底层也是数组,LinkedHashSet是HashSet的子类,底层同样是数组,它们在存储时都是无序的,但LinkedHashSet存储数据的时候是有指针指向的,遍历的时候会根据指针的指向遍历元素,遍历顺序就是添加的顺序,但存储并不是有序的,所以LinkedHashSet适用于频繁的遍历。

Map

 存储“键-值”对,key是不可重复的,value可以重复的,一个key-value对构成一个entry(Map.Entry),entry不可重复。
 Map中的API:

Object put(Object key,Object value);
Object remove(Object key);
void putAll(Map t);
void clear();
Object get(Object key);
boolean containsKey(Object key);
boolean containsValue(Object value);
int size();
boolean isEmpty();
boolean equals(Object obj);

  TIP:Map中的put(key,“value”);如果原来的key-value存在,其value值会被后添加的覆盖,且该方法会返回原来的value值
 Map的实现:
  HashMap:主要的实现类,可以添加null键,null值
  LinkedHashMap:是HashMap的子类,可以按照添加进Map的顺序进行遍历
  TreeMap:需要按照key所在类的指定属性进行排序。要求key是同一个类的对象。对key考虑使用自然排序 或 定制排序,Comparator的实现限定了key的类型

Comparator<Student> com = new Comparator<Student>() {
	@Override
	public int compare(Student o1, Student o2) {
		int compareTo = o1.getName().compareTo(o2.getName());
		if (compareTo != 0)
			return compareTo;
		else
			return o1.getAge().compareTo(o2.getAge());

	}
};

TreeMap<Student, Object> map = new TreeMap<Student, Object>(com);

   以上示例也可以改为Student实现Comparator接口的形式

public class Student implements Comparator<Student> {
	//...
}
TreeMap<Student,Object> map = new TreeMap<Student,Object>(new Student());

  Hashtable:是一个古老的实现类,线程安全的,不可以添加null键,null值,不建议使用。
  Properties:Hashtable的子类,常用来处理属性文件,由于属性文件里的 key、value 都是字符串类型,所以 properties 里的 Key 和 Value 都是字符串类型的。示例:

Properties pros = new Properties();
pros.load(new FileInputStream(new File("jdbc.properties")));//从项目类路径下读取配置文件
String user = pros.getProperty("user");
System.out.println(user);
String password = pros.getProperty("password");
System.out.println(password);	

  注意::LinkedHashMap是HashMap的子类,底层是数组而不是和LinkedList一样是链表。

Collections工具类

 Collections 是一个操作 Set、List 和 Map 等集合的工具类,Collections 中提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等的方法。

reverse(List);//反转 List 中元素的顺序
shuffle(List);//对 List 集合元素进行随机排序
sort(List);//根据元素的自然顺序对指定 List 集合元素按升序排序
sort(List,Comparator);//根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
swap(List,int,int);//将指定 list 集合中的 i 处元素和 j 处元素进行交换
Object max(Collection);//根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator);//根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection);
Object min(Collection,Comparator);
int frequency(Collection,Object);//返回指定集合中指定元素的出现次数
boolean replaceAll(List list,Object oldVal,Object newVal);//使用新值替换 List 对象的所有旧值

 在使用集合工具类Collections的复制方法时,不空的list是无法复制到空的list中去的,要先为目标list开辟一个和原list长度一样的空间,然后复制:

List list1 = Arrays.asList(new Object[list.size()]);
Collections.copy(list1,list);

 Collections将集合变为线程安全的API:

synchronizedCollection(Collection<T> c);
synchronizedList(List<T> list);
synchronizedMap(Map<K,V> m);
synchronizedSet(Set<T> s);
synchronizedSortedMap(SortedMap<K,V> m);
synchronizedSortedSet(SortedSet<T> s);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值