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---异步式线程安全的映射。