java基础之集合(上)

集合类

  • 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就要对对象进行存储,集合就是存储对象最常用的一种方式。
  • 集合特点:
    • 用于存储对象的容器。
    • 集合的长度是可变的。
    • 集合中不可以存储基本数据类型值。
  • 集合容器因为内部的数据结构不同,有多种具体容器。不断的向上抽取,就形成了集合框架。

集合类的构成:

集合框架的构成及分类

  • Java集合类主要由两个接口(Collection和Map)派生出来:

  • Collection

    • List(子接口):有序(存入和取出的顺序一致),可以重复,有索引

      • Vector(实现类):

        1. 底层数据结构:数组
        2. 线程:同步
        3. 操作数据:增删,查询都很慢
          • Enumeration elements(): 返回此向量的组件的枚举。
          • IO流中SequenceInputStream中用到
          Vector<FileInputStream> v = new Vector<FileInputStream>();
          Enumeration<FileInputStream> en =v.elements();
          SequenceInputStream sis = new SequenceInputStream(en);
          
      • ArrayList (实现类): 替代了Vector

        1. 底层数据结构:数组
        2. 线程:不同步
        3. 操作数据:增删稍慢、改查很快
      • LinkedList (实现类):

        1. 底层数据结构:链表(JDK1.7/8 之后取消了循环,修改为双向链表)
        2. 线程:不同步
        3. 操作数据:增删除很快,改查很慢
    • Set(子接口) :无序(存入和取出的顺序不一定一致),不能重复

      • HashSet(实现类):底层数据结构是哈希表,不同步
        • LinkedHashSet:具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现
      • TreeSet(实现类):底层数据结构是二叉树,不同步,可以对Set集合中的元素进行排序
  • Map:存储键值对,一次添加一对,而且要保证键的唯一性

    • Hashtable实现类):

      1. 底层数据结构:哈希表,不可以存入null键null值
      2. 线程:同步
      3. 操作数据:jdk1.0.效率低
        • Properties:线程同步,用来存储键值对型的配置文件的信息,可以和IO技术相结合
    • HashMap (实现类):

      1. 底层数据结构:哈希表,允许使用 null值和null键, 底层是基于数组和链表实现
      2. 线程:不同步
      3. 操作数据:将hashtable替代,jdk1.2.效率高
    • TreeMap (实现类):

      1. 底层数据结构:二叉树
      2. 线程:不同步
      3. 操作数据:用于给map集合中的键进行排序

Collection接口:

Collection的常见方法:

  • 添加:
    • boolean add(Object obj);
    • boolean addAll(Collection coll);
  • 删除:
    • boolean remove(Object obj);
    • boolean removeAll(Collection coll);
    • void clear( );
  • 判断:
    • boolean contains(Object obj);
    • boolean containsAll(Collection coll);
    • boolean isEmpty();判断集合中是否有元素。
  • 获取:
    • int size( );返回此 collection 中的元素数。
    • Iterator iterator( );
  • 其他:
    • boolean retainAll(Collection coll); 取交集
    • Object[ ] toArray( ); 将集合转成数组

Iterator接口

概述:

  • 对所有的Collection容器进行元素取出的公共接口,称为迭代器
  • Collection中有iterator方法,所以每一个子类集合对象都具备迭代器
  • 它替代了Vector类中的Enumeration(枚举)。迭代器的next方法是自动向下取元素,要避免出现NoSuchElementException。

方法:

  • boolean hasNext():若被迭代的集合元素还没有被遍历,返回true.

  • Object next():返回集合的下一个元素.

  • void remove():删除集合上一次next()方法返回的元素。(若集合中有多个相同的元素,都可以删掉)

  • 取出集合的元素代码示例:

//方式一:
Iterator it=al.iterator();//获取迭代器,用于取出集合中的元素。

while(it.hasNext())
{
	System.out.println(it.next());
}

//方式二:for循环结束,Iterator变量内存释放,更高效
for (Iterator it=al.iterator();it.hasNext() ; )
{
	System.out.println(it.next());
}

高级for循环 格式: for(数据类型 变量名:被遍历的集合(Collection)或者数组){ }

  • 对集合进行遍历的特点:只能获取集合元素,但是不能对集合进行操作
  • 迭代器除了遍历,还可以进行remove集合中的动作。如果是用ListIterator,还可以在遍历过程中对集合进行增删改查的动作。
    • 支持迭代器的集合均支持高级for,Map不支持,故需转成Set集合
  • 传统for和高级for区别:
    • 高级for有一个局限性。必须有被遍历的目标。
    • 建议在遍历数组的时候,还是希望用传统for,因为传统for可以定义脚标。

子接口:

  • ListIterator:专门输出List中的元素
    • 方法:
      • boolean hasNext()
      • E next() 返回列表中的下一个元素。
      • int nextIndex() 返回对 next 的后续调用所返回元素的索引。
      • void add(E e) 将指定的元素插入列表(可选操作)
      • void set(E e) 用指定元素替换 next 或 previous 返回的最后一个元素(可选操作)。
      • void remove()从列表中移除由 next 或 previous 返回的最后一个元素(可选操作)。
      • boolean hasPrevious() 如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。
      • E previous()返回列表中的前一个元素。

List

  • Collection的子接口,有序(存入和取出的顺序一致),元素都有索引(角标),允许重复元素

特有的常见方法。

特点:都可以操作角标。

  • 添加

    • void add(index,element);
    • void addAll(index,collection);
  • 删除

    • Object remove(index);
    • boolean remove(Object o) 从此列表中移除第一次出现的指定元素(如果存在)(可选操作)。
  • 修改

    • Object set(index,element);
  • 获取:

    • Object get(index);
    • int indexOf(object);
    • int lastIndexOf(object):
    • List subList(from,to);

    list特有的取出元素的方式之一

      for(int x = 0; x < list.size(); x++){
      	System.out.println( "get:" + list.get(x));
      }
    
  • 集合变数组:

    • Object[ ] toArray() 返回按适当顺序包含列表中的所有元素的数组(从第一个元素到最后一个元素)。
  • List集合特有的迭代器:ListIterator(列表迭代器)

    • 在迭代时,不可以通过集合对象的方法操作集合中的元素,因为会发生ConcurrentModificationException异常;
    • 所以,在迭代器时,只能用迭代器的方法操作元素,可是Iterator方法是有限的,只能对元素进行判断,取出和删除的操作;
    • 如果想要其他的操作如添加,修改等,就需要使用其子接口,ListIterator.该接口只能通过List集合的listIterator方法获取 。

ArrayList

  • 基于动态数组实现
  • 基本方法:增删改查,与List中的方法基本一致

LinkedList

基本方法:

  • void addFirst(); 1.6以后版本为offerFirst( )
  • void addLast(); 1.6以后版本为offerLast( )
  • E getFirst();获取但不移除,如果链表为空,抛出NoSuchElementException。
    1.6以后版本为:peekFirst(); 获取但不移除,如果链表为空,返回null
  • E getLast(); 1.6以后版本为:peekLast( );
  • E removeFirst(); 获取并移除,如果链表为空,抛出NoSuchElementException。
    1.6以后版本为:pollFirst( );获取并移除,如果链表为空,返回null
  • E removeLast(); 1.6以后版本为: pollLast( );

Set

  • Collection的子接口,元素不可以重复,是无序的,Set接口中的方法和Collection一致

HashSet:

  • 成员变量:
    • map: 用于存放最终数据的
    • PRESENT: 是所有写入map的value值
  • 构造函数: 利用了HashMap初始化了map
  • add方法:
    • 将存放的对象当做了 HashMap 的健,value 都是相同的 PRESENT
    • 由于 HashMap 的 key 是不能重复的,所以每当有重复的值写入到 HashSet 时,value 会被覆盖,但 key 不会收到影响,这样就保证了 HashSet 中只能存放不重复的元素
    • HashMap 会出现的问题 HashSet 依然不能避免
public boolean add(E e) {
	return map.put(e, PRESENT)==null;
}

概述:

  • 内部数据结构是哈希表,是不同步的。

  • HashSet是如何保证元素唯一性的呢?

    是通过元素的两个方法,haseCode和equals来完成的。
    如果元素的HashCode值相同,才会判断equals是否为true;
    如果元素的HashCode值不同,不会调用equals。
    注意:对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashCode和equals方法。

    	//HashSet集合中添加重复元素时,返回false
    	System.out.println(hs.add("java01"));
    	System.out.println(hs.add("java01"));//未存储进去为false
    

代码示例:

import java.util.HashSet;
import java.util.Iterator;

/*
 * 往HashSet集合中存入自定义对象姓名和年龄相同为同一个人,重复元素。
 */
class HashSetDemo {
	public static void main(String[] args) {
		System.out.println("以下为集合中元素存储的比较过程:");
		HashSet<Person> hs = new HashSet<Person>();
		hs.add(new Person("a1", 11));
		hs.add(new Person("a2", 12));
		hs.add(new Person("a3", 13));
		hs.add(new Person("a2", 12));
		hs.add(new Person("a3", 14));

		System.out.println("去掉重复元素后的结果:");

		Iterator<Person> it = hs.iterator();
		while (it.hasNext()) {
			Person p = it.next();
			System.out.println(p.getName() + "..." + p.getAge());
		}
	}
}

class Person {
	private String name;
	private int age;

	Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	// 复写hashCode方法,集合底层内部调用
	public int hashCode() {
		System.out.println(this.name + "...hashCode");
		return name.hashCode() + age * 3;// *3保证hash值的唯一性
	}

	// 复写equals方法
	public boolean equals(Object obj) {
		if (!(obj instanceof Person))
			return false;
		Person p = (Person) obj;
		System.out.println(this.name + ":equals:" + p.name);

		return this.name.equals(p.name) && this.age == p.age;// 此处equals为Object的方法.
	}

	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}
}

//运行结果
// 以下为集合中元素存储的比较过程:
// a1...hashCode
// a2...hashCode
// a3...hashCode
// a2...hashCode
// a2:equals:a2
// a3...hashCode
// 去掉重复元素后的结果:
// a1...11
// a2...12
// a3...13
// a3...14

LinkedHashSet

  • 具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现,该类为HashSet的子类,可以将无序变有序。

TreeSet

特点:

  • 可以对Set集合中的元素进行排序(默认字母的自然顺序),底层数据结构是二叉树
  • 判断元素唯一性的方式:就是根据保证元素唯一性的依据比较方法(实现Comparable接口中根据覆盖的compareTo方法返回值,实现Comparator接口中根据覆盖的compare方法)的返回结果是否是0,是0,就是相同元素,不存。

自定义排序:

  • 第一种方式:让元素自身具备比较性,元素需要实现Comparable接口,覆盖compareTo方法。这种方式也称为元素的自然顺序,或者叫做默认顺序。

    • Comparable接口:位于java.lang包中,只有一个方法compareTo
      • int compareTo(T o):比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
  • 代码示例:

import java.util.Iterator;
import java.util.TreeSet;

/**
 * 需求: 往TreeSet集合中存储自定义对象学生。 年龄和姓名均相同时,视为同一个人,
 * 		并按照学生的年龄进行排序, 当年龄相同时,按照名字的自然顺序排序
 */
public class TreeSetDemo {
	public static void main(String[] args) {
		TreeSet<Student> ts = new TreeSet<Student>();
		ts.add(new Student("C", 19));
		ts.add(new Student("D", 17));
		ts.add(new Student("C", 19));
		ts.add(new Student("A", 20));
		ts.add(new Student("E", 17));

		Iterator<Student> it = ts.iterator();

		while (it.hasNext()) {
			Student s = (Student) it.next();
			System.out.println(s.getName() + "..." + s.getAge());
		}
	}
}

class Student implements Comparable<Student> { // 该接口强制让学生具备比较性。
	private String name;
	private int age;

	Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public int compareTo(Student s) { // 内部底层调用

		// 存与取的方式相同,将不能除去重复的对象元素
		//return 1;

		// 存与取的方式刚好相反,将不能除去重复的对象元素
		// return *1;

		// 按自然顺序取出
		System.out.println(this.name + ":compareto:" + s.name);

		if (this.age > s.age) {
			return 1;
			// 判断次要条件:当年龄相同时,比较姓名是否相同
		} else if (this.age == s.age) {
			return this.name.compareTo(s.name);// 字符串自有的比较方法。
		} else
			return *1;

		/*简化书写
		int temp = this.age * s.age;
		return temp == 0? this.name.compareTo(s.name) : temp;*/
	}

	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}
}
  • 第二种方式:当元素自身不具备比较性时,或者具备的比较性不是所需要的,就让集合自身具备比较功能,定义一个类实现Comparator接口(java.util包),覆盖compare方法。 将该类对象作为参数传递给TreeSet集合的构造函数。

    TreeSet(Comparator<? super E> comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。

  • 注意:如果自定义类实现了Comparable接口,并且TreeSet的构造函数中也传入了比较器,那么将以比较器的比较规则为准

  • 代码示例:

import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/*
* 需求:按姓名排序,不修改compareTo方法
*/
class TreeSetDemo2 {
	public static void main(String[] args) {
		TreeSet<Student> ts = new TreeSet<Student>(new MyCompare());// 将比较器对象作为参数传递给TreeSet集合的构造函数
		ts.add(new Student("A", 20));
		ts.add(new Student("C", 19));
		ts.add(new Student("E", 17));
		ts.add(new Student("D", 17));
		ts.add(new Student("A", 18));
		ts.add(new Student("C", 19));

		Iterator<Student> it = ts.iterator();

		while (it.hasNext()) {
			Student s = (Student) it.next();
			System.out.println(s.getName() + "..." + s.getAge());
		}
	}
}

class Student implements Comparable<Student> {	// 该接口强制让学生具备比较性。
	private String name;
	private int age;

	Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public int compareTo(Student s){	// 内部底层调用

		// 按自然顺序取出
		System.out.println(this.name + ":compareto:" + s.name);
		int temp = this.age * s.age;
		return temp == 0? this.name.compareTo(s.name) : temp;
	}

	public String getName() {
		return name;
	}

	public int getAge() {
		return age;
	}
}

// 定义一个类,实现Comparator接口
class MyCompare implements Comparator<Student> {
	//覆盖compare方法
	public int compare(Student s1, Student s2) {
		int num = s1.getName().compareTo(s2.getName());
		if (num == 0) {

				/*//第一种方法
				if(s1.getAge()>s2.getAge())
				return 1;
				if(s1.getAge()==s2.getAge())
				return 0;
				return *1;
				*/

			/*//第二种方法
			return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));*/

			// 第三种方法
			return s1.getAge() * s2.getAge();
		}

		return num;
	}
}