数组
- 长度开始时必须指定,而且一旦指定,不能更改
- 保存的必须为同一类型的元素
- 使用数组进行增加/删除元素的示意代码-比较麻烦
二十四、集合
- 可以动态保存任意多个对象
- 提供了一系列方便的操作对象的方法
- 使用集合添加删除新元素的示意代码-简洁明了
集合主要是两组:单列集合,双列集合
Collection接口有两个重要子接口list set,他们的实现子类都是单列集合
Map接口的实现子类时双列集合,存放的k-v
Collection实现接口的特点:
- collection实现子类可以存放多个元素,每个元素可以是Object
- 有些Collection的实现类,可以存放重复的元素,有些不可以
- 有些Collection的是西安类,有些事有序的(List),有些不是有序的(set)
- Collection接口没有直接的实现子类,时通过它的子接口set和list来实现的
ArrayList的本质就是一个数组
ArrayList arrayList = new ArrayList();
arrayList.add("jack");
arrayList.add(10);//list.add(new Integer(10))本质上就是自动装箱
arrayList.add(true);
System.out.println(arrayList);
Colleact接口遍历元素方式1-使用Iterator迭代器
- Iterator对象称之为迭代器,主要用以遍历collection集合的元素
- 所有是西安类collection接口的集合类都有一个iterator方法,用以返回一个实现iterator接口的对象,即可以返回一个迭代器
- Iterator的结构
- Iterator仅仅用于遍历集合,它本身并不存放对象
hasNext()判断是否还有下一个元素
Next()作用1.下移 2.将下移以后集合位置上的元素返回
在调用用next方法前要调用hasnext,否则要是下边没东西会出exception
快捷键itit
如果希望再次遍历,需要重置我们的迭代器iterator = arrayList.iterator();
第二种遍历对象的方式-for循环增强
底层还是迭代器
List接口基本介绍:
List接口是collection接口的子接口
- List集合类中元素有序(即添加顺序和去除顺序一致)、且可重复
- List集合中每个元素
List 中set就是替换 sublist取自集合是前闭后开
ArrayList注意事项:
- ArrayList可以加入null,并且多个
- ArrayList是由数组实现数据存储的
- ArrayList基本等同Vector,除了ArrayList是线程不安全(执行效率高)看源码,在多线程情况下,不建议使用ArrayList·
ArrayList底层结构和源码分析
- ArratList维护了一个Object类型的数组elementData
transient Object[] elementDate//transient表示瞬间短暂的,是一个修饰符,表示这个被修饰的属性不被序列化
- 创建ArrayList对象时,如果使用无参构造器,第一次添加,则扩容elementdata为10,如果在其扩容,则扩容elementDate为1.5倍
- 如果使用的时指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,这届扩容elementData为1.5倍
源码中modcount是集合被修改的次数,目的是防止有多个线程取同时修改它
如果elementDate的大小不够就调用grow去扩容
第一次扩容是10后面是1.5倍》》是位运算,就是变成原来的0.5倍,扩容使用的是Arrays.copyOf(保证原先数据还在)
- 如果有参数构造器程序员指定elementDate大小,地磁扩容就按照elementDate的1.5倍扩容,其他的和3一样
Vector底层剖析:
- Veactor底层也是一个对象数组 protected Object[] elementDate
- Vector是线程同步的,即线程安全,有synchronized关键字
- 底层一夜是可变数组,安全但是效率不高,如果是无参,默认是10,满后2倍扩容,如果指定大小,则每次直接按2倍扩
- capacityIncrement是一个参数,里面是自定义增量
LinkedList:
- 底层是实现了一个双向链表和双端队列特点
- 可以添加任何元素,包括null
- 线程不安全,没有实现同步
底层机制:
- 底层维护了一个双向链表
- 两个属性first和last分别指向首节点和尾节点,里面又维护了prev、next、item三个属性
- Linklist的添加和删除,不是通过数组完成的,相对来说效率较高
LinkedList底层结构
Node构造器Node(Node prev , E element , Node next)
Remove自动返回被删除的对象
Set同理,在替换之后会返回原本的value(这两个都可以用Object接收)
因为LinkList是实现list接口,所以可以使用其遍历方式
ArrayList底层结构是可变数组,增删效率较低因为要数组扩容,改查效率高
LinkedList双向链表, 增删效率较高,改查效率较低
在实际开发中大部分都是查询,因此大部分选择ArrayList
Set接口基本介绍
- 无序(添加和取出数据不一样,没有索引,但它是固定的不会一会一变)
- 不允许重复元素,所以最多包含一个null
和list接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection的子接口一样
所以可以用1.迭代器 2.增强for 3.不能使用索引的方式来获取
Set的底层是一个数组+链表
hashSet的全面介绍:
- HashSet底层其实是一个hashmap(hashmap的底层是数组+链表+红黑树这个存储效率很高)
- 同理不同有重复元素,只能有一个null
- 添加一个元素时,先得到hash值-会转换成-索引值
- 找到存储表table,看这个索引位置是否已经存放有元素
- 如果没有,直接放入
- 如果有,调用equals比较(比的是谁,程序员说的算),如果相同,就放弃添加,如果不相同,就加到最后
- 在jdk8中,如果一条链表的元素个数到达了默认值8,并且table的大小》=64就会进行树化(红黑树)
Hash()能够得到一个key对应的hash值h = key。HashCode^(h>>16)为了防止让这个数不容易出现重复//^这个是异或
Resize()扩容
PRESENT是占位object,后面的value也是它
Node<K,V>[] tab; Node<K,V> p; int n, i;//定义了辅助变量
if ((tab = table) == null || (n = tab.length) == 0)//table就是HashMap得一个数组,类型是Node[],一开始table肯定是空的
DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 1向右左移4位就是1×2×2×2×2=16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//thr就是临界值=默认临界因子(0.75)×默认初始容量(16)同理,下一次数组到达临界值12,就会扩容到16*2=32.新的临界值就是32*0.75=24
threshold = newThr;//threshold域,界
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//通过上面计算的新容量,建立新数组
第一个if的作用就是当前的table如果是null,或者,其大小为0,就进行第一次扩容,到16个空间
if ((p = tab[i = (n - 1) & hash]) == null)//&位运算符
根据key得到的hash去计算该key应该存放到table表的哪个所引位置,并把这个位置的对象,赋给临时变量p,
判断p是否为null,
tab[i] = newNode(hash, key, value, null);
如果为null,表示还没有存放元素,就创建一个Node(value=PRESENT,next = null)
返回空表示成功,返回旧值表示添加失败
当if ((p = tab[i = (n - 1) & hash]) == null 不为null时,有三种情况
科普(key一样的,这个hash一定一样,但是不一样也有可能一样)
第一种:当前索引位置对应的链表的第一个元素和准备添加的key的hash的哈希值一样,并且满足第一个元素(p)与添加的key是同一个对象或者key不等于空且key的内容与p.key的内容相同(取决你重写的equals)
第二种:判断p是不是一颗红黑树,是的话,就按照红黑树的方式进行比较,调用putTreeVal(this,tab,hash,key,value)
第三种:上面两种都不满足的话就是第三种情况,一次和该链表的每个元素比较后,都不相同,则加入到该链表的最后(在把元素添加到链表后,立即判断该链表是否达到8【0-7】个节点,达到了就调用treeifyBin(tab, hash);
对当前这个链表进行树化转成红黑树,在转换成红黑树时,如果表的length达到64,才能真正树化,如果没达到,就对table表进行扩容);如果在比较过程中有相同的情况,直接break;
树化的条件是:既要链表长度达到8,也要table数组长度达到64
TREEIFY_THRESHOLD(链表的树化默认临界值是8)
MIN_TREEIFY_CAPACITY(table树化的默认长度)
当我们向hashset种增加一个元素,加入table表(在链表后面也算)就算是增加了一个
面试题:
这句话是hashcode里说的
- 如果两个对象根据equals(Object)方法相等,则对两个对象中的每个对象调用hashCode方法必须生成相同的整数结果。
所以 String hhh = new String("hhh");
String hhh1 = new String("hhh");
System.out.println(hsp.equals(hsp1));//true 这是因为String的equals是比内容的,因此他俩的hashcode也是相同的,因此在哈希表里不能重复出现
但是这俩地址不一样,System.out.println(hhh==hhh1);//false
hashset仍然只有一个hhh得到了解释