集合
1 集合
1.1 概述
Collection 是所有集合的顶级接口。是存放元素的容器。有很多子接口和实现类。不同的接口有不同的特点。不同的实现类有不同的实现方式。
Collection 这个代表泛型,相当于定义号集合中能放哪些数据类型。泛型只能用引用数据类型。
1.2 集合中的常用方法
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("java");
collection.add("ios");
collection.add("windows");
collection.add("go");
System.out.println(collection);
Collection<String> collection1 = new ArrayList<>();
collection1.add("java");
collection1.add("ios");
collection1.add("windows");
collection1.add("go");
// 比较集合中的元素是否相同
System.out.println(collection.equals(collection1));
// 清空集合中存储的所有元素 但是集合的对象还在
collection.clear();
// 判断集合是否为空
System.out.println(collection.isEmpty());
// 移除集合中的元素,移除成功返回true
System.out.println(collection1);
collection1.remove("ios");
System.out.println(collection1);
// 获取集合中的元素个数
System.out.println(collection1.size());
// 把集合对象转换成数组
// Object[] objects = collection1.toArray();
// for (Object object : objects) {
// System.out.println(object);
// }
String[] objects = collection1.toArray(new String[0]);
for (String str : objects) {
System.out.println(str);
}
// System.out.println(collection);
// 判断集合中是否包含某一个元素 返回true代表包含 返回false代表不包含
// System.out.println(collection.contains("go"));
}
private static void addDemo() {
// JDK7之后,如果左边泛型确定了,右边的泛型可以省略
// 创建集合对象
Collection<String> collection = new ArrayList<>();
// 添加元素到集合中
collection.add("java");
collection.add("python");
collection.add("C");
collection.add("C++");
collection.add("C#");
collection.add("golang");
System.out.println(collection);
}
1.3 List接口
List接口是一个有序的集合,方法中具有索引,允许存储重复的元素.
1.3.1 List接口中带索引的方法
public static void main(String[] args) {
// 创建对象
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
System.out.println(list);
// 在index索引处插入一个元素
list.add(1,5);
System.out.println(list);
// IndexOutOfBoundsException 索引越界异常
// list.add(8,6);
List<String> strings = new ArrayList<>();
strings.add("UI");
strings.add("test");
strings.add("develop");
strings.add("UI");
System.out.println(strings);
// 移除指定的元素
// 如果移除的是重复元素,那么会移除第一个
// strings.remove("UI");
// System.out.println(strings);
// 移除指定索引处的元素
// strings.remove(1);
// System.out.println(strings);
// 移除指定的所有元素
// while (strings.contains("UI")){
// strings.remove("UI");
// }
// System.out.println(strings);
// 找指定元素的第一个索引,找不到就是-1
// System.out.println(strings.indexOf("UI"));
// // 移除指定的所有元素
// while (strings.indexOf("UI") != -1){
// strings.remove("UI");
// }
// 替换指定索引处的元素,并返回替换之前的值
String str = strings.set(2, "boss");
System.out.println(str);
System.out.println(strings);
// 获取指定索引处的元素
String str1 = strings.get(2);
System.out.println(str1);
// 截取子集合 包含第一个索引,不包含第二个索引 (包头不包尾)
List<String> strings1 = strings.subList(0, 2);
System.out.println(strings1);
}
1.3.2 List接口的实现类–ArrayList
ArrayList是List接口的实现类,具有自己的特点
- 内部数据结构是数组
- 查询速度快
- 增删速度慢
- 线程不安全的集合
ArrayList底层数组的初始化容量是10
ElementData是ArrayList的底层数组
底层数组扩容机制是 (原来的容量 + 原来容量/2 )
方法
// 指定ArrayList的初始化容量 指定多少就是多少
ArrayList<String> list1 = new ArrayList<>(8);
// 将ArrayList的容量缩小为当前元素的实际的个数
list1.trimToSize();
1.3.2.1 练习 – 实现ArrayList中的常用方法
自定义ArrayList类的基本结构
public class ArrayListDemo {
// 存储数据的数组
private String[] data;
// 定义无参构造方法
public ArrayListDemo(){
// ArrayList底层数组的默认容量是10
data = new String[10];
}
// 定义有参构造
public ArrayListDemo(int len){
// 判断初始化容量是否合法
if (len < 0)
throw new IllegalArgumentException("arraylist容量不能是负数");
data = new String[len];
}
//记录元素的个数
private int size = 0;
- 添加元素方法
// 添加元素方法
public void add(String str){
// 判断是否需要扩容
if (size >= data.length){
this.grow();
}
data[size++] = str;
}
// 扩容方法
private void grow(){
if (data.length <= 1){
data = Arrays.copyOf(data,data.length + 1);
}else {
// 注意点:这里data.length >> 1一定要加小括号 因为 +的优先级比 >> 高
data = Arrays.copyOf(data, data.length + (data.length >> 1));
}
}
- 重写toString方法
// [3, 4, 5, 6]
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
for (int i = 0;i < size;i++){
if (i != size - 1){
stringBuilder.append(data[i]).append(", ");
}else {
stringBuilder.append(data[i]);
}
}
stringBuilder.append("]");
return stringBuilder.toString();
}
- 实现插入元素方法 add(int index,String str)
/**
* 插入元素方法
* @param index 插入元素的索引
* @param str 插入的元素
*/
public void add(int index,String str){
// 判断索引是否越界
if (index < 0 || index > size){
throw new IndexOutOfBoundsException("数组越界了,skr~");
}
// 判断是否需要扩容
if (size >= data.length){
this.grow();
}
// 方式一:统一把index后面的数据往后挪一位
for (int i = size - 1;i >= index;i--){
data[i + 1] = data[i];
}
// 方式二:
// System.arraycopy(data,index,data,index + 1,size - index);
//设置插入索引位置的字符串
data[index] = str;
// 实际元素加1
size++;
}
- 删除指定索引处的元素
public void remove(int index){
// 判断索引是否越界
if (index >= size || index < 0) throw new IndexOutOfBoundsException("数组越界了");
// 方式一:从索引处后面的数据统一向前挪一位
// System.arraycopy(data,index + 1,data,index,size - index - 1);
for (int i = index; i < size - 1 ; i++) {
data[i] = data[i + 1];
}
// 实际长度减一
size--;
}
- 删除指定元素
/**
* 查找字符串首次出现的位置
* @param str 字符串
* @return 所在的索引
*/
public int indexOf(String str){
for (int i = 0; i < size; i++) {
if (str == data[i] || str != null && str.equals(data[i])){
// 字符串和元素相等
return i;
}
}
// 如果没有找到,返回-1
return -1;
}
/**
* 删除指定元素
* @param str 指定的字符串
*/
public void remove(String str){
// 找到元素第一次出现的位置
int index = this.indexOf(str);
if (index != -1){
// 删除
this.remove(index);
}
}
- 其他方法
/**
* 判断是否越界
* @param index
*/
private void out(int index) {
// 判断索引是否越界
if (index >= size || index < 0) throw new IndexOutOfBoundsException("数组越界了");
}
/**
* 判断是否包含指定元素
* @return true 包含
*/
public boolean contains(String str){
return indexOf(str) != -1;
}
/**
* 根据索引获取元素
* @param index 索引
* @return 元素
*/
public String get(int index){
// 判断是否越界
this.out(index);
return data[index];
}
/**
* 判断集合是否为空
* @return
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 替换元素
*/
public void set(int index,String str){
// 判断是否越界
this.out(index);
data[index] = str;
}
/**
* 获取元素个数
*/
public int size(){
return size;
}
/**
* 清空集合
*/
public void clear(){
data = new String[10];
size = 0;
}
- 截取子列表
/**
* 截取子列表
* @return 子列表
*/
public ArrayListDemo subList(int fromIndex,int toIndex){
// 判断参数是否合法
this.out(fromIndex);
this.out(toIndex);
if (fromIndex > toIndex){
throw new IllegalArgumentException();
}
// 创建一个新的要截取的列表
ArrayListDemo sublist = new ArrayListDemo(toIndex - fromIndex);
// 拷贝要截取的内容到新数组中
System.arraycopy(this.data,fromIndex,sublist.data,0,toIndex - fromIndex);
// 设置新的列表的实际元素个数
sublist.size = toIndex - fromIndex;
return sublist;
}
ArrayList的特点:查询相对简单,增删相对复杂。线程不安全的集合。
1.3.3 List接口的实现类–LinkedList
LinkedList称之为链表,底层是一个双向链表结构。
链表的内存是不连续的
LinkedList的特点:
- 查询效率相对低
- 增删效率相对高
- 是一个线程不安全的集合
1.3.3.1 练习–实现LinkedList的常用方法
- 基本结构
public class LinkedListDemo {
// 表示元素结点的个数
private int size = 0;
// 头结点
private Node first;
// 尾结点
private Node last;
// 链表不需要扩容,也没有初始容量
// public LinkedListDemo(){}
// 定义结点的数据结构 是一个私有的成员内部类
private class Node{
// 数据
String data;
// 上一个结点的地址
Node prev;
// 下一个结点的地址
Node next;
// 提供有参构造方法保存成员变量
public Node(String data,Node prev,Node next){
this.data = data;
this.prev = prev;
this.next = next;
}
}
}
- 添加元素
/**
* 添加元素
* @param str
*/
public void add(String str){
// 创建结点(将新添加进来的字符串包装成Node对象)
Node node = new Node(str,null,null);
// 判断是否是第一次添加元素
if (size == 0){
this.first = node;
}else {
// 旧的尾结点的next设置成新添加来的结点
this.last.next = node;
// 新添加的结点的prev设置成旧的尾结点
node.prev = this.last;
}
// 把新添加的结点设置成尾结点
this.last = node;
// 元素个数+1
size++;
}
- 插入元素
/**
* 插入元素
*/
public void add(int index,String str){
// 判断索引是否越界
if (index > size || index < 0) throw new IndexOutOfBoundsException("索引越界");
// 插入元素分为三种情况
// 1.插入到尾部 2. 插入到头部 3.插入到中间
// 插入到尾部
if (index == size){
add(str);
return;
}
Node node = new Node(str,null,null);
// 插入到头部
if (index == 0){
// 将新结点的next设置成原来的头结点
node.next = this.first;
// 将头结点的prev设置成新的结点
this.first.prev = node;
// 将新结点设置成新的头结点
this.first = node;
}else {
// 插入到中间
// 先查询要插入索引的元素
// 获取头结点,一个个向下查询
Node no = first;
for (int i = 0; i < index; i++) {
// 一个个向下查找
no = no.next;
}
// no经过上面的循环之后,就是要插入索引处原来的那个结点
// no的前一个结点的next属性赋值为新结点
no.prev.next = node;
// node的prev设置为no的上一个结点
node.prev = no.prev;
// 新结点的next设置为旧结点
node.next = no;
// 旧结点的prev设置为新结点
no.prev = node;
}
size++;
}
- 根据索引查找结点
public Node getNode(int index){
//判断是否索引越界
if(index < 0 || index > size - 1)
throw new IndexOutOfBoundsException("索引越界");
//循环找到索引指定的元素
Node no;
if(index >= size / 2){
no = this.last;
for (int i = 0; i < size - index - 1; i++) {
no = no.prev;
}
}else {
no = this.first;
for (int i = 0; i < index; i++) {
no = no.next;
}
}
return no;
}
- 根据索引查找元素
//根据索引查找元素
public String get(int index){
return this.getNode(index).data;
}
- 根据元素返回索引
//根据元素返回索引
public int indexOf(String str){
//先看尾是不是
if(Objects.equals(str, this.last.data)){
return size - 1;
} else {
Node no = this.first;
for(int i = 0;i < size - 1;i++){
if(Objects.equals(str, no.data)){
return i;
}
no = no.next;
}
}
//都不是就返回 -1
return -1;
}
- 删除指定索引元素
//删除指定索引元素
public void remove(int index){
//判断是否索引越界
if(index < 0 || index > size - 1)
throw new IndexOutOfBoundsException("索引越界");
if(size == 1){
this.clear();
return;
}
if(index == size - 1){
this.last = this.last.prev;
this.last.next = null;
size--;
return;
}
if(index == 0){
this.first = this.first.next;
this.first.prev = null;
size--;
return;
}
Node node = this.getNode(index);
node.prev.next = node.next;
node.next.prev = node.prev;
size--;
}
- 删除指定元素匹配的第一项
//删除指定元素的第一个匹配项
public void remove(String str){
int i = indexOf(str);
if(i == -1){
System.out.println("不存在");
return;
}
remove(i);
}
- 重写 toString() 方法
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("[");
Node no = this.first;
for(int i = 0;i < size;i++){
if(i == 0){
stringBuilder.append(no.data);
}else {
stringBuilder.append(",").append(no.data);
}
no = no.next;
}
stringBuilder.append("]");
return stringBuilder.toString();
}