Java基础(十二)-集合

集合

用于存储一组数据的大小不定的容器。

Collection<E>

集合的顶级接口

Collection<E>---E表示泛型

Collection<String> c;---表示集合中存储的是String类型---由于泛型的限定,集合中只能存储引用类型的数据

// 表示元素类型是String类型
Collection<String> c = new ArrayList<String>();
// 添加元素
c.add("adf");
// 将集合转化为Object类型的数组
 Object[] os = c.toArray();
// 将集合转化为对应类型的数组
/*
 * 在底层会判断传入的数组的大小是否大于等于元素的个数
 * 如果大于等于了元素个数,则直接使用传入的数组来存储元素,最后返回传入的数组。
 * 如果小于元素个数,底层只会根据传入对的类型来创建一个和元素个数等大的数组
 */
String[] ss = c.toArray(new String[0]);
for (String s : ss) {
System.out.println(s);
}
// 获取元素个数---集合中元素的个数和集合的大小是两个概念
System.out.println(c.size());
//清空集合
c.clear();
// 判断是否一个空集合
 System.out.println(c.isEmpty());
// 遍历集合
for (String s : c) {
 System.out.println(s);
 }
// 删除元素---在删除这个元素之前会首先判断这个元素是否存在。
 c.remove("adg");
// 判断指定的元素是否存在
System.out.println(c.contains("jkl"));

List<E>

保证元素的存入顺序---元素是有序的,可以存储重复元素---元素有序可重复

元素存在下标,所以可以通过下标来获取和操作对应的元素

List<String> list = new ArrayList<String>();
// 添加元素
list.add("abc");
list.add("def");
list.add("ghi");
// list.add(10, "xyz");
// 移除指定下标位置上的元素
list.remove(2);
// 替换指定下标上的元素
 list.set(1, "opq");
// 获取指定下标位置上的元素
 System.out.println(list.get(3));
// 获取指定元素在列表中第一次出现的位置
// 如果元素不存在,则返回-1
System.out.println(list.indexOf("ad"));
// 向列表的指定下标上插入指定的元素
list.add(1, "mno");
// 判断两个列表是否一致
List<String> list2 = new ArrayList<String>();
list2.add(new String("abc"));
 list2.add("def");
 list2.add("ghi");
 list2.add("jkl");
 list2.add("abc");
// 在比较两个集合是否一致的时候,实际上依次调用对应位置上的元素的equals来比较两个元素是否一致。
System.out.println(list.equals(list2));

ArrayList

基于数组的。默认初始容量是10,每次是在上一次的基础上扩容一半。 10 -> 15 -> 22内存空间连续。是一个线程不安全的集合.增删元素比较慢,查询元素较快

练习:数组来实现一个简易版的ArrayList,元素是String,实现add/remove/set/get/indexOf/contains/isEmpty/size/toString方法

public class ListExer {
	// 存储元素
	private String[] data;
	// 记录已经存储的元素个数
	private int size = 0;
	public ListExer() {
		data = new String[10];
	}
	public ListExer(int capacity) {
		// 需要判断容量是否符合事实
		if (capacity < 0) {
			capacity = 10;
		}
		data = new String[capacity];
	}
	private void grow() {
		if (data.length <= 1) {
			data = Arrays.copyOf(data, data.length + 1);
			return;
		}
		data = Arrays.copyOf(data, data.length + (data.length >> 1));
	}
	public void add(String s) {
		// 判断是否需要扩容
		if (size >= data.length) {
			this.grow();
		}
		data[size] = s;
		size++;
	}

	public void add(int index, String s) {
		// 判断下标是否越界
		if (index > size) {
			throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
		}
		// 判断是否需要扩容
		if (size >= data.length) {
			this.grow();
		}
		// 插入元素
		// for (int i = size - 1; i >= index; i--) {
		// data[i + 1] = data[i];
		// }
		System.arraycopy(data, index, data, index + 1, size - index);
		data[index] = s;
		size++;
	}
       public void remove(String s) {
		// 获取这个元素第一次出现的下标
		int index = this.indexOf(s);
		// 判断这个下标是否存在
		if (index != -1) {
			this.remove(index);
		}
	}
	private void out(int index) {
		// 判断下标越界
		if (index >= size) {
			throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
		}
	}
	public void remove(int index) {
		this.out(index);
                // for (int i = index; i < size - 1; i++) {
		// data[i] = data[i + 1];
		// }
		System.arraycopy(data, index + 1, data, index, size - index - 1);
		size--;
	}
	public void set(int index, String s) {
		this.out(index);
		data[index] = s;
	}
	public String get(int index) {
		this.out(index);
		return data[index];
	}
	public boolean contains(String s) {
		return this.indexOf(s) != -1;
	}

	public boolean isEmpty() {
		return size <= 0;
	}
	public int indexOf(String s) {
		for (int i = 0; i < size; i++) {
			if (s == data[i] || s != null && s.equals(data[i])) {
				return i;
			}
		}
		return -1;
	}
	public int size() {
		return size;
	}
	public String toString() {
		StringBuilder sb = new StringBuilder("[");
		for (int i = 0; i < size; i++) {
			sb.append(data[i]).append(", ");
		}
		String str = sb.toString();
		if (str.length() > 1) {
			str = str.substring(0, str.length() - 2);
		}
		return str += "]";
	}
	
	public static void main(String[] args) {		
		ListExer le = new ListExer(3);	
		le.add("a");
		le.add("b");
		le.add("c");		
		le.add(0,"e");		
		le.remove(0);
		System.out.println(le);		
	}
}

LinkedList

基于链表实现的。内存空间是不连续的。增删元素相对较快,查询元素较慢。是一个线程不安全的集合


面试题:

ArrayList 和 LinkedList 有什么区别?

ArrayList和LinkedList都实现了List接口,有以下的不同点: 
1、ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。 
2、相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。 
3、LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。

Vector

基于数组的。默认的初始容量是10,每次扩容一倍。内存空间连续。增删元素较慢,查询元素相对较快。是一个线程安全的集合。---Java中最早的集合。

Stack

栈。---遵循先进后出的原则。最先放入的元素---栈底元素,最后放入的元素---栈顶元素。将元素放入栈中---入栈/压栈;将元素从栈中取出---出栈/弹栈


练习:

1. Vector来实现Stack

public class StackExer1 {
	private Vector<String> v = new Vector<>();
	public StackExer1() {
	}
	public boolean empty() {
		return v.isEmpty();
	}
	public String peek() {
		// 判断栈是否为空
		if (v.isEmpty()) {
			throw new EmptyStackException();
		}
		return v.get(v.size() - 1);
	}
	public String pop() {
		String str = this.peek();
		// 移除栈顶元素
		v.remove(v.size() - 1);
		return str;
	}
	public void push(String str) {
		v.add(str);
	}
	public int search(String str) {
		for (int i = v.size() - 1; i >= 0; i--) {
			if (str == v.get(i) || str != null && str.equals(v.get(i))) {
				return v.size() - i;
			}
		}
		return -1;
	}
}

2. 用数组实现Stack

public class StackExer2 {
	private String[] data = new String[10];
	private int size = 0;
	public StackExer2() {
	}
	public boolean empty() {
		return size <= 0;
	}
	public String peek() {
		if (size <= 0) {
			throw new EmptyStackException();
		}
		return data[size - 1];
	}
	public String pop() {
		String str = this.peek();
		size--;
		return str;
	}
	public void push(String s) {
        	if (size >= data.length) {
			data = Arrays.copyOf(data, data.length * 2);
		}
		data[size] = s;
		size++;
	}
	public int search(String str) {
		for (int i = size - 1; i >= 0; i--) {
			if (str == data[i] || str != null && str.equals(data[i])) {
				return size - i;
			}
		}
		return -1;
	}
}

Queue

队列---先进先出

线性集合:List,Queue

Set

散列集合---元素不可重复,不保证元素顺序---Set中的元素无序不可重复

HashSet---默认初始容量是16,加载因子是0.75f,每次扩容一倍。是一个线程不安全的集合

Iterator

迭代器---用于迭代遍历集合。通过指针的挪动来获取对应的元素,通过标记这个元素是佛可用来确定是否删除这个元素---不允许直接增删原集合

foreach---本质上也是在做迭代遍历。---如果一个对象能够使用增强for循环,那么这个对象对应的类必须实现Iterable---JDK1.5的特性之一

Collections

是一个操作集合的工具类

Comparator

比较器---重写compare方法,将比较规则写到compare方法中---根据返回值的正负来确定大小:如果返回值是正数,表示第一个参数排到第二个参数之后;反之表示第一个参数排到第二个参数之前

如果没有指定排序规则,这个时候要求集合中的元素对应的类必须实现Comparable,比较规则是写在compareTo方法中

练习:

疯狂值(一组数字,排列顺序,两个为一组相减,值最大,输出这组数字)

5 15 20 35 50 70

20 50 5 70 15 35  ->  215

35 15 70 5 50 20 -> 215

方法一 集合:

public class CrazyExer1 {
	public static void main(String[] args) {
	        Scanner s = new Scanner(System.in);
		// 获取输入的数字个数
		int number = s.nextInt();
		List<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < number; i++) {
			list.add(s.nextInt());
		}
		s.close();
		// 需要对集合进行排序
		Collections.sort(list);		
		int sum1 = sort(list);		
		Collections.reverse(list);
		int sum2 = sort(list);		
		System.out.println(sum1 > sum2 ? sum1 : sum2 );
		// 5 10 15 20 50 70 80
	}
	public static int sort(List<Integer> list) {
		// 存储排序之后的结果
		List<Integer> result = new ArrayList<Integer>();
		// 先放入集合的最后一位
		result.add(list.get(list.size() - 1));
		// 标记存放的情况
		// 0->前小
		// 1->后小
		// 2->前大
		// 3->后大
		int i = 0;
		for (int start = 0, end = list.size() - 2; start <= end;) {
			if (i == 0) {
				result.add(0, list.get(start));
				start++;
			} else if (i == 1) {
				result.add(list.get(start));
				start++;
			} else if (i == 2) {
				result.add(0, list.get(end));
				end--;
			} else {
				result.add(list.get(end));
				end--;
				i = -1;
			}
			i++;
		}	
		// 记录和
		int sum = 0;
		for (int j = 0; j < result.size() - 1; j++) {
			sum += Math.abs(result.get(j) - result.get(j + 1));
		}
		return sum;
	}
}

方法二 数组:

public class CrazyExer2 {
	public static void main(String[] args) {
		Scanner s = new Scanner(System.in);
		int number = s.nextInt();
		int[] arr = new int[number];
		for (int i = 0; i < number; i++) {
			arr[i] = s.nextInt();
		}
		s.close();
		// 数组排序
		Arrays.sort(arr);
		int sum1 = sort(arr);
		for (int start = 0, end = arr.length - 1; start < end; start++, end--) {
			int temp = arr[start];
			arr[start] = arr[end];
			arr[end] = temp;
		}
		int sum2 = sort(arr);
		System.out.println(sum1 > sum2 ? sum1 : sum2);
	}
        private static int sort(int[] arr) {
		int[] result = new int[arr.length];
		int mid = arr.length / 2;
		result[mid] = arr[arr.length - 1];
		int i = 0;
		int offset = 1;
		for (int start = 0, end = arr.length - 2; start <= end;) {
			if (i == 0) {
				result[mid - offset] = arr[start];
				start++;
			} else if (i == 1) {
				result[mid + offset] = arr[start];
				start++;
				offset++;
			} else if (i == 2) {
				result[mid - offset] = arr[end];
				end--;
			} else {
				result[mid + offset] = arr[end];
				end--;
				offset++;
				i = -1;
			}
			i++;
		}
		int sum = 0;
		for (int j = 0; j < result.length - 1; j++) {
			sum += Math.abs(result[j] - result[j + 1]);
		}
		return sum;
	}
}

泛型

参数化类型---ParameterizedType---JDK1.5出现的

List list = new ArrayList();---底层以Object类型来存储

泛型的擦除---用具体类型来替换泛型的过程---发生在了编译期

泛型的继承

// ? 表示泛型的通配符
// 遍历元素类型是数值类型的集合---泛型不向下兼容
	// ? extends /接口 表示传入这个类/接口本身或者其子类/子接口元素
	// 泛型的上限
	public static void it(List<? extends Number> list) {
		// 能向这个集合添加元素么?--不行,除了null
		// list.add(new Integer(4));
		list.add(null); 
		for (Number number : list) {
			System.out.println(number);
		}
	}
// String及其父类
	// ? super /接口	表示传入这个类/接口及其父类/父接口元素
	// 泛型的下限---同一个泛型不能既规定上限又规定下限
	public static void it2(List<? super String> list) {
	}

使用泛型好处:
1.
将运行时期出现问题类型转换异常ClassCastException移到了编译时期。方便解决问题并且减少了运行时间

2.避免了强制转换麻烦。

映射---Map<K,V>

MapJava中映射的顶级接口。K-Key---键,V-Value------存储的时候是一个键对应了一个值---键值对---要求键必须唯一,值随意。---一个map中存储了很多的键值对。

Map不是集合,但是它是集合框架中的一员。

Map.Entry---代表键值对的接口---内部接口

Entry---每一个entry对象代表了一个键值对。

一个map对象实际上是由多个entry对象组成的。

问题:如何遍历一个Map

1. 先获取所有的键,依次根据键获取值---keySet

2. 获取由键值对组成的集合/数组---entrySet


HashMap---允许键或者值为null,默认初始容量是16,加载因子是0.75f,每次扩容一倍。是一个异步式线程不安全的映射。

Hashtable---不允许键或者值为null,默认初始容量是11,加载因子是0.75f。是一个同步式线程安全的映射。

ConcurrentHashMap---异步式线程安全的映射。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值