Java之集合详解

集合

一、集合的概念

集合是指存储多个对象,比数组结构复杂,功能更加强大的结构。

二、Collection父接口

具备有基本的对集合元素增删改查的方法。

三、List子接口及其实现类

List子接口具备有以下特点:

有序

有下标

元素可以重复

3.1 ArrayList[重点]

ArrayList是List接口最常用的一个实现类。特点是底层以数组的方式实现。

特点是:可以使用下标访问,遍历速度快,添加,删除元素性能较差,需要扩容,复制。

非线程安全

简单使用:

public static void main(String[] args) {
    ArrayList list = new ArrayList<>(); // 创建一个集合
    list.add("hello"); // 添加元素
    list.add(2); // 实际添加的是包装类的对象,而不是int,因为集合不能添加基本数据类型
    Student stu = new Student("1", "张三", 20);
    list.add(stu);
    Integer in = (Integer)list.get(1); // 根据下标获取数据
    System.out.println(in);
    System.out.println(list.get(2));
    System.out.println("集合元素个数:" + list.size());
    // 删除元素
    list.remove(0); // 根据下标删除
    System.out.println("集合元素个数:" + list.size());
    // 直接覆盖某个下标上的元素
    list.set(1, 3); 
    // 遍历元素
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
    // 判断集合是否为空
    System.out.println(list.isEmpty());
    // 判断集合是否包含元素
    System.out.println(list.contains(3));
    // 创建集合
    ArrayList list1 = new ArrayList<>();
    // 创建一个新的对象,此对象与上面的stu内容一致
    Student stu1 = new Student("1", "张三", 20);
    list1.add(stu1); // 添加元素stu1
    //判断集合是否包含元素stu,通过该对象的equals方法判断
    System.out.println("是否包含stu:" + list1.contains(stu)); 
    // 清空集合
    list1.clear();
    // 通过截取一个集合的部分元素形成一个新的集合
    List list2 = list.subList(0, 1); // 截取0号元素到1号元素
    for (int i = 0; i < list2.size(); i++) {
        System.out.println(list2.get(i));
    }
}

原理部分:

1、创建对象时,创建长度为多少的数组?

如果通过无参构造方法创建,则创建长度为0的数组,如果通过有参构造方法,int类型的参数的大小即为创建数组的长度。

transient Object[] elementData; // ArrayList中存放对象的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 空数组
private static final Object[] EMPTY_ELEMENTDATA = {}; // 空数组

// 无参构造方法,指定当前数组为空数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 有参构造方法指定当前数组长度为传入的长度
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

2、无参构造创建的空数组,第一次添加元素,会如何扩容?

直接扩容为10个大小。

如果添加元素到达数组能存放的最大大小时,如何扩容?(扩容因子为达到上限。)

扩容1/2,最终大小为原来的1.5倍

private int size; // 当前数组中存放的元素个数

private static final int DEFAULT_CAPACITY = 10; // 默认指定的大小

public int size() {
    return size;
}

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // 判断是否扩容
    elementData[size++] = e; // 将元素放入到数组中
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    // 如果是通过无参构造方法创建的集合,并且第一次添加元素,直接指定大小默认大小(10个)
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    // 扩容方法
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0) // 判断指定的大小是否比数组大小大
        grow(minCapacity); // 空间扩容
}

/*
扩容并复制元素
*/
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 每次扩容扩大1/2,即最终大小为原来的1.5倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0) // 如果超过保留大小,则直接指定为int的最大值
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity); // 复制元素
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}
3.2 Vector

使用与ArrayList基本一样。线程安全,效率低。现在基本被废弃。

3.3 LinkedList

数组和链表:

数组特点是遍历性能好,添加,删除性能差。

而链表结构与数组相反,添加、删除性能好,但是遍历性能相对差。(单向和双向)

基本用法:

public static void main(String[] args) {
    LinkedList list = new LinkedList();
    // 链表结构向头尾添加性能最优
    list.add("hello"); // 直接使用add会自动添加到尾部,相当于addLast
    list.addLast("world");// 向尾部添加
    list.getLast(); // 得到尾部的元素
    list.getFirst(); // 得到头部的元素
    // 通过下标查找,性能不好,需要循环遍历
    // 代码有相应的优化,即如果该下标小于中间值,则从头开始向下找
    // 如果该下标大于中间值,则从尾部开始向上找
    list.get(1); // 得到第一号元素
    list.remove(1); // 根据下标删除
    list.removeLast();// 删除最后一个
}

总结:

LinkedList会有一个first,即头节点,last即尾节点。

每次向头部添加元素,只需要构造一个节点对象,并将该节点的next指向原list的first,并将list的first指向当前节点即可。

每次向尾部添加元素,只需要构造一个节点对象,并将原list的last节点的next指向该节点,并将该节点的prev指向原list的last,并将list的last指向当前节点即可。

四、泛型

泛型的作用,主要是限定在使用某些类或者方法时,对应类型。

例如,泛型集合。在使用泛型创建的集合中,该集合必须使用该泛型类型的元素,获取时不需要强制转换。

自定义泛型:

public class MyList<E> {
	public void m(E e) {
		
	}
}

集合泛型的使用:

public static void main(String[] args) {
    // 当没有使用泛型时,向集合中添加元素时,可以添加任意类型
    // 当从集合中取出元素时,需要转换成相应的类型
    ArrayList list = new ArrayList();
    list.add("hello");
    list.add(2); // Integer
    Integer in = (Integer) list.get(1);
    for (int i = 0; i < list.size(); i++) {
        Object obj = list.get(i);
    }

    // 对集合使用泛型
    // 通过泛型要求该集合只能使用String类型
    // 添加了泛型后获取元素不用强制转换
    ArrayList<String> list1 = new ArrayList<>();
    list1.add("hello");
    list1.add("world");
    String str = list1.get(1);
    System.out.println(str);

    // foreach循环
    for (String string : list1) {
        System.out.println(string);
    }

    MyList<String> my = new MyList<>();
    my.m("hello");
}

注意:Java的泛型为假泛型
Java中的泛型主要是编译前的代码检查和赋值时类型的改变,在实际运行时并没有泛型。

五、Collections工具类

Collections工具类是针对集合的操作编写的工具类。可以对集合进行排序、倒置、打乱等操作。

注意:如果集合元素是自定义的元素,排序之前需要实现java.lang.Comparable接口。

public static void main(String[] args) {
    // 直接将几个元素一次性放入一个集合中
    List<String> list = Arrays.asList("hello", "world", "test", "aaa");
    // 倒置
    Collections.reverse(list);
    System.out.println(list);
    // 随机打乱
    Collections.shuffle(list);
    System.out.println(list);
    // 排序
    Collections.sort(list);
    System.out.println(list);


    List list1 = Arrays.asList(
        new Product(5, "牙膏"),
        new Product(4, "牙刷"),
        new Product(1, "毛巾"),
        new Product(2, "杯子"),
        new Product(3, "衣架")
    );
    // 排序
    // 如果集合中是自定义元素,需要实现java.lang.Comparable接口
    Collections.sort(list1);
    System.out.println(list1);
}
public class Product implements Comparable<Product>{
	private int id;
	private String name;
	// 省略getter、setter、构造、等方法的重写
    
	@Override
	public int compareTo(Product o) {
		if(o != null) {
			if(this.id > o.id) {
				return 1;
			}else if(this.id < o.id){
				return -1;
			}else {
				return 0;
			}
		}
		return 0;
	}
}
六、Map接口及其实现类

map接口使用key-value方式来保存数据。

常见方法:put(key, value)

get(key)

keySet()

values()

6.1 HashMap[重点]

使用key-value方式存储。线程不安全。

public static void main(String[] args) {
    // 使用key-value的形式存储
    // key键 value值
    // key不能重复,如果重复,值覆盖
    // key可以是任意类型,但是推荐使用字符串
    // key可以为null,但是只能有一个,value可以是null
    HashMap<String, Song> map = new HashMap<>();
    // 添加元素
    map.put("a", new Song("如烟", "五月天", 200));
    // 取值,如果通过一个没有的名称获取值,返回null
    Song s = map.get("a");
    System.out.println(s.play());
    // 尝试key重复的情况,会覆盖
    map.put("a", new Song("知足", "五月天", 200));
    // 取值
    Song s1 = map.get("a");
    System.out.println(s1.play());
    // keySet 键集(所有的键的集合)
    Set<String> keys = map.keySet();
    // 借助键集来循环
    for (String string : keys) {
        System.out.println("key:" + string + ", value:" + map.get(string).play());
    }
    // 值集
    Collection<Song> values = map.values();
    // 循环值集
    for (Song song : values) {
        System.out.println(song.play());
    }
    // 大小
    int size = map.size();
    // 循环键值(使用entrySet)
    for (Entry<String, Song> entry : map.entrySet()) {
        System.out.println("key:" + entry.getKey() + ", value:" + entry.getValue().play());
    }

    // 删除,如果删除一个没有的名称,对应原集合没有任何影响
    map.remove("b");

    // 是否包含key
    map.containsKey("a");
    // 是否包含值
    Song song = new Song("", "", 100);
    map.containsValue(song); // 是通过equals来判断
}

原理:

以数组+链表结构组合而成。既有数组的优点,又有链表的优点。

添加元素时,根据key的hash值,来确定添加到哪一个数组的元素节点后。

查询元素时,根据key的hash值,来提升查找的性能。

添加元素时,当元素的总个数大于64,且单条链表上的元素超过8个时,会将该条链表转换成红黑树(JDK1.8),以进一步提升查找的性能。

扩容因子:0.75

扩容容量x2

即当元素的个数达到容量的0.75时,进行扩容,扩容大小为原来的两倍。

6.2 Hashtable

用法基本与HashMap相似,但是是线程安全的,key和value都不能为null。性能较差,已基本不用。

6.3 Properties

继承自Hashtable,但是添加限制键值类型为String的操作方法。

setProperty()

getProperty()

作用是为了操作配置文件.properties文件而存在。

config.properties

username=zhangsan
password=1234567890
public static void main(String[] args) throws IOException {
    // 存放配置信息
    Properties prop = new Properties();
    prop.setProperty("path", "c:/users/");
    prop.setProperty("username", "root");
    prop.setProperty("password", "root");
    System.out.println(prop.getProperty("password"));
    // 很多时候配置信息是写在工程的某一个文件中
    // 读取properties文件信息
    Properties prop1 = new Properties();
    // 读取配置文件并加载文件中的信息
    InputStream is = MyPropertiesDemo.class.getResourceAsStream("/config.properties");
    prop1.load(is); // 加载信息
    System.out.println(prop1.getProperty("username"));
    System.out.println(prop1.getProperty("password"));
}
6.4 有序、无序和排序

有序是指会按照添加的顺序规律来保存数据。循环输出时会体现该规律。反之则是无序。

会将添加的元素按照指定的排序规则进行排序,取值或循环显示时会按照排序后的结果显示。

6.4 TreeMap

实现了SortedMap接口,要求所有的key应该实现java.lang.Comparable接口,会在添加元素时按照指定的排序规则进行排序。

public static void main(String[] args) {
    // 添加元素时,会按照指定排序规则将添加的元素key进行排序
    TreeMap<Song, String> map = new TreeMap<>();
    map.put(new Song("", "", 200), "zhangsan");
    map.put(new Song("", "", 220), "lisi");
    map.put(new Song("", "", 120), "wangwu");
    map.put(new Song("", "", 60), "zhaoliu");
    Set<Song> keySet = map.keySet();
    for (Song song : keySet) {
        System.out.println(song.play());
    }
}
七、Set接口及其实现类

无序、无下标、元素不重复

7.1 HashSet[重点]

元素不重复是使用hashCode来计算的,所以要实现该功能,需要元素对应的类型重写equals和hashCode方法。

添加相同元素,后面添加的会无法添加,保留之前的元素,不会覆盖。

基本用法:

public static void main(String[] args) {
    // 无序、元素不能重复
    HashSet<String> set = new HashSet<>();
    set.add("f");
    set.add("a");
    set.add("g");
    set.add("c");
    set.add("c");
    for (String string : set) {
        System.out.println(string);
    }

    // 小技巧:如果遇到需要去重功能,可以将元素放入set中
    // 注意元素是否重复是根据hashCode和equals来判断,需要重写这些方法
    HashSet<Student> set1 = new HashSet<>();
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    for (Student student : set1) {
        System.out.println(student);
    }

    System.out.println("=====去重前======");
    // 去重功能
    ArrayList<Student> list = new ArrayList<>();
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(2, "lisi"));
    list.add(new Student(2, "lisi"));
    list.add(new Student(3, "wangwu"));
    list.add(new Student(3, "wangwu"));
    for (Student student : list) {
        System.out.println(student);
    }
    System.out.println("=====去重后======");
    HashSet<Student> set2 = new HashSet<>(list);
    for (Student student : set2) {
        System.out.println(student);
    }
}

原理:使用HashMap的key作为set的元素存储空间。

7.2 LinkedHashSet

有序。(添加元素的顺序,使用链表实现<双向>)

LinkedHashSet继承自HashSet,所以具备有HashSet的基本使用方式。

不同的是,实现来自于LinkedHashMap,LinkedHashMap继承自HashMap,加了一个链表来实现了添加有序功能。

public static void main(String[] args) {
    // 有序(插入顺序)、元素不能重复
    LinkedHashSet<String> set = new LinkedHashSet<>();
    set.add("f");
    set.add("a");
    set.add("g");
    set.add("c");
    set.add("c");
    for (String string : set) {
        System.out.println(string);
    }

    // 小技巧:如果遇到需要去重功能,可以将元素放入set中
    // 注意元素是否重复是根据hashCode和equals来判断,需要重写这些方法
    LinkedHashSet<Student> set1 = new LinkedHashSet<>();
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    for (Student student : set1) {
        System.out.println(student);
    }

    System.out.println("=====去重前======");
    // 去重功能
    ArrayList<Student> list = new ArrayList<>();
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(2, "lisi1"));
    list.add(new Student(2, "lisi2"));
    list.add(new Student(3, "wangwu"));
    list.add(new Student(3, "wangwu"));
    for (Student student : list) {
        System.out.println(student);
    }
    System.out.println("=====去重后======");
    LinkedHashSet<Student> set2 = new LinkedHashSet<>(list);
    for (Student student : set2) {
        System.out.println(student);
    }
}
7.3 TreeSet

元素需要实现java.lang.Comparable接口,会自动将元素排序的集合。

底层是使用TreeMap来实现。

基本使用方法跟HashSet基本一样。

public static void main(String[] args) {
    // 排序、元素不能重复
    TreeSet<String> set = new TreeSet<>();
    set.add("f");
    set.add("a");
    set.add("g");
    set.add("c");
    set.add("c");
    for (String string : set) {
        System.out.println(string);
    }

    // 小技巧:如果遇到需要去重功能,可以将元素放入set中
    // 注意元素是否重复是根据hashCode和equals来判断,需要重写这些方法
    TreeSet<Student> set1 = new TreeSet<>();
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    set1.add(new Student(1, "zhangsan"));
    for (Student student : set1) {
        System.out.println(student);
    }

    System.out.println("=====去重前======");
    // 去重功能
    ArrayList<Student> list = new ArrayList<>();
    list.add(new Student(3, "wangwu"));
    list.add(new Student(3, "wangwu"));
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(1, "zhangsan"));
    list.add(new Student(2, "lisi1"));
    list.add(new Student(2, "lisi2"));
    for (Student student : list) {
        System.out.println(student);
    }
    System.out.println("=====去重后======");
    TreeSet<Student> set2 = new TreeSet<>(list);
    for (Student student : set2) {
        System.out.println(student);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

向天祈祷不掉头发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值