JAVA基础整合——集合框架1

集合框架

这是一个重点内容,是要求我们重点掌握的内容。

一、集合框架的概述

什么是集合框架呢?集合是一个批量数据的存储容器。与数组类似,但是只是类似而已。数组的长度一般是不可变的,数组的存储数据只是单一类型的。集合就不同,存在集合中的数据都是引用类型的,也就是说可以存储对象,切记:集合中只能存入引用类型数据。

二、 JAVA中集合的体系结构

首先我给大家上个图:
在这里插入图片描述

  1. 所有的集合分为两大类:单列集合和双列集合,所谓的单列是指集合中每个元素都是一个值,双列是指每个元素都由两个值组成(key-valuue)键值对集合,key和value之间存在着映射关系。
  2. 单列集合的顶级接口时Collection,其实这个顶级接口上面还有一个Iterable接口,这个接口我们一般都是用来构建一个子类的迭代器对象取访问该子类中的集合对象的。双列接口的顶级接口是Map。
  3. Collection接口下面又分为两个子接口:List、Set接口
  4. List接口的特点是:有序有重复,Set接口的特点是:无序无“重复”,注意这个重复为什么要打引号呢,后续我们会详细讲这个重复问题。
  5. List接口下有三个常用的实现类:ArrayList、LinkedList、Vector。
  6. Set接口下有两个常用的实现类:HashSet、TreeSet。在HashSet类下面又有一个子类LinkedHashSet。
  7. Map接口有两个常用实现类:HashMap和TreeMap,然后再HashMap下有一个子类LinkedHashMap。

每个集合的底层都采用不同的数据结构实现的。

三、Collection接口中常用方法

  • add(E e):向集合中添加一个新元素,当没有指定具体的存储类型时,默认为Object类型。
  • size():返回当前集合中元素的个数
  • 迭代操作:万能的迭代方式(迭代器),迭代器的理解时一个游标,第一次它时处在这个集合第一个元素的头上,不指向任何一个对应的元素。
    1.获取迭代器对象调用迭代器对象的next()方法表示让迭代器往下移动一位并获取指向的元素的值返回循环的条件使用迭代器对象的hasNext()方法来控制,该方法可以用来判断是否存在下一个元素,如果存在则返回true否则false
Iterator iterator = collection.iterator();
while(iterator.hasNext()) {
    Object value = iterator.next();
    System.out.println(value);
}
  • remove(Object o ):从集合中一出指定的元素
    切记:remove方法只能删除一个元素,如果集合中存在多个重复的值,需要我们自己编写删除的过程。
    切记:迭代器迭代时,只是迭代当前集合的一个副本,所以子啊迭代的过程中不允许动态的去修改副本中的元素。也就是说不允许直接去remove,而可以通过iterator.remove();,迭代器对象自己的remove()方法去动态修改。

  • remove方法删除元素的细节源码:

//remove方法的删除细节
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

上诉代码中删除时比较的过程中,还是用equals()方法比较。用equals()时我们要注意是用的Object父类中比较地址的equals()方法,还是子类中重写了比较规则的equals()方法。一般情况下如果要存入集合的对象,都重写equals方法。

  • addAll(Collection collection):批量添加,将一个集合中所有的元素添加到另一个集合中。
  • clear():情况集合中所有的元素。
  • contains(Object o):判断集合中存在某个指定的元素,返回boolean,此方法再判断的时候也是equals方法比较。
  • isEmpty():判断集合是否为空,注意集合中一个元素没有才叫空。
  • toArray():将当前集合转成一个数组

四、List接口

首先List接口时从Collection继承,特点时有序有重复。
有序:每个元素都有下标,存入的顺序和取出的顺序是一致的。和数组类似。
List接口的结构我们发现相比于Collection来说List这个子接口中添加了一些关于下标相关的操作方法:

  1. add(int index,Object e):将新元素添加到指定的位置。

  2. get(int index):根据下标获取某个位置的元素并返回。

  3. set(int index,Object e):修改指定下标的元素值

  4. remove(int index) :根据下标删除指定位置的元素
    注意:如果按照一个int类型的数据内容进行删除,需要手动进行包装,否则将参数理解为下标

  5. sort(Comparator c ):排序,这里的排序是根据集合中的元素内容进行排序。注意如果集合中的元素是一个对象,我们要将对象实现一个Comparable接口,并实现该接口的未实现的方法,将你想要比较的规则写在里面并以返回值返回,这个叫自然排序。还有一种是定制排序,以匿名内部类的卸载sort方法的括号里面,例如:

 List list=new ArrayList();
 list.add(111);
 list.add(222);
 list.add(444);
 list.add(777);	
 list.add(666);
 //System.out.println(list.get(3));
 list.sort(new Comparator() {
     @Override
     public int compare(Object o1, Object o2) {
         // TODO Auto-generated method stub
         return (Integer)o1-(Integer)o2;
     };
 });

扩展一个小芝士点:可变参数类型,语法:类型符…参数名称,本质上是一个数组。

public static void m1(int... a) {
    System.out.println(a.length);
    System.out.println(a[3]);
}

可以调用Arrays工具类的一个静态方法asList(T…a),可以将一个数据集直接转换成一个List集合。

List<Integer> list = Arrays.asList(1,2,3,4,5);

小芝士点:增强for循环(foreach循环):底层是基于迭代器实现的,这种遍历方式也适用与任何框架,不论是有序还是无序的,都可。

五.扩展——泛型

我对于泛型的理解是:它其实也是一个类型的参数,但是这个参数可以是任何类型的,如果不指定类型就会默认指定为Object类型。
切记:使用时千万不要指定该类型为基本数据类型。
在我们使用集合框架时如果不指定具体的泛型,就会存在类型安全问题,指定泛型的目的时让编译器编译我们代码的时候能够对集合中的每个元素进行类型的检查,让该集合中只能存入我们想要的元素,不能进入一个非法数据。例如一个只存放学号的String类型,突然来了一个Integet数据,这时编译器就会报错,提示我们这个数据不合法。

List<String> list=new ArrayList<>();  //在JDK高版本中后面尖括号中会自动类型推断

使用泛型的好处:从集合获取的元素就是我们指定想要的元素类型,无序进行类型的强制转换,这种可以帮我们合理规范我们的集合。也减轻我们编程负担。

  • 扩展:我们也可以定义自己的泛型类,只需在类的定义后面使用<类型占位符>
//在定义类中的成员时,我们就当T是一个已知的某个具体类型
public class MyClass<T> {
	
	private T i;
	
	public T getI() {
		return i;
	}
	
	public void setI(T i) {
		this.i=i;
	}
	
	public MyClass(T i) {
		this.i=i;
	}
	
	public T test01(T s) {
		return s;
	}
}

问题:JAVA中的泛型为什么叫伪泛型呢?
答:JAVA中指定的泛型只是在我们编译期判断该类型是否合法,但是在程序的运行期间我们的元素类型还是Object类型的,C#中泛型才叫真泛型。

List接口下的三个常用实现类

1.ArrayList

ArrayList底层原理介绍:

  • ArrayList底层采用的是数组来实现的,底层中ArrayList中有一个private Object[] elementData;数组,我们往集合中放入的元素就是保存在这个数组中。
  • 在使用无参构造方式创建ArrayList对象的时候,系统会默认给我们分配一个空数组,这个空数组只是定义了,实际上是没有内存分配的,当我们真正要存放元素时,就会给我们分配对应的空间,这种策略是一种典型的”延迟策略“。
  • 在能确定数组元素大小的情况下,尽量的调用有参构造来进行ArrayList集合创建,这样可以避免在添加的过程中发生内部数组的扩容,提前确定数组的大小,能提升程序的性能。
  • 下面时ArrayList添加元素的过程请耐心推看:
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //确定数组的容量是否足够,并传入添加当前元素需要的最小容量  
        elementData[size++] = e;//将新元素的值添加到内部数组的最后并对元素个数进行增
        return true;//返回添加操作的状态
}
private void ensureCapacityInternal(int minCapacity) {// 11
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)//10);
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {//11
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//数组为空
        return Math.max(DEFAULT_CAPACITY, minCapacity);//如果是第一次添加元素则返回的容量是默认值10
    }
    return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {//11
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//如果添加元素要求的最小容量大于数组当前的实际长度,则需要扩容
        grow(minCapacity);
}
private void grow(int minCapacity) {//11
    // overflow-conscious code
    int oldCapacity = elementData.length;//10
    int newCapacity = oldCapacity + (oldCapacity >> 1);//扩容后的容量应该是原来的1.5倍  15
    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);//按照新容量创建一个新的数组并完成从老数组中进行元素值的拷贝
}

注意:在我们第一次往ArrayList集合中添加元素的时候,是会进行数组的扩容操作的。(这里指的是无参构造创建ArrayList集合对象)。数组扩容第一次的容量是:0---->10;
ArrayList的特点:数组是属于线性表这种数据结构,查找和检索获取元素效率非常高,时间复杂度为O(1),但是增加和删除效率低,需要移动元素时间复杂度为O(n)。

2.LinkedList

LinkedList底层采用双向链表,每个元素我们可以称为”节点“,每个节点的分为前、中、后三个部分,前面存放的内容是指向上一个节点的地址,中间存放元素的数据,后面存放的是指向下一个节点的地址。为什么要这么设计呢?因为在内存中这些节点可以不连续的,这样我们就可以充分利用内存的空间。有效利用一些分散的内存碎片。
注意:我这里给大家简单介绍一下碎片是什么,怎么来的?
解释: 举个例子当我们使用数组时,数组的长度都是大于或者等于我们需要存放元素的个数,所以在用数组存放的过程中就会有一些内存空间浪费,这种未利用的空间就叫内部碎片,这种内部碎片是很难处理的,因为数组的长度是静态不可变的。所以我们需要合理的去利用内存空间,就不能产生过多的碎片。
LinkedList特点:遍历和查找效率不高,但是增删的效率非常高,不会影响到链表中其他元素。
在经过上诉的介绍后,LinkedList除了实现List接口中的方法还扩展了一些自己特有的方法,添加了一些特殊位置(头、尾)的插入删除操作,例如:

LinkedList list=new LinkedList();		
list.add("111");
list.addFirst("666");//在头部添加一个新元素
list.addLast("999");//在尾部添加一个新元素		
Object data=list.removeFirst();//移除最前面的元素并返回
list.removeLast();//移除列表中最后一个元素并返回
list.getFirst();//获取头部元素并返回
list.getLast();//获取尾部元素并返回

3.Vector

该集合的使用包括原理和ArrayList完全一样,不同的是大部分方法都有一个synchronized修饰,用来保证线程安全的,此处由于开发中几乎不用,就不做详细介绍。

六、Collections集合工具类的使用

首先我们要弄清楚什么是Collection,什么是Collections。这两者有很大的区别:Collection是一个集合接口,Collections是一个工具类。所以这两者内部构造完全不同,切记不能混淆!

  • 我们可以用Collections工具类给我们提供的静态方法来帮我们操作集合,例如:
Collections.shuffle(list);//随机的进行元素位置的交换  扰动
Collections.sort(list);//排序
Collections.reverse(list);//反转
Collections.synchronizedList(list);//对集合做线程的同步

七、Set接口

Set接口的特点:无序无”重复“

  • 无序:添加的顺序和迭代的顺序可能不一致,但是无序不代表随机,而是没有对应的下标。
  • 无重复:这里的重复,如果是一个对象,我们就要重写hashCode()方法和equals()方法,来帮助我们去重复问题,我们人认为的重复是内容重复就代表重复的,而Object类中只是对与地址的比较,认为地址一样就是重复的。所以需要重写这两个方法。
  • Set接口从Collection接口继承,没有在这个继承上做任何方法扩展。
  • 切记:因为Set接口元素没有下标,无法通过普通的for循环进行遍历,但是可以用增强for循环和迭代器去遍历集合。

面试题:定义一个方法功能是能够对接收到一个List集合,并对其中的元素去除重复的值(过程尽量简单):

public static void main(String[] args) {
		List<Integer> asList = new ArrayList<>();
		asList.add(1);
		asList.add(2);
		asList.add(2);
		asList.add(2);
		List<Integer> list = quChong(asList);
		System.out.println(list);
	}
	public static List<Integer> quChong(List<Integer> list) {
		Set<Integer> set = new HashSet<>(list);
		return new ArrayList<Integer>(set);
	}

1.HashSet

HashSet底层代码:

public HashSet() {
    map = new HashMap<>();
}//也就是说HashSet底层是HashMap

HashMap底层采用的数据结构是散列表(哈希表)
散列表=数组+数据链表
在后面我们将Map集合会详细讲一讲这个HashMap。
HashSet添加元素执行过程:先创建一个长度为16的一个数组。

  1. 根据当前要添加的元素”aaa“,调用hashCode()方法得到一个哈希码;
  2. 根据这个哈希码通过一定算法计算这个对象应该存到哈希表数组的下标;哈希转换也就是讲任何一个哈希码地址的值都能转换成一个固定范围的值。后面讲到HashMap的时候会详细讲解这个算法。
  3. 判断这个下标对应的位置是否已经存在元素?如果不存在,直接存入,如果存在,则依次讲添加的新元素和这个桶位上每个元素进行相等比较(注意是调用Object中equals方法还是重写的equals方法),如果没有发现相同内容的元素则以链表的形式添加到最后,如果有重复则放弃添加注意:HashSet这里遇到相同元素是选择放弃添加,HashMap是选择覆盖掉这个元素。一定切记!

2.LinkedHashSet

这个是HashSet的一个子类,可以保证访问的顺序和添加的顺序的一致,它只是采用在存放元素的时候加入一种手段,将这些元素的先后顺序用链子串起来,以便后续迭代的时候能按照存放的顺序输出。

3.TreeSet

TreeSet就是一个TreeMap,TreeMap底层采用的是二叉树。
特点:能够对容器中所有的元素按照从小到大的顺序排列。

Set<String> set=new TreeSet<>();		
set.add("aaa");
set.add("bbb");
set.add("eee");
set.add("ddd");
System.out.println(set);
//打印:aaa  bbb   ddd     eeee

注意:存入TreeSet的元素必须具有大小的比较能力,否则会抛出运行期异常。
放入TreeMap中的对象必须实现Comparable接口,该接口中定义一个compareTo()的方法,具体的大小比较规则由实现类直接去定制。
指定对象的排序规则由两种方式:

  1. 在对象对应的类中重写compareTo()方法,称为对象的自然排序。
  2. 在创建TreeSet对象时传入一个比较器对象(采用匿名内部类对象的方式)称为定制排序。

如果指定的定制排序规则那么默认的自然排序规则就会失效;
结论:对于经常使用的排序规则按自然排序处理,对于偶尔才使用一次的规则采用现场传入一个比较器对象,例如:

public class Student implements Comparable<Student>{
	private String name;
	private int age;
	private double score;
	public Student(String name, int age, double score) {
		super();
		this.name = name;
		this.age = age;
		this.score = score;
	}
	public double getScore() {
		return score;
	}
	public void setScore(double score) {
		this.score = score;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
	}
	public Student(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Student() {
		super();
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		long temp;
		temp = Double.doubleToLongBits(score);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Student other = (Student) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (Double.doubleToLongBits(score) != Double.doubleToLongBits(other.score))
			return false;
		return true;
	}
	@Override
	public int compareTo(Student o) {
		//System.out.println("--------调用compareTo()--------------");
		// TODO Auto-generated method stub
		if(this.score>o.score) {
			return 1;
		}else if(this.score==o.score) {
			return 0;
		}else {
			return -1;
		}
	}
	
}

测试类如下

//测试
public static void main(String[] args) {
    //下面时定制排序,匿名内部类形式
    Set<Student> set=new TreeSet<>(new Comparator<Student>() {
        public int compare(Student o1, Student o2) {
            return o1.getAge()-o2.getAge();
        };
    });
    set.add(new Student("zs",18,98.5));
    set.add(new Student("ls",28,68.5));
    set.add(new Student("ww",19,88.5));
    set.add(new Student("zs",21,58.5));
    System.out.println(set);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值