Java容器

一、容器

Java容器,java容器是javaAPI所提供的一系列类的实例,用于在程序中存放对象,主要位于Java.util包中,其长度不受限制,类型不受限制,你在存放String类的时候依然能够存放Integer类,两者不会冲突。

容器API类图结果如下所示:

img

1、Collection接口

Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。

2、ArrayList 实现List接口

ArrayList list = new ArrayList<>();//不需要指定长度
String[] string = new String[n];
基操: list.add(String)、list.get(index)

定位:list.get(index)、list.indexOf(String)、(包含)list.contains(String)
求长:list.size()
替换:list.add()、list.add(index,String):插入,数据必须连续 、list.set(index,String):替换index的值
list.remove(index或String):移除index的值或String :{只移除一个String的值,从左往右}
list.removeAll(Collection<?>)

(容器的)迭代器 Iterator

Iterator iterator = list.iterator();
iterator.hasNext():boolean是否有下一个值、iterator.next()

ArrayList<String> list = new ArrayList<>();
//ArrayList的遍历
//迭代器遍历
		while(iterator.hasNext()) {
			String string =  iterator.next();
			System.out.println(string);
		}
//for循环依次取值,增强for循环实际就是用了迭代器
//增强for循环遍历
for (String str : list)  {
			System.out.print(str+" ");
		}

包装类(Wrapper Class)

java并不是纯面向对象的语言,java语言是一个面向对象的语言,但是java中的基本数据类型却不是面向对象的,但是我们在实际使用中经常将基本数据类型转换成对象,便于操作,比如,集合的操作中,这时,我们就需要将基本类型数据转化成对象!

基本数据类型 包装类

byte Byte

boolean Boolean

short Short

char Character

int Integer

long Long

float Float

double Double

包装类与基本类型的转换

从源代码的角度来看,基础类型和包装类型都可以通过赋值语法赋值给对立的变量类型,如下面的代码所示。

Integer a = 1;     //自动装箱
int a = new Integer(1);   //自动拆箱



//int b = a实际上是int b = a.intValue()
//包装类	包装类转基本类型	基本类型转包装类
//Byte	Byte.valueOf(byte)	byteInstance.byteValue()
//Short	Short.valueOf(short)	shortInstance.shortValue()
//Integer	Integer.valueOf(int)	integerInstance.intValue()
//Long	Long.valueOf(long)	longInstance.longValue()
//Float	Float.valueOf(float)	floatInstance.floatValue()
//Double	Double.valueOf(double)	doubleInstance.doubleValue()
//Character	Character.valueOf(char)	charInstance.charValue()
//boolean	Boolean.valueOf(booleann)	booleanInstance.booleanValue()

这种语法是可以通过编译的。但是,Java作为一种强类型的语言,对象直接赋值给引用类型变量,而基础数据只能赋值给基本类型变量,这个是毫无异议的。那么基本类型和包装类型为什么可以直接相互赋值呢?这其实是Java中的一种“语法糖”。“语法糖”是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

两个包装类引用相等性

在Java中,“==”符号判断的内存地址所对应的值得相等性,具体来说,基本类型判断值是否相等,引用类型判断其指向的地址是否相等。看看下面的代码,两种类似的代码逻辑,但是得到截然不用的结果。

Integer a1 = 1;
Integer a2 = 1;
System.out.println(a1 == a2); // true

Integer b1 = 222;
Integer b2 = 222;
System.out.println(b1 == b2); // false

这个必须从源代码中才能找到答案。Integer类中的valueOf()方法的源代码如下:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high) // 判断实参是否在可缓存范围内,默认为[-128, 127]
        return IntegerCache.cache[i + (-IntegerCache.low)]; // 如果在,则取出初始化的Integer对象
    return new Integer(i); // 如果不在,则创建一个新的Integer对象
}

由于1属于[-128, 127]集合范围内,所以valueOf()每次都会取出同一个Integer对象,故第一个“”判断结果为true;而222不属于[-128, 127]集合范围内,所以valueOf()每次都会创建一个新的Integer对象,由于两个新创建的对象的地址不一样,故第一个“”判断结果为false。

3、LinkedList

操作与ArrayList基本一致,其只有底层实现不同,LinkedList底层使用链表实现的,而ArrayList使用数组实现的。

注意可以自己创建一个LinkedList,应当有节点类和头结点类。

4、ArrayList 底层原码

//设置一个默认容量数值
private static final int DEFAULT_CAPACITY = 10;
//设置一个没有元素的数组为用来初值,这时容量为0
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList方法操作的数组,实际使用的
transient Object[] elementData;
//即数组有多长,有多少个元素(与容量不同)
private int size;

(数组)容量的扩容

①、首先是构造方法

//构造方法,首先将使用的数组赋初值为{},其容量为0
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

②、add(E)方法:可以往ArrayList里头添加元素,因此需要容量才可以扩充,以第一次使用add为例

 public boolean add(E e) {   //E为类中传入的泛型
        // 确保容量是否足够,否则会出错,第一次size为0,传入size+1=1
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;   //进行下一个位置赋值
        return true;  //返回值
    }
private void ensureCapacityInternal(int minCapacity) {
        //传入为minCapacity,初次为1,先调用calculateCapacity(elementData, minCapacity)
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }


 private void ensureExplicitCapacity(int minCapacity) { //参数是从calculateCapacity方法返回的
        modCount++;

        // 判断当前容量和数组的长度,容量>length
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);  //进入grow方法,传入minCapacity,也是calculateCapacity方法返回的,初次使用时还是为10
    }



 private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //初次传入时elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA(构造方法),因此进入if语句
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //返回DEFAULT_CAPACITY=10和minCapacity的最大值,这里即返回10
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //否则,ekementData不为空,直接放回minCapacity
        return minCapacity;
        }



  private void grow(int minCapacity) { 
        // 设置旧容量为数组长度,初次传入时为0
        int oldCapacity = elementData.length;
        //设置新容量= 旧容量+旧容量右移一位(即除以2)
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果新容量小于传入的minCapacity,则新容量为minCapacity,初次传入时newCapacity=0,minCapacity = 10,所以进入if,newCapacity更新为10
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果新容量大于MAX_ARRAY_SIZE,MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,即20几亿
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);//执行hugeCapacity
        // minCapacity is usually close to size, so this is a win:
        //执行Array.copyOf(),相当于扩容,初次传入即容量设置为10
        elementData = Arrays.copyOf(elementData, newCapacity);
    }      


private static int hugeCapacity(int minCapacity) {
        //判断异常
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //如果minCapacity > MAX_ARRAY_SIZE,返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

注:

第一次使用add方法是,容量扩容到10,当插入第10个元素时,容量依然没有增大。而当插入第11个元素时,需要扩容,这是size = 10,调用 ensureCapacityInternal(size + 1);

然后计算 calculateCapacity 的返回,这里elementData已经不为空,所以直接返回 newCapacity = size+1 = 11。

进入 ensureExplicitCapacity 中,容量>length,调用grow()方法

此时的旧容量为10,则新容量 = 10 + 10/2 = 15 ,最后进行判断,容量扩充为15

由此可得,每次到达扩容界限时,扩容后大小是原来容量的1.5倍(newCapacity = oldCapacity + (oldCapacity >> 1))

0 ---- 10 ----- 15 ------ 15+15/2=22 -------- 22+22/2=33 ------- 33+33/2=49

(数组)容量的缩减

trimToSize方法

public void trimToSize() {
        modCount++;
        //如果当前元素小于数组长度(容量),若不为,则将容量缩减为size大小(元素个数,刚好满的)
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }


//当执行了trimToSize方法后,容量进行了缩减后,在进行调用add方法才会进行扩容,扩容规则仍然是当前容量的1.5倍。

数组和List的异同:

相同:

(1)都属于线性表,有序的放置元素

(2)都有角标

不同:

(1)数组创立时必须声明长度,而List不用

(2)数组的长度不可改变,而List可以

(3)数组可以包含基本类型和对象类型,而List只可以放对象型(即可以使用包装类)

(4)数组是物理连续,而List中有ArrayList为物理连续,但LinkedList为逻辑连续

二、Set (集合)

遍历只可用迭代器和增强for循环

1、HashSet

HashSet里头的方法和List有相同,但没有get方法,因为集合中是不存在角标的,因此存放在HashSet中的元素是无序的,而且其中没有重复值。因此其最大的功能为去重。

HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。

HashSet 允许有 null 值。

HashSet 是无序的(其实顺序是根据HashCode值计算的,很复杂,相当于没有),即不会记录插入的顺序。

添加元素

HashSet 类提供类很多有用的方法,添加元素可以使用 add() 方法:

判断元素是否存在

我们可以使用 contains() 方法来判断元素是否存在于集合当中:

删除元素

我们可以使用 remove() 方法来删除集合中的元素:

计算大小

如果要计算 HashSet 中的元素数量可以使用 size() 方法:

2、LinkedHashSet

LinkedHashSet继承自HashSet,源码更少、更简单,唯一的区别是LinkedHashSet内部使用的是LinkHashMap。这样做的意义或者好处就是LinkedHashSet中的元素顺序是可以保证的,也就是说遍历序和插入序是一致的。

3、TreeSet

(字符时)字典序,插入时必须是比较(实现Comparable接口,重写comparaTo() 方法。)的对象

TreeSet底层是二叉树,可以对对象元素进行排序

TreeSet是JAVA中集合的一种,TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。它继承于AbstractSet抽象类,实现了NavigableSet,Cloneable,java.io.Serializable接口。

一种基于TreeMapNavigableSet实现。

因为TreeSet继承了AbstractSet抽象类,所以它是一个set集合,可以被实例化,且具有set的属性和方法。

TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。

TreeSet的性能比HashSet差但是我们在需要排序的时候可以用TreeSet因为他是自然排序也就是升序下面是TreeSet实现代码这个类也似只能通过迭代器迭代元素

ps:TreeSet是有序的Set集合,因此支持add、remove、get等方法。

4、Collections 工具类

Collections 类是 Java 提供的一个操作 Set、List 和 Map 等集合的工具类。Collections 类提供了众多操作集合的静态方法,使用这些方法可快速实现对集合元素的排序、查找替换和复制等操作,就像Arrays一样

img

img

二、Map(映射表)

Map是一个顶级接口,主要学习其底下实现的五个类:HashMap、LinkedHashMap、

Map<K,V> :Key,Value 键值对。一个键对应一个value(映射)

Map中添加元素使用put()放置

1、HashMap

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。

HashMap 是无序的,即不会记录插入的顺序。即Key就相当于一个HashSet,所以在原码中HashSet就是用HashMap实现的

HashMap 的 key 与 value 类型可以相同也可以不同,也可为null(空key也是key,空value也是value),可以是字符串(String)类型的 key 和 value,也可以是整型(Integer)的 key 和字符串(String)类型的 value。

HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

添加元素

HashMap 类提供类很多有用的方法,添加键值对(key-value)可以使用 put() 方法:

// 引入 HashMap 类      
import java.util.HashMap;

public class RunoobTest {
    public static void main(String[] args) {
        // 创建 HashMap 对象 Sites
        HashMap<Integer, String> Sites = new HashMap<Integer, String>();
        // 添加键值对
        Sites.put(1, "Google");
        Sites.put(2, "Runoob");
        Sites.put(3, "Taobao");
        Sites.put(4, "Zhihu");
        System.out.println(Sites);
    }
}

执行以上代码,输出结果如下:

{four=Zhihu, one=Google, two=Runoob, three=Taobao}

访问元素

我们可以使用 get(key) 方法来获取 key 对应的 value:

        Sites.put(1, "Google");
        Sites.put(2, "Runoob");
        Sites.put(3, "Taobao");
        Sites.put(4, "Zhihu");
        System.out.println(Sites.get(3));

执行以上代码,输出结果如下:

Taobao

删除元素

我们可以使用 remove(key) 方法来删除 key 对应的键值对(key-value):

        Sites.put(1, "Google");
        Sites.put(2, "Runoob");
        Sites.put(3, "Taobao");
        Sites.put(4, "Zhihu");
        Sites.remove(4);
        System.out.println(Sites);

执行以上代码,输出结果如下:

{1=Google, 2=Runoob, 3=Taobao}

计算大小

如果要计算 HashMap 中的元素数量可以使用 size() 方法:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值