Java关于集合学习笔记

学习主题:

Java 中的集合体系
集合是Java常用类库最重要的一部分。
集合是数据的容器。相比于数组数组的长度是固定的,数组无法改变自己的大小,即使使用动态扩容,也是创造新的数组。而且数组不是很适合数据的插入。所以数组满足不了我们的需要,所以Java官方内置了各种数据结构,经过发展到了jdk1.2之后集合到了一起。类集是Java对数据结构一种成熟的体现。
下面我将总结一下我对于集合的相关学习。

Java集合的框架图

学习内容:


Collection接口:

Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。 此接口定义在 java.util 包中。Collection常用方法
Collection或者其他的子类都拥有相同的方法对数据进行操作。
public boolean add(E e) 插入数据
public E get(int index) 获取数据(List中)
public boolean contains(Object o) 判断集合中是否包含
public E remove(int index) 删除数据

List接口:

在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。
List常用方法

注:List相比于Collection大部分方法是可以使用的,其中有一些方法有重载和扩展。
其中remove方法进行了重载,具体如下:
public E remove(int index)删除指定位置的内容。使用下标进行操作,并且取出来。如果你操作List,想获取和删除共同进行,即可以使用remove方法。
public E set(int index,E element)方法是对某个指定下标进行修改,覆盖的操作。
public void add(int index,E element)方法是对其进行添加,通俗的说就是往后 挤。
List subList(int fromIndex,int toIndex)方法就是给定一段下标进行截取。

List 接口类有如下几个: ArrayList(95%)(线程不安全),Vector(4%)(线程安全),LinkedList(1%)

ArrayList接口:

ArrayList为数组结构,特点:增删慢,查找快

// ArrayList的声明
ArrayList<Integer> data= new ArrayList<>();

根据API查看构造方法:
构造方法
构造方法内输入数字为数组大小,每次扩容为原来的1.5倍,建议使用一参构造方法根据数组大小输入合适的数。
Q:为什么初始容量为10?
A:根据源码
无参构造函数
其中elementData是可以存储任何数据类型的数组,赋值DEFAULTCAPACITY_EMPTY_ELEMENTDATA,的值为

transient Object[] elementData;
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

长度为0

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };

正是因为长度为0,在发现无法存入的时候,就会发生扩容。我们再来查看一下扩容的源码。不同版本的jdk貌似算法有所不同,我以自己电脑上的版本为准。先初始化ArrayList,再进行添加,初始长度为0,则进行扩容。

   public boolean add(E e) {
    
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

返回为true,再查看ensureCapacityInternal()与calculateCapacity()两个方法;

扫描二维码关注公众号,回复: 11655798 查看本文章
    private void ensureCapacityInternal(int minCapacity) {
    
    
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    
    
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

可以看出如果数组elementData与DEFAULTCAPACITY_EMPTY_ELEMENTDATA相同,也就是都为空时,就返回DEFAULT_CAPACITY与minCapacity最大值,而minCapacity为1,DEFAULT_CAPACITY默认为10。

private static final int DEFAULT_CAPACITY = 10;

所以扩容长度默认为10.

接下来我们用实例来测试其他的方法,源代码和结果如下:

import java.util.ArrayList;
import java.util.Collection;

public class Test1 {
    
    
	public static void main(String[] args) {
    
    
		//数组结构
		//特点:增删慢,查找快
		ArrayList<Integer> data= new ArrayList<>();
		data.add(1);//顺序添加
		data.add(2);
		data.add(3);
		data.add(4);
		data.add(5);
		data.add(0,6);//第一个参数是下标插入
		System.out.println(data); // 打印all对象调用toString()方法

	}
}

在这里插入图片描述

import java.util.ArrayList;
import java.util.Collection;

public class Test1 {
    
    
	public static void main(String[] args) {
    
    
		//数组结构
		//特点:增删慢,查找快
		ArrayList<Integer> data= new ArrayList<>();
		ArrayList<String> data1= new ArrayList<>();
		data.add(1);//顺序添加
		data.add(2);
		data.add(3);
		data.add(4);
		data.add(5);
		data.add(0,6);//第一个参数是下标插入
		data.remove(4);//删除下标为4的数
		data1.add("hello");//插入数组类型的
		data1.add("world");
		data1.remove("hello");//删除内容为hello的数组
		System.out.println(data); // 打印all对象调用toString()方法

	}
}

在这里插入图片描述

Vector接口:

Vector 本身也属于 List 接口的子类,基本和ArrayList一样。

import java.util.Vector;

public class Test1 {
    
    
	public static void main(String[] args) {
    
    
		Vector<String> all = new Vector<String>(); // 实例化List对象,并指定泛型类型 
		all.add("hello "); // 增加内容,此方法从Collection接口继承而来 
		all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的 
		all.add("world"); // 增加内容,此方法从Collection接口继承而来 
		all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的 
		all.remove("world");// 删除指定的对象 
		System.out.print(all); 
	}
}

在这里插入图片描述
区别:
在这里插入图片描述
在这里插入图片描述
相比于ArrayList可以自定义扩容的大小。

LinkedList:

此类的使用几率是非常低,此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类。主要是链表,增删快,查找满。可以当作栈或者队列使用。
在这里插入图片描述
新加操作:
public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的最后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的最后一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public void push(E e) :将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true。

LinkedList<Integer> all = new LinkedList<Integer>(); // 实例化List对象,并指定泛型类型 
		LinkedList<Integer> all1 = new LinkedList<Integer>();
		//压栈
		all.push(1);
		all.push(2);
		all.push(3);
		//弹栈
		Integer i = all.pop();
		//队列
		all1.addFirst(4);
		all1.addFirst(5);
		all1.removeLast();
		System.out.println(all); 
		System.out.println(i); 
		System.out.println(all1); 

在这里插入图片描述

Iterator迭代器:

Iterator主要是针对输出集合,Ilterator主要是针对Collection,而ListIlterator是针对List。

常用方法:
public E next() :返回迭代的下一个元素。
public boolean hasNext() :如果仍有元素可以迭代,则返回 true。

主要原理是在数组第一个地址之前有一个指针,通过next()方法,不断地向下移动,再用hasNext()判断循环调节,即可进行遍历。换句话说,hasNext()说如果下一个地址有元素,next()就可以将指针往后移动并且返回下一个元素。具体实现如下

ArrayList<Integer> all = new ArrayList<Integer>(); // 实例化List对象,并指定泛型类型 
		all.add(1); // 增加内容,此方法从Collection接口继承而来 
		all.add(2);
		all.add(3);
		all.add(4);
		all.add(5);
		all.add(6);
		Iterator<Integer> iterator = all.iterator();
		while(iterator.hasNext()) {
    
    
			Integer i = iterator.next();
			System.out.println(i);
		}

在这里插入图片描述
其中remove()方法一定要获取后再进行删除,具体实现也就是先next()移动指针再进行remove();但是此时指针已经移动,如果想回复指针初始状态,则必须配合previous进行使用。

增强for:

增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作

ArrayList<Integer> all = new ArrayList<Integer>(); // 实例化List对象,并指定泛型类型 
		all.add(1); // 增加内容,此方法从Collection接口继承而来 
		all.add(2);
		all.add(3);
		all.add(4);
		all.add(5);
		all.add(6);
		for(int i:all) {
    
    
			System.out.println(i);
		}

String类型也是一样:

		ArrayList<String> all = new ArrayList<String>(); // 实例化List对象,并指定泛型类型 
		all.add("杨花落尽子规啼,"); // 增加内容,此方法从Collection接口继承而来 
		all.add("闻道龙标过无锡。");
		all.add("我寄愁心与明月,");
		all.add("随风直到夜郎西。");

		for(String i:all) {
    
    
			System.out.println(i);
		}

在这里插入图片描述

Set接口:

Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。 Set 接口并没有对 Collection 接口进行扩充,基本上还是与 Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法使用循环进行输出。 那么在此接口中有两个常用的子类:HashSet、TreeSet。

总结的来说Set是一个不包含重复元素的单值集合。

HashSet:
方法和Collection一样(因为是子类嘛)但是没有get的方法,可以用interator进行迭代。他的特性是散列存储,也被称之为散列表。
根据源码,HashSet使用add方法,是将元素放入一个HashMap里面。后续会讲解HashMap,总之特性解释乱序。

  	public boolean add(E e) {
    
    
        return map.put(e, PRESENT)==null;
    }
    private transient HashMap<E,Object> map;
HashSet<String> all = new HashSet<String>(); // 实例化List对象,并指定泛型类型 
		all.add("杨花落尽子规啼,"); // 增加内容,此方法从Collection接口继承而来 
		all.add("闻道龙标过无锡。");
		all.add("我寄愁心与明月,");
		all.add("随风直到夜郎西。");

		for(String i:all) {
    
    
			System.out.println(i);
		}

在这里插入图片描述

HashSet<String> all = new HashSet<String>(); // 实例化List对象,并指定泛型类型 
		all.add("杨花落尽子规啼,"); // 增加内容,此方法从Collection接口继承而来 
		all.add("闻道龙标过无锡。");
		all.add("我寄愁心与明月,");
		boolean flag1 = all.add("随风直到夜郎西。");
		boolean flag2 = all.add("随风直到夜郎西。");

		System.out.println(flag1);
		System.out.println(flag2);

在这里插入图片描述
这里可以显示无法存入相同元素。
TreeSet:
和HashSet不同,TreeSet采用二叉树进行操作,方法都是一样的,但是数据结构不同。顺序不是无序,而是自然顺序。具体展示如下:

		TreeSet<String> all = new TreeSet<String>(); // 实例化List对象,并指定泛型类型 
		all.add("杨花落尽子规啼,"); // 增加内容,此方法从Collection接口继承而来 
		all.add("闻道龙标过无锡。");
		all.add("我寄愁心与明月,");
		all.add("随风直到夜郎西。");

		for(String i:all) {
    
    
			System.out.println(i);
		}

在这里插入图片描述

TreeSet<String> all = new TreeSet<String>(); // 实例化List对象,并指定泛型类型 
		all.add("z"); // 增加内容,此方法从Collection接口继承而来 
		all.add("b");
		all.add("a");
		all.add("e");

		for(String i:all) {
    
    
			System.out.println(i);
		}

在这里插入图片描述
由此可见,字符串,都是由Unicode码进行排序。但是如果你不使用系统自带的类型,而是自定义的类型,比如class Person类,这个时候使用TreeSet就会发生错误。所以再使用自定义类的时候一定要给出判断大小的定义。

TreeSet<Person> all = new TreeSet<Person>(); // 实例化List对象,并指定泛型类型 
		Person p1 = new Person("小李",18);
		Person p2 = new Person("小卢",19);
		all.add(p1);
		all.add(p2);
		for(Person i:all) {
    
    
			System.out.println(i);
		}
	}
	static class Person{
    
    
		private String name;
		private int age;
		
		public Person(String name,int age) {
    
    
			this.name = name;
			this.age = age;
		}
	}

在这里插入图片描述

由此引出一个Comparable类,并使用其一个抽象方法compareTo:

		public int compareTo(Person per) {
    
     
			if (this.age > per.age) {
    
     
				return 1; 
			} 
			else if (this.age < per.age) {
    
     
				return -1; 
			} 
			else {
    
     
				return 0; 
			} 
		}

由此,Person类的判断规则是年龄来进行比较,返回值为正数,负数和0。
完善后的代码如下:

public static void main(String[] args) {
    
    
		TreeSet<Person> all = new TreeSet<Person>(); // 实例化List对象,并指定泛型类型 
		Person p1 = new Person("小李",18);
		Person p2 = new Person("小卢",19);
		all.add(p1);
		all.add(p2);
		for(Person i:all) {
    
    
			System.out.println(i);
		}
	}
	static class Person implements Comparable<Person>{
    
    
		private String name;
		private int age;
		
		public Person(String name,int age) {
    
    
			this.name = name;
			this.age = age;
		}
		public String toString() {
    
     
			return "姓名:" + this.name + ",年龄:" + this.age; 
		}
		public int compareTo(Person per) {
    
     
			if (this.age > per.age) {
    
     
				return 1; 
			} 
			else if (this.age < per.age) {
    
     
				return -1; 
			} 
			else {
    
     
				return 0; 
			} 
		}
	}

在这里插入图片描述

Map 接口:

Map不再是单值存储,Map和Collection同一级别。存储的是一对数据,为键值对,key,value。
相当于钥匙和锁。数据接相当于锁,需要对应的key去打开。
形象的说身份证号和人,身份证号不可重复,而Map中key也是不可重复的。
在这里插入图片描述
Map和数组类似,都是通过键来寻找数据,数组是系统提供的0,1,2,3…,而Map是自定义的。
Map的遍历十分麻烦,先用keyset()把键全部拿出来,再进行迭代进行寻找。
其中添加操作V put(K key,V value),原理是用新值覆盖旧值,如果不存在旧值就返回null,如果存在旧值就返回旧值。
而删除remove(),可以通过键来删除,也可以通过一对键值对来删除。通过键来删除的时候,返回被删除的数据(成功),失败的话就是 null。
boolean containsValue(Object value)判断是否存在值。
boolean containsKey(Object key)判断是否存在键。

HashMap:

哈希表的结构是对象数组+链表。
hashcode的int值与数组长度进行取模的运算。如果取模结果重复了,那将存储在同一个数组中(哈希桶),数组中存在一个链表可以不断地向下存储。
当一个数组中链表的长度大于8是会将链表转化为红黑树,哈希桶中的数据量减小为6时,会从红黑树变为链表。
还存在特殊的特性,Java中初始桶的数量为16,散列因子为0.75.

static final float DEFAULT_LOAD_FACTOR = 0.75f;

通俗的来说,当75%的桶子装了时将会对桶进行扩容。
在这里插入图片描述

Map<Integer, String> map = new HashMap<Integer, String>(); 
		map.put(1, "张三A"); 
		map.put(1, "张三B"); // 新的内容替换掉旧的内容 
		map.put(2, "李四"); 
		map.put(3, "王五"); 
		String val = map.get(1); 
		System.out.println(val);

在这里插入图片描述

Map<Integer, String> map = new HashMap<Integer, String>(); 
		map.put(1, "张三A"); 
		map.put(2, "李四"); 
		map.put(3, "王五"); 
		Set<Integer> set = map.keySet(); // 得到全部的key 
		Collection<String> value = map.values(); // 得到全部的value 
		Iterator<Integer> iter1 = set.iterator(); 
		Iterator<String> iter2 = value.iterator(); 
		System.out.print("全部的key:"); 
		while (iter1.hasNext()) {
    
     
			System.out.print(iter1.next() + "、");
		}
		System.out.print("\n全部的value:"); 
		while (iter2.hasNext()) {
    
     
			System.out.print(iter2.next() + "、"); 
		}

在这里插入图片描述

Map<String, String> map = new HashMap<String, String>(); 
		map.put("ZS", "张三"); 
		map.put("LS", "李四");
		map.put("WW", "王五"); 
		map.put("ZL", "赵六"); 
		map.put("SQ", "孙七"); 
		Set<String> set = map.keySet(); // 得到全部的key 
		Iterator<String> iter = set.iterator(); 
		while (iter.hasNext()) {
    
     
			String i = iter.next(); // 得到key 
			System.out.println(i + " --:> " + map.get(i)); 
		}

在这里插入图片描述

在这里插入图片描述
注:HashMap键的数据对象,一定不能乱改,特别是自定义对象,因为key->value是根据哈希值进行查找,一旦改变将会丢失。

HashMap 与 Hashtable 的区别:

在这里插入图片描述
HasshMap,Hashtable,ConcurrentHashMap,三者最大的区别在于多线程,线程的安全。
HasshMap 线程不安全,效率高;Hashtable 线程安全,效率低。
ConcurrentHashMap采用分段锁机制,保证线程安全,和效率高。

猜你喜欢

转载自blog.csdn.net/cying7/article/details/108537699