集合
用于存储一组数据的大小不定的容器。
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>
Map是Java中映射的顶级接口。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---异步式线程安全的映射。