javaSE(六).集合框架

1.Collection

JDK提供了这样一个容器,集合Collection。Collection是一个接口,定义了集合的相关的操作方法,其有两个子接口List和Set

List:可重复集

Set:不可重复集

元素是否重复,取决于元素的equals()比较的结果

集合的基本方法:

方法 说明
int size() 获取集合中元素的个数
boolean isEmpty()  查看集合是否是空的(没有元素)
void clear() 清空集合

(1).泛型在集合中的应用

泛型是JavaSE 5.0引入的新特性,泛型的本质是参数化类型。在类、接口和方法的定义过程中,所操作的数据类型被传入的参数指定。

Java泛型机制广泛的应用在集合的框架中。所有的集合类型都带有泛型参数,这样在创建集合时可以指定放入集合中元素的类型。Java编译器可以据此进行类型检查,这样可以减少代码在运行时出现错误的可能性。

如下方法:

方法 说明
boolean addAll(Collection<? extends E> c)

批量添加,该方法需要我们传入一个集合,并将该集合中的所有元素添加到

当前集合中。如果此collection由于调用而发生更改,则返回true

boolean contarinsAll(Collection<?> c) 该方法用于判断当前集合是否包含给定集合中的所有元素,若包含则返回true。
boolean remove(Object o)

从当前集合中删除给定元素第一个与equals比较为true的。删除元素是根据

元素的equals的结果判断的。

如下代码:

public class TypeDemo<T> {
	private T x;

	public TypeDemo(T x) {
		this.x = x;
	}

	public T getX() {
		return x;
	}

	public void setX(T x) {
		this.x = x;
	}

	@Override
	public String toString() {
		return x+"";
	}
}
class TestTypeDemo {

	public static void main(String[] args) {
		TypeDemo<String> t = new TypeDemo<String>("");
        String s = t.getX();
	}
}

(2).迭代器

每种集合都实现了迭代器,遍历的方式都是一样的。Iterator是一个接口Java希望我们使用统一的方式遍历所有的集合

使用迭代器遍历集合必遵循以下过程:问,取,删(删除不是必须操作)

方法:boolean hasNext() 询问迭代器,遍历的集合是否还有元素可以取出

public static void main(String[] args){
    Collection c = new HashSet();  //ArrayList等
		c.add("a");
		c.add("b");
		c.add("c");
		c.add("d");
		c.add("e");

		/**
		 * Iterator是一个接口
		 * Java希望我们使用统一的方式遍历所有的集合
		 * 使用迭代器遍历集合必遵循以下过程:
		 * 问,取,删(删除不是必须操作)
		 */
		Iterator it = c.iterator();
		
		/**
		 * boolean hasNext()
		 * 询问迭代器,遍历的集合是否还有元素可以取出
		 */
		while(it.hasNext()){
			Object obj = it.next();
			String str = (String)obj;
			System.out.println(str);
		}
}

(3).List集合

List接口是Collection的子接口,用于定义线性表结构。可以将List理解为存放对象的数组,只不过其元素个数可以动态的增加或减少。List接口的两个常见实现类为ArrayListLinkedList,分别用动态数组和链表的方式实现了List接口。

可以认为ArrayListLinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别。ArrayList更适合遍历,随机访问,而ArrayList更适合于插入和删除。在性能要求不是特别苛刻的情形下可以忽略这个差别。

List除了继承Collection定义的方法外,还根据线性表的结构定义了一系列的方法:

方法 说明
E get(int index) 获取集合中指定下标对应的元素(下标从0开始)
E set(int indexE element) 将给定元素存入指定位置,并将原位置的元素返回
void add(int index,T t) 将给定的元素添加到指定的位置上,原位置上以及后面的元素顺序相后移动(插入元素)
E remove(int index)  删除指定位置上的元素,返回值为被删除的元素
List subList(int start,int end)   获取当前集合中的子集(左闭右开)

注:修改子集元素,也会影响原集合

a.List转换为数组

ListtoArray()方法用于将集合转换为数组。但实际上该方法是在Collection中定义的,所以所有的集合都具备这个功能。

Object[] toArray();

<T>T[] toArray(T[] a);

两个方法,第二个是比较常用的,可以传入一个指定类型的数组,该数组的元素类型应与集合的元素类型一致。返回值则是转换后的数组,该数组会保存集合中的所有元素。

public static void main(String[] args){
    Collection<String> c = new ArrayList<String>();
    c.add("one");
    c.add("two");
    c.add("three");
    String[] array = c.toArray(new String[c.size()]);
    for(String s : array){
	    System.out.println(s);
    }
}

b.数组转换为List

数组转换List集合,不能转换为set集合,原因在于set集合不能存放重复元素,通过Arrays转换的集合不能创建新元素。

所有的集合(包含set集合)都支持一个构造方法,参数传入一个Collection,这个构造方法的好处是创建当前集合时默认包含给定集合中的所有元素。

c.集合排序

Collections.sort(List<T> list)            通过Collections这个集合工具类对List集合进行排序(由小到大)

注意:使用Collections的sort()方法进行排序的集合,要求元素必须实现Comparable接口,只有实现了该接口才认为是可以比较的元素(集合元素可以直接比较的不用)

public class Cell implements Comparable<Cell> {
    private int x;
	private int y;
	public Cell(int x,int y){
		this.x = x;
		this.y = y;
	}
    /**
	 * 比较大小的方法
	 * 返回值不关注具体的值,只关注取值范围
	 * 返回值<0 : 当前对象比给定的对象小
	 * 返回值=0 : 两个对象相等
	 * 返回值>0 : 当前对象比给定对象大
	 */
	public int compareTo(Cell o) {
		/**
		 * 比较规则,y值大的这个对象就大
		 */
		return this.y-o.y;
	}
}
class test{
    public static void main(String[] args){
		List<Cell> list = new ArrayList<Cell>();
		list.add(new Cell(4,5));
		list.add(new Cell(1,7));
		list.add(new Cell(1,2));
		list.add(new Cell(3,3));		
		System.out.println();
		
		/**
		 * 使用Collections的sort()方法进行排序的集合
		 * 要求元素必须实现Comparable接口,只有实现了
		 * 该接口才认为是可以比较的元素
		 */
		Collections.sort(list);
		System.out.println(list);
	}
}

d.比较器

什么时候声明比较器?

当集合中的元素已经实现了Comparable接口时,并且实现了比较规则,但是该比较规则不能满足我们对于排序的需求时,我们可以额外的定义一个比较规则。

public class SortCollection3 {
	public static void main(String[] args){
		List<String> list = new ArrayList<String>();
		list.add("奥斯特洛夫斯基");
		list.add("奥黛丽赫本");
		list.add("上官若水");
		list.add("李明");
		System.out.println(list);
		Comparator<String> com = new MyComparator();
		Collections.sort(list,com);
		System.out.println(list);
	}
}

/**
 * 什么时候声明比较器?
 * 当集合中的元素已经实现了Comparable接口时,并且实现了比较规则,但是
 * 该比较规则不能满足我们对于排序的需求时,我们可以额外的定义一个比较规则
 * @author Administrator
 *
 */
class MyComparator implements Comparator<String>{
	 	
	public int compare(String o1, String o2) {
		return o1.length()-o2.length();
	}
}

何时使用匿名内部类:

当我们需要用到一个实例,而该实例所属的类需要实现一个接口或者继承一个类,并且该实例仅在这里用一次,不会在其他地方使用时,我们就应当选取匿名内部类的形式创建实例。

public class SortCollectionDemo4 {
	public static void main(String[] args){
		List<String> list = new ArrayList<String>();
		list.add("奥斯特洛夫斯基");
		list.add("奥黛丽赫本");
		list.add("上官若水");
		list.add("李明");
		System.out.println(list);
		Comparator<String> com = new MyComparator(){
			public int compare(String o1, String o2) {
				return o1.length()-o2.length();
			}
		};
		Collections.sort(list,com);
		System.out.println(list);
	}
}

2.Queue和Stack

队列(Queue)是常用的数据结构,可以将队列看成特殊的线性表,队列限制了对线性表的访问方式:只能从线性表的一端添加(offer)元素,从另一端取出(poll)元素。

队列遵循先进先出(FIFO first input firts out)的原则

JDK提供了Queue接口,同时使得LinkedList实现了该接口(选择LinkedList实现queue的原因在于queue经常要进行添加和删除的操作,而LinkedList在这方面效率较高)

方法 说明
boolean offer(T t) 入队方法,将给定元素添加到队列末尾
T poll() 用于获取队首元素。出队操作。获取后,队列中将不再包含该元素
T peek() 用于获取队首元素,仅引用,  不做出队操作

DequeJava中没实现栈Stack集合,限制了Deque集合的一端当栈来用)

Dequequeue的子接口,定义了所谓的“双端队列”即从队列的两端分别可以入队(offer)和出队(poll),LinkedList实现了该接口。

如果将Deque限制为只能从一端入队和出队,则可实现栈(Stack)的数据结构,对于栈而言,入栈称之为push,出栈称之为pop

栈遵循先进先出(FILO first input last output)的原则

方法 说明
void push(T t)

向栈顶”压入“一个元素,入栈操作

T pop() 取出栈顶元素,出栈操作,取出后,该元素会从栈中删除
T peek() 用于获取队首元素,仅引用,  不做出栈操作

3.Map

该数据结构看起来更像是一个多行两列的表格,每一条数据包含两个信息key-value,其中keyMap中不允许重复,重复值必须得是equalstrue

基本方法:

方法 说明
V put (K v,V v)

将给定的keyvalue存入map

若给定的keymap中不存在,则是添加新内容,返回值是null

若给定的keymap中存在,则是替换key对应的value,返回原来的value

V get(K k) 根据给定的key获取对应的value
boolean contrainsKey(K k)

判断当前map中是否含有给定的key,是否含有是根据keyequals判断的

V remove(K k) 根据给定的key删除这一项,返回值为删除的value

HashMap

存入的是键值对key-value

原理:实际上HashMap内有一个散列数组,存入元素的时候,根据key值计算得出hashCode值,根据hashCode值来确定这个元素存入的位置(一定的规则)。若有多个元素的key计算后得出的hashCode值相同,则这个数组的位置存一个链表,put一个元素,链表增加一个元素(拉链法),当然这种情况尽量避免。

对于重写equals方法的对象,一般要妥善重写继承自Object类的hashCode方法(Object类提供hashCode方法将返回该对象所在内存地址的整数形式)。

注意:

作为key的对象有以下要求

a.首先key这个类重写了equals方法,那么也要重写hashCode

b.作为key的对象若存入HashMap后,其会影响hashCode值的内容不要发生变化,否则可能影响Map操作

 

(1).重写equals方法

重写equals方法时应当重写hashCode方法,重写规则如下:

a.一致性,若两个对象equals方法比较结果为true,那么hashCode返回的数字必须相同。但反之则不是必须的,但是也应当尽量避免。即:两个对象若equals方法比较结果为false,它们的hashCode方法返回值应尽量不同,否则影响HashMap性能。

b.hashCode方法在当前对象内容没有发生改变的前提下多次调用应当返回相同的数字。

c.hashCode返回的数值应符合hash算法的要求,试想如果有很多对象的hashCode方法的返回值相同,则会大大降低hash表的效率,一般情况下可以使用IDE(如,Eclipse)工具自动生成hashCode方法。

 

(2).装载因子及HashMap优化

Capacity:容量,hash表里bucket(桶)的数量,也就是散列数组的大小。

Initial capacity:初始容量,创建hash表时,初始bucket的数量,默认构建容量为16,也可以使用特定容量。

Size:大小,当前散列表中存储元素的个数。

Load factor 加载因子,默认值0.7575%),当向散列表增加数据时如果size/capacity的值大于Load factor则发生扩容并且重新散列(rehash)。

性能优化:加载因子较小时,散列查找性能会提高,同时也浪费了散列桶空间容量。0.75是性能和空间相对平衡结果。在创建散列表时指定合理容量,减少rehash提高性能。

 

(3).遍历Map的三种方式

a.遍历所有的key

Set<K> keySet()   该方法可以获取Map中所有的key,并将它们存入一个Set集合中返回

Set<String> keySet = map.keySet();
for(String key : keySet){
	System.out.println(key);
}

b.遍历所有的键值对

Set<Entry> entrySet()  该方法将每一元素的key-value存入一个Entry实例中,并将这些Entry实例存入一个Set集合并返回

Set<Entry<String,Integer>> entrySet = map.entrySet();
for(Entry entry : entrySet){
	System.out.println(entry.getKey()+":"+entry.getValue());
}

c.遍历所有的value

Collection<V> values()  将所有元素的值放入到一个集合中

Collection<Integer> values = map.values();
for(Integer i : values){
	System.out.println(i);
}

可以发现遍历元素和put进去的元素顺序不一致:可以使用LinkedHashMap集合,会有序

继承了HashMap

 

猜你喜欢

转载自blog.csdn.net/weixin_41968788/article/details/81293531