java的基础学习(四:数组和集合)

文章目录

一、数组的定义及相关概念

1.1 定义

1.定义:数组是多个相同类型数据按一定顺序排列的组合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。
2.相关概念:
<1>元素
<2>下标(角标、索引)
<3>数组名
<4>数组的长度
3.数组:属于引用数据类型。数组的元素可以是基本数据类型也可以是引用数据类型。
4.数组的分类:
<1>按照维度:一维数组、二维数组、三维数组。
<2>按照元素的类型:基本数据类型变量的数组、引用数据类型变量的数组

1.2 数组的特点

1.数组一旦初始化完成,数组的长度确定。长度一旦确定,就不可更改。
2.数组中的元素是在内存空间中连续存储的。

1.3 一维数组的使用和默认初始化

不同类型的数组的默认初始化的情况:
整型:0
浮点型:0
字符型:\u0000 或0,不等同于’0’
布尔型:false
引用类型:null,不等同于"null"
此时的规范与后面类的属性的默认初始化值一致!

public class ArrayTest {
 public static void main(String[] args) {
  //1.数组的声明和使用
  int[] arr1;//声明
  //静态初始化:数组的初始化和数组元素的赋值同时进行
  arr1 = new int[]{1,2,3,4};
  //合并声明和初始化
  int[] arr2 = new int[] {1,2,3,4,5,6};
   //或:int arr2[] = new int[]{1,2,3,4,5,6};
   //或:int[] arr2 ={1,2,3,4,5,6};
  //动态初始化:数组的初始化和数组元素的赋值分开进行
  String[] arr3 = new String[3];
  //2.如何调用数组的元素:通过角标调用,从0开始,到长度n-1结束
  arr3[0] = "刘二";
  arr3[1] = "张三";
  arr3[2] = "李四";
  //数据的重新赋值
  arr3 = new String[5];
  //错误的声明方式
  //int[] arr1 = new int[4]{1,2,3,4};
  //3.数组的长度:使用length属性调用
  System.out.println(arr3.length);//5
  System.out.println(arr2.length);//6
  int[] arr4 = new int[3];
  //4.遍历数组元素
  for(int i = 0;i<arr4.length;i++) {
   System.out.println(arr4[i]);
  }
 }
}

1.4 二维数组的使用

1.如果数组的元素又是数组类型,则构成了多维数组。
2.二维数组的默认初始化
二维数组的外层元素(例如:arr[1] [ ] )的初始化值为:地址值或null
二维数组的内层元素(例如:arr[1] [1] )的初始化值为:
如果外层元素初始化值为地址值时,此时内层元素默认初始化值于一维相同;
如果外层元素初始化值为null时,则内层元素还不存在,调用时报错。
补充: 查看二维数组变量的值,只要初始化过二维数组,存储都是地址值。
数组的常用算法:复制、反转、查找

public class ArrayTest1 {
 public static void main (String[] args) {
  //1.二维数组的声明和初始化
  int[] arr1 = new int[] {1,2,3};//一维初始化
  //静态初始化
  int[][] arr2 = new int[][] {{1,2,3},{4,5},{7,8}};
  //动态初始化方法1
  int[][] arr3 = new int[3][2];
  //动态初始化方法2
  int[][] arr4 = new int[3][];//错误方法:int[][] arr4 = new int[][2];
  arr4[0] = new int[3];
  arr4[1] = new int[4];
  arr4[2] = new int[2];
  //2.使用数组元素:使用角标方式
  System.out.println(arr2[1][0]);
  arr4[1][3] = 1; //赋值
  //3.数组的长度
  System.out.println(arr2.length);//3
  System.out.println(arr2[1].length);//2
  //4.二维数组的遍历
  System.out.println("********");
  
  for(int i = 0;i<arr2.length;i++) {
   for(int j = 0;j < arr2[i].length;j++) {
    System.out.print(arr2[i][j]);
   }System.out.println();
  }

二维数组的默认初始化值

//5.二维数组的默认初始化值
   //外层元素:
  System.out.println(arr3[0]); //[I@15db9742 地址值
  System.out.println(arr3[1]); //[I@6d06d69c
  System.out.println(arr3[2]);//[I@7852e922
   //内层元素:
  System.out.println(arr3[0][0]); //0
  System.out.println("********");
  String[][] arr5 = new String[3][2];
  
  System.out.println(arr5[0]); //[Ljava.lang.String;@4e25154f
  System.out.println(arr5[1]); //[Ljava.lang.String;@70dea4e
  System.out.println(arr5[2]);//[Ljava.lang.String;@5c647e05
  
  System.out.println(arr3);//[[I@33909752
  System.out.println(arr5);//[[Ljava.lang.String;@55f96302
  
  System.out.println("********");
  int[][] arr11 = new int[3][];
  double[][] arr12 = new double[3][];
  String[][] arr13 = new String[3][];
  System.out.println(arr11);//[[I@3d4eac69
  System.out.println(arr11[0]); //NUll
  System.out.println(arr11[1]); //NUll
  System.out.println(arr11[2]);//NUll
//  System.out.println(arr11[2][0]);//报错:Exception in thread "main" java.lang.NullPointerException
  System.out.println(arr12);//[[D@42a57993
  System.out.println(arr12[0]); //NUll
  System.out.println(arr12[1]); //NUll
  System.out.println(arr12[2]);//NUll
  System.out.println(arr13);//[[Ljava.lang.String;@75b84c92
  System.out.println(arr13[0]); //NUll
  System.out.println(arr13[1]); //NUll
  System.out.println(arr13[2]);//NUll

二位数组的内层解析和常用算法:复制、反转、查找

//6.二位数组的内层解析
  //复制
  String[] arrs1 = new String[] {"AA","BB","CC","DD","JJ","GG"};
  String[] arrs2 = new String[arrs1.length];
  for(int i = 0;i < arrs1.length;i++) {
   arrs2[i] = arrs1[i];
  }
  //反转
//  for(int start = 0,end = arrs1.length -1;
//    start < end;start++,end--) {
//   String temp = arrs1[start];
//   arrs1[start] = arrs1[end];
//   arrs1[end] = temp;
//  }
  //遍历
  for(int i = 0;i < arrs1.length;i++) {
   System.out.println(arrs1[i]);
  }
  //查找(搜索)
  //方式一:线性查找
  String str = "GG";
  boolean isFlag = true;
  for(int i = 0;i < arrs1.length;i++) {
   if(arrs1[i].equals(str)) { //比较两个字符串的内容是否相等
    System.out.println("找到了!索引位置为: " + i);
    isFlag = false;
    break;
   }
  }
  if(isFlag) {
   System.out.println("不好意思,没有找到!");
  }
  //方式二:二分法查找,前提:数组要有序
  int[] arr22 = new int[] {-99,-54,-2,0,2,33,43,256,999};
  boolean isFlag1 = true;
  int number = 255;
  int head = 0,end = arr22.length -1;
  while(head <= end) {
   int middle = (head + end) / 2;
   if(arr22[middle] == number) {
    System.out.println("找到了指定元素,索引为: " + middle);
    isFlag = false;
    break;
   }else if(arr22[middle] > number) {
    end = middle -1;
   }else  {head = middle + 1;}
  }
  if(isFlag1) System.out.println("未找到指定元素!");
  //数组元素的排序算法,详见Java实现冒泡排序、快速排序算法文章
 }
}

二、集合的概念

2.1 Collection和Map

JDK在数组和链式结构基础上,重新设计了很多容器类型。
两大类:一、Collection :一组对象,比喻“单身party”;二、Map:键值对,(key,value),比喻“情侣party”,“家庭party”
容器的共同的行为特征,操作方式:增、删、改、查…
把容器的操作的行为标准化,用接口来声明。
在这里插入图片描述

三、Colletion

Collection是层次结构中的根接口(在java.util.Colletion),Collection表示一组对象。
JDK不提供此接口的任何直接实现:它提供更具体的子接口(如Set和List)实现。

3.1 Collection的常用方法

1.添加 add、addAll

add(Object obj) 一次添加一个
addAll(Collection other) 一次添加多个,把other中的元素都添加到当前集合中。即this = this ∪(并) other

2.删除 remove、removeAll、clear

remove(Object o) 一次删除一个
removeAll(Collection other) 一个删除多个,this = this - this ∩ other
clear() 清空,删除所有的对象

3.修改 Collection根接口没有提供修改的方法!

4.查询 contain、containAll、isEmpty()

contain(Object o) 判断o是否在当前集合中
containAll(Collection c) 判断c中的元素是否都在当前集合中,即判断c是否是this的子集
boolean isEmpty()
注意: Collection根接口中没有提供获取一个元素的方法。

5.获取有效元素个数 int size()

6.遍历 Object[] toArray()、foreach、Iterator

(1)老方法 Object[] toArray() :如果底层实现就是数组比较简单,如果不是,效率比较低。无论底层如何,都会返回一个size长度的数组,浪费空间。
(2)foreach
(3)Iterator 迭代器遍历
特殊: retainAll(Collection c) 保留当前集合和c集合的交集,即this = this ∩ c

 @Test
 public void test1() {  
 //多态引用,关注Collection的方法,ArrayList是Collection的子接口List的实现类。
  Collection c = new ArrayList();
  c.add("张三");
  c.add("李四");
  c.add("王五");
  c.add("钱七");
  Collection other = new ArrayList();
  other.add("王五");
  other.add("赵六");
  c.remove("张三");
  c.removeAll(other);
  Object[] array = c.toArray();
  for (int i = 0; i < array.length; i++) {
   System.out.println(array[i]);
  }
 }

3.2 foreach 遍历

foreach:增强版for循环
语法结构:
for(元素类型 元素名 : 可迭代的容器名){ }
可迭代的容器名:例如数组和集合
把元素名看成形参,每循环一次,把数组或集合的元素依次作为实参赋值给他。

  • 即:如果元素是基本数据类型,那把数组或集合的元素“数据值”赋给它,它怎么修改和实参无关。
  • 元素是引用数据类型,那把数组或集合的元素“地址值”赋给它,那么它的属性修改和实参有关,
    对它的地址值修改和实参无关。
  • 结论: 如果你只是查看数组和集合的元素,foreach比较简单; 如果涉及删除、修改就不要用了。
@Test
 public void test1() {
  int[] arr = { 1, 2, 3, 4, 5 };
  // 元素类型 int,元素名自己取
  for (int num : arr) {
   System.out.println(num);
  }
  String[] names = { "张三", "李四", "王五" };
  for (String str : names) {
   System.out.println(str);
  }
 }
  @Test
  @SuppressWarnings("all")
  public void test2() {
   Collection c = new ArrayList();
   c.add("张三");
   c.add("李四");
   c.add("王五");
   c.add("钱七");
   for (Object obj : c) {
    System.out.println(obj);
   }
  }

3.3 Iterator 迭代器类型

java.util.Iterator 迭代器类型:用于迭代(遍历)Collection系列的集合。

3.3.1步骤:(1)通过集合对象拿迭代器对象;(2)Iterator方法迭代

1.先通过Collection系列集合的对象拿到迭代器对象
2.再通过Iterator的方法进行迭代

3.3.2 三个方法:hasNext、next、remove

  • boolean hasNext() 判断集合是否有下一个元素需要迭代
  • Object next() 取出下一个元素
  • void remove() 删除刚跟迭代的元素
    Iterator 与foreach方法最大的区别是Iterator 可以进行元素的删除、修改操作!
@Test
 public void test1() {
  Collection c = new ArrayList();
  c.add("张三");
  c.add("李四");
  c.add("王五");
  c.add("钱七");
  // 1.先通过Collection系列集合的对象拿到迭代器对象
  Iterator iterator = c.iterator();
  // 2.再通过Iterator的方法进行迭代
  while (iterator.hasNext()) {// 判断集合中是否有下一次需要迭代
   Object next = iterator.next();
   System.out.println(next);
  }
 }
 @Test
 public void test() {
  Collection c1 = new ArrayList();
  c1.add(new Student(1, "张三", 90));
  c1.add(new Student(2, "李四", 43));
  c1.add(new Student(3, "王五", 67));
  c1.add(new Student(4, "赵六", 87));
  c1.add(new Student(5, "钱七", 65));
  Iterator iterator = c1.iterator();
  while (iterator.hasNext()) {
   Object next = iterator.next();
   Student stu = (Student) next;
   if (stu.getScore() < 60) {
    iterator.remove();// 调用迭代器的remove,而不是集合的remove!
   }
  }
  for (Object object : c1) {
   System.out.println(object);
  }
 }
}

注意: 用迭代器或foreach遍历时,再用集合对象的remove方法,会产生ConcurrentModificationException(并发异常)因为迭代器和集合两条线同时操作元素。

3.4 List

3.4.1 List 概述

List: (java.util.List 接口)
即列表:可重复的,有序的(按顺序存储)
(1)有序:可以对元素的索引index,进行控制
(2)可重复
实现类:例如ArrayList、Vector(动态数组)、LinkedList:双向链表、双端队列、Stack:栈、队列 Queue、双端队列Deque

3.4.2 List 常用方法

首先,List 继承了Collection,有它的所有的方法。List还增加了跟index相关的方法。

1.添加 add、addAll

add(int index ,E element):在index位置加入一个元素
addAll(int index,Collection c):在index位置加入多个元素

2.删除 remove(int index)
3.修改 set(int index ,E element)

Collection没有提供修改方法。

4.查询 indexOf()、lastIndexOf()、get()、subList()

int indexOf(Object o):从前往后
int lastIndexOf(Object o): 从后往前
get(int index):返回index位置元素
subList(int fromIndex,int toIndex): 截取[fromIndex,toIndex)

5.遍历 toArray\foreach\Iterator和ListIterator

新方法:ListIterator
是Iterator的子接口,Iterator有的ListIterator也有,还增加了:

  • A.Iterator只能从前往后遍历,ListIterator可以从任意位置开始,从前还是从后都可。
    ListIterator步骤:
  • 第一步:先获取ListIterator的对象: 集合对象.ListIterator()
  • 第二步: 通过遍历方法 hasNext() + next() 或 hasPrevious() + previous()
  • B.不仅可以在遍历时删除,还增加了set和add方法
@Test
 public void test1() {
  List l = new ArrayList();
  l.add("张三");
  l.add("王五");
  l.add("赵六");
  l.add("钱七");
  l.add("刘八");
  l.add(0,"李四");
  ListIterator listIterator = l.listIterator();
  //从前往后
  while(listIterator.hasNext()) {
   Object next = listIterator.next();
   System.out.println(next);
  }
  //从后往前
  listIterator = l.listIterator(l.size());//要让迭代器指向第一个元素
  while(listIterator.hasPrevious()) {
   Object previous = listIterator.previous();
   System.out.println(previous);
  }
  //从任意位置开始遍历,例如从3往前遍历
  listIterator = l.listIterator(3);//要让迭代器指向第一个元素
  while(listIterator.hasPrevious()) {
   Object previous = listIterator.previous();
   System.out.println(previous);
  }
 }

3.4.3 List的常见实现类

1.Vector:动态数组

内部实现:数组,初始化大小为10

2.ArrayList:动态数组

内部实现:数组,初始化大小为10

3.LinkedList:双向链表、双端队列

内部实现:链表

4.Stack:栈、队列 Queue、双端队列Deque

Stack:后进先出(LIFO) 、先进后出(FILO)
压栈:push 弹栈:pop(移除栈顶元素) peek(返回栈顶元素,但不移除)
队列 Queue:先进先出(FIFO)
offer(e)添加方法, poll()移除队列,peek()
双端队列Deque:队头和队尾都可以添加和删除元素

3.4.4 Vector与ArrayList区别

Vector:旧版,线程安全的。扩容机制为原来的2倍。支持新、旧版迭代器
ArrayList:新版,线程不安全,扩容为1.5倍。不支持老版Enumeration迭代器

3.4.5 动态数组和LinkedList的区别

LinkedList内部实现链表,对索引相关操作效率低,插入、删除,只涉及前后的元素关系,元素类型是结点类型,Node(prev,data,next);
动态数组的内部实现是数组,对索引的相关操作效率很高,插入、删除和涉及到移动元素
结论:如果后面的操作对索引更多,选择动态数组,如果添加、删除操作多,选链表

3.5 Set

3.5.1 Set 概述

Set: 即集:不可重复的,无序的(和添加顺序无关)
java.util.Set :接口,也是Collection的子接口
(1)不支持重复
(2)无序的(和添加顺序无关)

3.5.2 Set的实现类

结论: 如果既要元素不重复,又要按大小:TreeSet
如果既要元素不重复,又要保证添加顺序:LinkedHashSet
如果只是要元素不重复:HashSet

(1)HashSet:完全无序

如何保证不重复:依据元素的equals()方法

(2)TreeSet:大小顺序,和添加顺序无关

如何保证不重复:依据元素的“大小”顺序
除了implements Comparable接口的方式外还可以,TreeSet set = new TreeSet();按照元素的自然排序。
TreeSet set = new TreeSet(定制比较器对象);按照定制比较器比较,即TreeSet set = new TreeSet(new Comparator(){});
别忘记导包,import java.util.Comparator;

(3)LinkedHashSet:遍历时可保证添加顺序,存储和添加顺序无关

LinkedHashSet是HashSet的子类,但是它的元素比HashSet的元素要多维护一个添加的顺序。
LinkedHashSet效率比HashSet低,每次添加、删除要同时考虑顺序。
LinkedHashSet和HashSet一样依据equals()方法决定是否重复。

@Test
 public void test1() {
  Set set = new HashSet();
  set.add("张三");
  set.add("李四");
  set.add("王五");
  for (Object object : set) {
   System.out.println(object);//李四   张三  王五
  }
 }
@Test
 public void test2() {
  Set set = new TreeSet();
  set.add("zhangsan");//String类型实现了Comparable
  set.add("lisi");
  set.add("wangwu");
  for (Object object : set) {
   System.out.println(object);
  }
 }
@Test
 public void test3() {
  Set set = new TreeSet();
  set.add(new Student1(2, "张三", 90));//如果成功,Student要实现java.lang.Comparable
  set.add(new Student1(1, "李四", 66));
  set.add(new Student1(3, "王五", 87));
  for (Object object : set) {
   System.out.println(object);
  }
 }
}
class Student1 implements Comparable{
private int id;
 private String name;
 private int score;
 @Override
 public String toString() {
  return "Student [id=" + id + ", name=" + name + ", score=" + score + "]";
 }
 @Override
 public int compareTo(Object o) {
  Student1 stu =(Student1) o;
  return this.id - stu.id;//按学号大小排
 } 
}

四、Map

java.util.Map:
和Collection的最大的不同,是它存储**“键值对,映射关系”**

4.1 Map 常用方法

1.添加 put、putAll

put(key,value) 一次添加一对映射关系
putAll(Map map) 一次添加多对映射关系,this = this ∪ map

2.删除 remove、clear()

remove(Object key): 根据key删除一对
clear() 清空

3.修改 put

通过put可以替换value,只要key相同,就可以替换

4.查询 containsKey、containsValue、get、isEmpty()

(1)containsKey(Object key) 判断某个key是否存在
(2)containsValue(Obejct value) 判断某个value是否存在
(3)get(Object key) 根据key获取value
(4)boolean isEmpty() 是否为空

5.获取映射关系,键值对数:int size()

6.遍历 (1)Set keySet();(2)Collection values();(3) Set entrySet()

(1)Set keySet();
(2)Collection values();
(3) Set entrySet() :由entry对象构成的set,因为key不重复,那么entry对象也不会重复

 @Test
 public void test1() {
  Map map = new HashMap();
  map.put("张三","李四");
  map.put("赵四",null);
  map.put("王五",new String[]{"钱七","王八"});
  System.out.println(map.size());//3
  Set keySet = map.keySet();//因为map的key不能重复,拿出所有的key就是一个set
  for (Object key : keySet) {
   System.out.println(key);//张三 赵四 王五
  }
  Collection values = map.values();
  for (Object value : values) {
   System.out.println(value);//李四  null  [Ljava.lang.String;@28c97a5
  }
  Set entrySet = map.entrySet();
  for (Object entry : entrySet) {
   System.out.println(entry);//张三=李四  赵四=null  王五=[Ljava.lang.String;@28c97a5
  }
 }

4.2 Map的实现类

1. HashTable:哈希表,最古老的

最古老的,线程安全,它的key和value不能为null

2. HashMap:哈希表

新的,线程不安全,它的key和value允许为null

3. LinkedHashMap

它是HashMap的子类,它比HashMap多维护了添加的顺序。

4. TreeMap:按照key的“大小”顺序

映射关系的顺序会按照key的“大小”顺序
要求:映射关系的key必须支持排序,即实现java.lang.Comparable接口,或单独为TreeMap指定定制比较器。

5. Properties:key和value的类型一定是String

Properties是HashTable的子类,它的key和value的类型一定是String

@Test
 public void test1() {
  //获取系统属性
  Properties properties = System.getProperties();
  Set entrySet = properties.entrySet();
  for (Object object : entrySet) {
   System.out.println(object);
  }

4.3 HashMap 源码分析

1、JDK1.7以及以前,HashMap的底层实现是数组+链表

1.数组元素的类型:

  • Map.Entry接口的类型(key,value)或HashMap.Entry内部类类型,实现了Map.Entry,(key,value,next)
    2.为什么要有链表:两个对象的hash值可以相同也可能不同,在相同时数组table[index]无法存放在一起
    3.数组的初始化长度:默认16,手动写必须是2的N次方
  • 数组会扩容,如果不扩容,链表会变得很长,它的查询、添加效率很低
  • 什么情况扩容?有一个变量threshold达到临界值时,会考虑扩容,还要看当前添加(key,value)时,
  • 是否table[index] == null,如果不为空就会扩容。
  • DEFAULT_LOAD_FACTOR :默认加载因子:0.75;
  • threshold = table.length * 0.75=16*0.75 = 12
    4.index如何计算
    拿到一个key的hash值之后如何计算[index]
  • (1)key是null,固定位置[index] = [0]
  • (2)第一步,先用hashCode的值通过hash(key)函数计算一个“hash值”;
  • 第二步,再根据“hash值”与table.length -1 运算。
    5.如何做到key不重复
  • 如果key重复,会替换掉旧的value
  • key相同,先判断hash值。如果hash值一样,判断key的地址或equals是否相等,满足则替换旧的value。
    6.新的(key,value)添加到table[index],是作为table[index]的头,原来的元素作为next
    在这里插入图片描述

2、JDK1.8,HashMap的底层实现是数组+链表(或红黑树)

1.为什么要要修改为链表或红黑树的设计? 当某个链表过长时,查询效率低。

  • 如果table[index]的链表的节点个数大于8,就要考虑红黑树。TREEIFY_THRESHOLD:转化阈值
    2.什么时候树化?
  • 如果table[index]的结点数量到8个,还要判断table.length是否达到64,如果没有,先扩容。
  • 演示:8 --> 9个, length从16–>32
  • 9 --> 10个, length从32–>64
  • 10 --> 11个, length已到64,table[index]会从Node类型转换为TreeNode类型
  • MIN_TREEIFY_CAPACITY = 64,最小树化容量
    3.什么时候从树–>链表
  • 当你删除结点时,这棵树的结点个数小于6个,会反树化
  • UNTREEIFY_THRESHOLD = 6
    4.新的(key,value)添加到table[index],JDK是在链表的尾部
    成语:七上八下
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值