黑马程序员——java基础——集合

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------


一、集合基础知识

1、集合的用途

        集合与数组有一些相似的地方,就是都可用于存储数据,作为数据的窗口。那么集合与数组有什么不同之处呢?或者说集合能为我们带来一些什么数组不具备的功能呢?首先我们在定义数组时,必须对其长度进行初始化,也就是说,数组一经定义,其存储的数据量就固定了,若存储数据的量超出了数组的大小,则会发生异常,就是我们常见的数组下标越界异常。而集合是可变长度的容器,数据添加到一定的数量,集合会自动增长,比如增长一倍,或增长原长度的一半等。

        另外在声明数组时,我们必须确定数组元素的数据类型,并且数据类型一经明确,之后将数据存入该数组时,只能存入该类型或该类型的子类对象。而集合中可以保持任意类型的对象。

        有一点是数组具备而集合不具备的特性,就是数组可以作为基本数据类型的容器,而集合只能存储对象。集合在这一方面的改进是,存储基本数据类型对应的包装类。

2、集合的继承体系结构

        体系结构用一张图来表示将会更加明显,如图:



        该体系结构图中最上面的Iterator接口先不说,我们先来看左边蓝色区域的部分。

        这一部分Collection为超类,先从它来学起。Collection为java.util包中的一个接口,集合为一种容器工具,放在java.util包中也很好记了。参看该接口的API文档,有一些常见的增删改查方法,读者可自行查询学习,这里就没有必要一一介绍了,常用的一些方法如下:

        添加元素的add方法,删除元素的remove方法,清空集合的clear方法,判断元素是否存在的contains方法,判断集合是否为空的isEmpty方法,获取集合中元素个数的size方法等。

        由于后面要说到集合的遍历操作,这里必须讲讲Iterator接口了,Iterator接口作用于集合框架的所有类,该接口定义了元素的遍历方式和取出方式,只是一个定义,这是因为不同集合底层数据结构不同,所以相应的元素遍历方式也不同,所以下面的各种子类集合对其进行了实现,实现方式自然是不同的,这都是不同集合底层数据结构不同所导致的。举毕向东老师一个本人认为十分形象的例子来说明Iterator,大家都见过商店或电玩城外放置的一种投币后操纵一个电动夹子来抽取奖品的一种机器,Iterator如同这样一个功能的东西,而这个机器就如同存储元素的集合对象,每个集合内部都有一个Iterator类的实现类,就像夹子在机器内部一样,而且如同夹子有不同的样式,如三个角的,两个角的,不同集合类内部的Iterator实现类的实现方式也是不同的,都是为了取元素的方便,所以实现方式会不同。

        Iterator接口中定义的方法有hasNext判断下一个元素是否存在,为什么说下一个呢?因为之前说到,Iterator是用于遍历集合的,当然要判断下一个元素是否存在了。next方法用于获取下一个元素,remove方法用于删除当前元素。

3、List集合

         List类中的集合采用线性存储结构,其中ArrayList采用数组,是一种连续存储结构,底层结构就是数组,内存中用一块连续的内存空间来表示。LinkList采用的是链表,数据在内存中不一定是连续的,也就是应用到了指针。它们中有一些特有的方法,这些方法都可以通过索引来对元素进行操作,这些方法都很简单,读者可以参考API。不得不强调的一点是,List集合有其特有的迭代器ListIterator,它是Iterator接口的子接口,它提供了一些Iterator中没有的操作List集合中元素的方法,如对集合中的元素进行增加,删除,修改。为什么Iterater又要自行定义这些方法,集合对象中不是已经有了吗?这是因为在集合的遍历过程中是不允许使用集合对象对元素进行增删之类的操作的,这会引起并发修改异常,试想Iterator在迭代元素,迭代的是集合中的元素,而集合自身又在对元素进行增删,那迭代的元素是不是不确定了呢!所以在遍历集合时,要用Iterator提供的方法进行元素的操作,以保证安全。最后强调一点,ArrayList集合与LinkList集合都是线程不安全的,在多线程中要考虑同步互斥问题,List集合中还有一个不太常用的子类Vector,它是线程安全的,已很使用了。Vector的操作很简单,通过addElement()加入一个对象,用elementAt()取出它,还可以查询当前所保存的对象的个数size();另外还有一个Enumeration类提供了连续操作Vector中元素的方法,这可以通过Vector中的elements()方法来获取一个Enumeration类的对象,可以用一个While循环来遍历其中的元素。用hasMoreElements()检查其中是否还有更多的元素。用nextElement()获得下一个元素。Enumeration的用意在于使你能完全不用理会你要遍历的容器的基础结构,只关注你的遍历方法,这也就使得遍历方法的重用成为可能。由于这种思想的强大功能,所以在Java2中被保留下来,不过具体实现,方法名和内部算法都改变了,这就是Java2中的Iterator以及ListIterator类。然而Enumeration的功能却十分有限,比如只能朝一个方向进行,只能读取而不能更改等。

4、Set集合

         Set类中的集合是非线性存储结构,其中HashSet底层采用哈希表,TreeSet采用二叉树结构。而Map类的集合也是非线性的存储。Set集合可以保证存储元素的唯一性,特别的TreeSet还能对元素进行排序,这也是它们底层数据结构不同所导致的。首先HashSet底层的哈希表存在一个哈希值的概念,集合中每个元素都有一个唯一的哈希值,通过这个哈希值来决定元素存放的位置,所以相同元素,哈希值也相同,保证了元素的唯一性,这在java中哈希值的决定,往往是通过类元素中的成员变量来决定的,这也体现了唯一性的特点,所以相同的元素是不能存入Set集合中的,存入HashSet集合中的元素要实现Object中的hashCode方法才有意义;TreeSet也以类似的确保元素唯一性的功能代码,TreeSet中的元素类都必须继承Comparable接口,实现相应的compareTo方法,才能保证元素的唯一性,并还可以确定元素排序的依据。 Hashtable也是Java中一个有用的容器类库。它的基本目标是实现两个或多个对象之间进行关联。通过使用pub(Object key,Objectvalue)方法把两个对象进行关联,需要时用get(Object key)取得与key关联的值对象。还可以查询某个对象的索引值等等。值得说明的这里的get方法查找一个对象时与Vector中的get方法在内部实现时有很大不同,在一个Hashtable中查找一个键对象要比在一个Vector中快的多。这是因为Hashtable使用了一种哈希表的技术(在数据结构中有详细讲解),在Java每个对象缺省都有一个通过ObjecthashCode()方法获得的哈希码,Hashtable就是利用这个哈希实现快速查找键对象的。

5、List集合与Set集合的小结

      ListSet集合用于存储各种包装类,IntegerString以及自定义类型,Map集合用于存储键值对,键值也都是包装类的。

      用集合进行数据的存储时,要有针对性的选取,不是随意选取的,这是由于不同集合的底层实现方式不同决定的,底层实现方式不同,对集合中数据的操作也有不同的影响。如ArrayList利于随机读取数据,因为它是以数组结构的顺序存储,用索引即可获得对应的数据,非常高效。但缺点是在集合中数据量大时,不利于数据的增删,这也是由于数组为连续存储结构所导致的。而同为线性结构的LinkList则在增删数据时非常高效,但缺点是不利于随机查询,因为这种结构要顺链查找,每次都得从第一个元素找起。Set集合中的HashSet底层采用的是哈希表,能通过哈希值查询元素,可见查询上是较高效的,增删时也不必保持数据的顺序,但结构相对List集合要复杂。TreeSet底层采用二叉树,结构也很复杂,在数据的增删时,结构还会进行调整,称为平衡二叉树,调整的目的是为了保证查询元素的高效,增删上也很快捷。

      对这些集合的掌握,可通过其父类Collection开始,常见的方法如添加元素add、移除remove、查询contains等,

      对集合的遍历有foreach语句,迭代器iterator等方式。要注意的有在进行集合遍历时,不要用集合对象进行集合元素的增删, 不然会发生异常。但可用迭代器进行remove操作,其中ArrayList中还有ListIterator迭代器还提供了增加元素的方法。

6、Map集合

      Map集合不属于Collection集合的子类,Map集合存储的是键值对,即映射表。子类有HashMap与TreeMap,底层同HashSet与TreeSet,其实HashSet与TreeSet采用对应的Map集合实现的。Map集合中有些常用的方法有存取键值对的方法,判断键值是否存在等方法,也是很简单的应用。这里必须说明的是对Map集合的遍历问题,从集合的继承体系图中可以看出Map也有实现Iterator,因为无论是Collection集合还是Map集合,同为集合,都有遍历元素的操作。Map集合有两种遍历方式,第一种是通过其keySet方法,获取键的集合对象,是一个Set集合,然后就可使用Set集合的遍历方式进行键的获取了,对应的值的获取也就一蹴而就了;第二种方式是通过方法entrySet获取Map.Entry对象,Entry类是Map中的一个静态内部类,封装了获取键值的方法,可以将它理解为键值的映射对象。

      TreeMap也可以同TreeSet一样传入比较器对象:


import java.util.*;
class AgeComparator implements Comparator<Student>//自定义比较器
{
	public int compare(Student s1,Student s2)
	{
		int num=s1.getAge()-s2.getAge();//年龄优先比较
		if(num==0)
			return s1.getName().compareTo(s2.getName());
		return num;
	}
}
class TreeMapDemo 
{
	public static void main(String[] args) 
	{
		TreeMap<Student,String> tm=new TreeMap<Student,String>(new AgeComparator());//构造方法中传入比较器
		tm.put(new Student("blisi01",22),"Beijing");
		tm.put(new Student("alisi02",21),"Tianjing");
		tm.put(new Student("alisi02",21),"Guangzhou");
		tm.put(new Student("dlisi03",20),"Shanghai");
		tm.put(new Student("clisi04",23),"Wuhan");
		Set<Map.Entry<Student,String>> entrySet=tm.entrySet();//以Map.Entry对象的Set集合进行遍历
		for(Iterator<Map.Entry<Student,String>> it=entrySet.iterator();it.hasNext();)
		{
			Map.Entry<Student,String> me=it.next();
			Student s=me.getKey();
			String addr=me.getValue();
			System.out.println(s+"   "+addr);
		}
	}
}



7、集合类的总结

      Java中的集合框架提供了一套设计优良的接口和类,使程序员操作成批的数据或对象元素极为方便。这些接口和类有很多对抽象数据类型操作的API,而这是我们常用的且在数据结构中熟知的。并且Java用面向对象的设计对这些数据结构和算法进行了封装,这就极大的减化了程序员编程时的负担。程序员也可以以这个集合框架为基础,定义更高级别的数据抽象,比如栈、队列和线程安全的集合等,从而满足自己的需要。
Java
的集合框架,抽其核心,主要有三类:ListSetMapListSet继承了Collection,而Map则独成一体。初看上去可能会对Map独成一体感到不解,它为什么不也继承Collection呢?但是仔细想想,这种设计是合理的。一个Map提供了通过KeyMap中存储的Value进行访问,也就是说它操作的都是成对的对象元素,比如put()get()方法,而这是一个SetList所不就具备的。当然在需要时,你可以由keySet()方法或values()方法从一个Map中得到键的Set集或值的Collection集。

      java.util里面有一个Arrays类,它包括了一组可用于数组的static方法,这些方法都是一些实用工具。其中有四个基本方法:用来比较两个数组是否相等的equals();用来填充的fill();用来对数组进行排序的sort();以及用于在一个已排序的数组中查找元素的binarySearch()。所有这些方法都对primitiveObject进行了重载。此外还有一个asList()方法,它接受一个数组,然后把它转成一个List容器。
虽然Arrays还是有用的,但它的功能并不完整。举例来说,如果它能让我们不用写for循环就能直接打印数组,那就好了。此外,正如你所看到的fill()只能用一个值填数组。所以,如果你想把随即生成的数字填进数组的话,fill()是无能为力的。


二、集合的一些典型应用

1.TreeMap存储字符串中字符的个数

      TreeMap底层使用二叉排序树来存储键值对中的键,在实际的运用中,使用的是更高级的平衡二叉树结构,即随集合中元素的增多,自身结构会进行调整,这样做是为了提高查询与增删数据时的效率。集合中的Map.Entry记录是按键进行了排序的。在没有指定排序器Comparator的情况下是按键对Comparable接口中的实现方式进行排序的。

      基于此种特性,用TreeMap来对字符串中出现了的字符的个数进行记录是恰到好处的,键为各个字符,值则为字符出现的个数。


import java.util.*;
class TreeMapDemo 
{
	public static void main(String[] args) 
	{
		String result=getCharsCount("ab+ca=bca-bcd8efd4e3fabc");
		System.out.println(result);
	}
	public static String getCharsCount(String s)
	{
		char[] chs=s.toCharArray();
		TreeMap<Character,Integer> tm=new TreeMap<Character,Integer>();
		int count=0;
		for (int i=0;i<chs.length ;i++ )
		{
			if(!(chs[i]>='a'&&chs[i]<='z'||chs[i]>='A'&&chs[i]<='Z'))//非字符的不予记录
				continue;
			Integer value=tm.get(chs[i]);//不存在相应字符,则返回null
			if(value!=null)
				count=value;
			count++;
			tm.put(chs[i],count);//重写入集合中
			count=0;//清零
		}
		StringBuilder sb=new StringBuilder();
		Set<Map.Entry<Character,Integer>> entrySet=tm.entrySet();
		for(Iterator<Map.Entry<Character,Integer>> it=entrySet.iterator();it.hasNext();)//遍历读取
		{
			Map.Entry<Character,Integer> me=it.next();
			Character ch=me.getKey();
			Integer in=me.getValue();
			sb.append(ch+"("+in+")");
		}
		return sb.toString();
	}
}

2.实现元素唯一的ArrayList

      ArrayList是一种底层基于数组的存储集合,按索引进行随机访问,是一种随机访问速度快的存储结构,但缺点是当集合中元素很多时,集合的增删效率会很差,这是由于数组的排序方式决定的。

      ArrayList集合是由索引来访问的,不保证元素的唯一性。以下代码实现了ArrayList存储元素的唯一性。

      思想很简单,就是在加入对象前,先看集合中是否已存在相同的对象,因此对集合中元素类的equals方法的编写是重点。


import java.util.*;
class Person
{
	private String name;
	private int age;
	public Person(String name,int age)
	{
		this.name=name;
		this.age=age;
	}
	public String getName()
	{
		return name;
	}
	public int getAge()
	{
		return age;
	}
	public boolean equals(Object obj)
	{
		if(!(obj instanceof Person))
			return false;
		Person p=(Person)obj;
		System.out.println(name+"..."+p.name);
		return name.equals(p.name)&&age==p.age;
	}
}
class SingleArrayList 
{
	public static void main(String[] args) 
	{
		ArrayList al=new ArrayList();
		al.add(new Person("lisi01",31));
		al.add(new Person("lisi02",32));
		al.add(new Person("lisi03",33));
		al.add(new Person("lisi02",32));
		al.add(new Person("lisi03",33));
		al.add(new Person("lisi01",31));

		al=getSingleArrayList(al);
		for(Iterator it=al.iterator();it.hasNext();)
		{
			Person p=(Person)it.next();
			sop(p.getName()+"..."+p.getAge());
		}
	}
	public static ArrayList getSingleArrayList(ArrayList al)//返回元素唯一的ArrayList集合对象
	{
		ArrayList newal=new ArrayList();
		for(Iterator it=al.iterator();it.hasNext();)
		{
			Object obj=it.next();
			if(!newal.contains(obj))
				newal.add(obj);
		}
		return newal;
	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值