容器
左边是一个一个往里扔,右边是一对一对往里扔
容器这一章,很多容器的底层实现还是数组。
List常用方法
List是有序可重复,,,重复的都可以放进去,什么叫重复。就是两个元素互相equals。。
/**
* 测试List中的基本方法
* ArrayList顾名思义:底层是基于数组来实现的。
*
*/
public class Test01 {
public static void main(String[] args) {
List list = new ArrayList();
//ArrayList:底层实现时数组,线程不安全,效率高。所以,查询快。修改、插入、删除慢。
//LinkedList:底层实现是链表,线程不安全,效率高。所以,查询慢。修改、插入、删除快。
//Vector:线程安全的,效率低。
//关于线程。如果这些定义成局部变量那就和线程没什么关系了,所以大部分还是可用的。成员变量才有关系,还记得书上写的吗。
//怎么记? 数组就是查询快,有下标嘛。数组是查询最快的
list.add("aaa");
list.add("aaa");
list.add(new Date());
list.add(new Dog());
list.add(1234); //包装类的:自动装箱!
list.remove(new String("aaa")); ---
System.out.println(list.size()); -- 注意这个size和内部数组的长度不一样,size一定是<=数组长度。
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
list.set(3, new String("3333"));
list.add(4, new String("3333"));
/* System.out.println(list.isEmpty());
list.remove(new Dog()); //这里和hashcode和equals
System.out.println(list.size());
List list2 = new ArrayList();
list2.add("bbb");
list2.add("ccc");
list.add(list2);
//跟顺序的操作。list是有序。 可以重复就有序嘛,哈哈。。
String str = (String) list.get(0); 返回是object
System.out.println(str);
list.set(1, "ababa"); //指定某个位置往里放
list.remove(0);*/ //删除某个位置
}
}
class Dog {
}
ArrayList自己实现
/**
* 自己实现一个ArrayList,帮助我们更好的理解ArrayList类的底层结构!
*ArrayList底层是数组,可以支持数组扩容,
* 传了索引就要查看索引是否越界rangeCheck,增加add就要检查扩容问题。
*/
public class SxtArrayList /*implements List*/ {
private Object[] elementData; //容器里面的对象常常称之为元素
private int size;
public int size(){ //面向对象封装的思想,不能直接使用private变量
return size;
}
public boolean isEmpty(){
return size==0;
}
public SxtArrayList(){
this(10);
}
public SxtArrayList(int initialCapacity){
if(initialCapacity<0){
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
elementData = new Object[initialCapacity];
}
public void add(Object obj){
//数组扩容和数据的拷贝
if(size==elementData.length){
Object[] newArray = new Object[size*2+1];
System.arraycopy(elementData, 0, newArray, 0, elementData.length);//参数:原数组,从哪开始拷,目标数组,从哪开始拷,拷几个
// for(int i=0;i<elementData.length;i++){ //和上面效果是一样的
// newArray[i] = elementData[i];
// }
elementData = newArray; //扩容后把老的数组替换
}
elementData[size++]=obj;
// size++;
}
public Object get(int index){ //get不会扩容
rangeCheck(index);
return elementData[index];
}
public void remove(int index){
rangeCheck(index);
//删除指定位置的对象
//a b d e
int numMoved = size - index - 1; (可以这么记size-1-index)
if (numMoved > 0){
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
}
elementData[--size] = null; // Let gc do its work,让最后一个元素为空
}
public void remove(Object obj){
for(int i=0;i<size;i++){
if(get(i).equals(obj)){ //注意:底层调用的equals方法而不是==.
remove(i);
}
}
}
public Object set(int index,Object obj){
rangeCheck(index);
Object oldValue = elementData[index];
elementData[index] = obj;
return oldValue;
}
public void add(int index,Object obj){
rangeCheck(index);
ensureCapacity(); //数组扩容
System.arraycopy(elementData, index, elementData, index + 1,
size - index); //增加为啥不是size-1-index。因为index处的元素不能丢。
elementData[index] = obj;
size++;
}
private void ensureCapacity(){
//数组扩容和数据的拷贝
if(size==elementData.length){
Object[] newArray = new Object[size*2+1];
System.arraycopy(elementData, 0, newArray, 0, elementData.length);
// for(int i=0;i<elementData.length;i++){
// newArray[i] = elementData[i];
// }
elementData = newArray;
}
}
private void rangeCheck(int index){ //注意里面的方法要设成private
if(index<0||index>=size){
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SxtArrayList list = new SxtArrayList(3);
list.add("333");
list.add("444");
list.add("5");
list.add("344433");
list.add("333");
list.add("333");
System.out.println(list.size());
// System.out.println(list.get(6));
list.remove("444");
System.out.println(list.size());
}
}
LindedList自己实现
双向链表:节点中有上下两个节点的引用
单向链表:节点中只有下一个节点的引用
//用来表示一个节点,单向链表就只有一个
public class Node {
Node previous; //上一个节点
Object obj;
Node next; //下一个节点 这里应该要设成private,但是要大量的写set get有点麻烦,这里暂且不加private
public Node() {
}
public Node(Node previous, Object obj, Node next) {
super();
this.previous = previous;
this.obj = obj;
this.next = next;
}
public Node getPrevious() {
return previous;
}
public void setPrevious(Node previous) {
this.previous = previous;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
public class SxtLinkedList /*implements List*/ {
private Node first; //节点类可以单独定义,也可以定义在内部表示专门为外部类服务
private Node last; //为什么定义首尾,因为知道了首尾节点就可以顺着找到其他节点。
private int size;
public void add(Object obj){
Node n = new Node();
if(first==null){ //脑子里想着链表的图,再赋值
n.setPrevious(null);
n.setObj(obj);
n.setNext(null); //先设节点的值
first = n; //再设链表的值
last = n;
}else{
//直接往last节点后增加新的节点
n.setPrevious(last);
n.setObj(obj);
n.setNext(null); //先设节点的值
last.setNext(n); //再设链表的值,这里加在链表最后,影响的只是last
last = n;
}
size++;
}
public int size(){
return size;
}
private void rangeCheck(int index){
if(index<0||index>=size){
try {
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Object get(int index){ //2 //遍历的时候记一个数,遍历第一个记为1,第二个记为2
rangeCheck(index);
// 0 1 2 3 4
Node temp = node(index);
if(temp!=null){
return temp.obj;
}
return null;
}
public Node node(int index){
Node temp = null; //temp表示每次遍历当前指定的这个节点
if(first!=null){
// 一进来的话,让index索引和size/2比较,小于一半就从前遍历,大于一半从后遍历
//>>表示移位符,右移一位表示除以2,左移表示乘以2。为啥不直接除,因为移位比除法快得多
if (index < (size >> 1)) {
temp = first;
for(int i=0;i<index;i++){//找index次就可以,外层的if是优化用的
temp = temp.next;
}
}else{
temp = last;
for (int i = size - 1; i > index; i--){
temp = temp.previous;
}
}
}
// LinkedList l;
return temp;
}
public void remove(int index){
Node temp = node(index);
if(temp!=null){
Node up = temp.previous;
Node down = temp.next;
up.next = down;
down.previous = up;
size--;
}
}
//脑子中有点形象思维:按照上一节点-->新节点-->下一节点的顺序,修改引用
public void add(int index,Object obj){ //index索引处插入一个节点。思路:先获得该节点,然后修改引用
Node temp = node(index); //它是新节点的下一个
Node newNode = new Node();
newNode.obj = obj;
if(temp!=null){
Node up = temp.previous; //它是新节点的上一个
up.next = newNode;
newNode.previous = up;
newNode.next = temp;
temp.previous = newNode;
size++;
}
}
public static void main(String[] args) {
SxtLinkedList list = new SxtLinkedList();
list.add("aaa");
list.add("bbb");
// list.add(1,"BBBB");
list.add("ccc");
list.add("ddd");
list.add("eee");
// list.remove(1);
System.out.println(list.get(3));
}
}
Map
映射的意思。。。
注意注意:容器里面存放的是地址,remove只是从容器中移除了地址,但是对象还在。
下面的代码中:执行了remove(“高琪”),也还是可以打印出wife的名字。 因为删地址前,已经把wife对象给w了。
public class TestMap {
public static void main(String[] args){
Map map = new HashMap();
map.put("高琪",new Wife("高琪老婆"));
map.put("老王",new Wife("老王老婆"));
map.clear(); //清空map
// map.remove("高琪"); 放这里就不行
Wife wife = (Wife)map.get("高琪");
map.remove("高琪"); //放这里依然可以取wife的值
System.out.println(wife.name);
}
}
class Wife{
String name;
public Wife(String name) {
this.name = name;
}
}
Map的子类中重点看下面两个:区别。。
Hash — 线程不安全,效率高
HashTable – 安全,效率低 和arraylist、vector & stringbuilder、stringbuffer的区别差不多。。
简单的实现 :
/**
*自定义实现Map的功能!
*暂不完美! 因为效率比较低,每次找元素都要遍历
*Map:存放键值对,根据键对象找对应的值对象.键不能重复!
*
*/
public class SxtMap001 {
SxtEntry[] arr = new SxtEntry[990];
int size;
public void put(Object key,Object value){
SxtEntry e = new SxtEntry(key,value);
//解决键值重复的处理
for(int i=0;i<size;i++){
if(arr[i].key.equals(key)){
arr[i].value=value; //如果key相等,那就把value覆盖掉
return ;
}
}
arr[size++] = e;
}
//remove就不写了,道理类似
public Object get(Object key){
for(int i=0;i<size;i++){
if(arr[i].key.equals(key)){
return arr[i].value;
}
}
return null;
}
public boolean containsKey(Object key){
for(int i=0;i<size;i++){
if(arr[i].key.equals(key)){
return true;
}
}
return false;
}
public boolean containsValue(Object value){
for(int i=0;i<size;i++){
if(arr[i].value.equals(value)){
return true;
}
}
return false;
}
public static void main(String[] args) {
SxtMap001 m = new SxtMap001();
m.put("高琪", new Wife("杨幂"));
m.put("高琪", new Wife("李四"));
Wife w = (Wife) m.get("高琪");
System.out.println(w.name);
}
}
class SxtEntry { //Entry条目条款的意思。。
Object key;
Object value;
public SxtEntry(Object key, Object value) {
super();
this.key = key;
this.value = value;
}
}
复杂的自己实现:
思路:查询的时候为了不遍历,可以考虑用数组(索引是hashcode),那数组又不可能无限大,就可以想办法把hashcode转成一个数组长度内(假设是999)的数字,怎么转呢? 可以考虑取999的余数,那就在之间了。
//a:1000–>1 b:10000–>13 但是余数可能重复。
余数冲突了怎么办??那就引出结论
结论:
数组里存放链表,链表里面再放key和value。
/**
* 自定义Map的升级版:
* 1. 提高查询的效率
* //Map的底层结构就是:数组里面放的是链表,链表里放KV,始终记住一个图。。
* 记住数组不为空,就拿到list去遍历KV.
* 这里就写了个put和get
*/
public class SxtMap002 {
LinkedList[] arr = new LinkedList[9]; //数组里面放的是链表,链表里放KV,始终记住一个图。。
int size;
public void put(Object key,Object value){
SxtEntry e = new SxtEntry(key,value);
int hash = key.hashCode(); //每个对象都有个地址,这个方法会根据地址生成一个数(哈希码),
hash = hash<0?-hash:hash; //哈希码有时候算出来是负值,这里先取反 再取余数。。数组index不能是负的
int a = hash%arr.length;
if(arr[a]==null){ //这个位置没有元素的时候,那就新建一个list,把元素放入list,再把list放入数组
LinkedList list = new LinkedList();
arr[a] = list;
list.add(e);
}else{
LinkedList list = arr[a]; //如果已经有一个元素了,那就往链表后加,key重复就覆盖
for(int i=0;i<list.size();i++){
SxtEntry e2 = (SxtEntry) list.get(i);
if(e2.key.equals(key)){
e2.value = value; //键值重复直接覆盖!
return;
}
}
arr[a].add(e);
}
//a:1000-->1 b:10000-->13
}
public Object get(Object key){
int a = key.hashCode()%arr.length;
if(arr[a]!=null){ //数组不为空,就拿到list去遍历key
LinkedList list = arr[a];
for(int i=0;i<list.size();i++){
SxtEntry e = (SxtEntry) list.get(i);
if(e.key.equals(key)){
return e.value;
}
}
}
return null; //数组[哈希码]等于空,说明list都没有,那就没有元素。。
}
public static void main(String[] args) {
SxtMap002 m = new SxtMap002();
m.put("高琪", new Wife("杨幂"));
m.put("高琪", new Wife("李四"));
Wife w = (Wife) m.get("高琪");
System.out.println(w.name);
}
}
通过以上实现,我们频繁的使用了hashcode()和equals()方法。
java中有这个规定:两个内容相等的对象就是两个对象equals。也就是两个对象equals,那hashcode一定要相等。 反之则不然。而且,要重写的话,两个方法都要重写。脑子里始终想起那个图。都到了equals那数组位置肯定一样,但是数组位置一样的有很多元素啊,就不一样equals了。
数组有个专业名字叫:buget桶。
fdsafsdafsad
顺带插一点Obejct的东西:
object又不是抽象类,为啥有没有实现的方法呢。因为前面加了native,表示本地方法。
是调用本地的资源,和操作系统相关的方法。。
public final native Class<?> getClass();
public native int hashCode(); //object的hashcode方法默认的是 拿内存中的地址进行计算,所以声明了native
public final native void notify();
要重写equals的话,hashcode也要重写。可以根据IDE自动重写,也可以自己重写
IDE自动生成。。。。。
class Cat extends Object{
String id;
String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Cat)) return false;
Cat cat = (Cat) o;
if (id != null ? !id.equals(cat.id) : cat.id != null) return false;
return name != null ? name.equals(cat.name) : cat.name == null;
}
@Override
//这里做了一个散列算法。一般是取一个质数,再去散列,这样会分布的比较均匀
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
2、map存值:分拣思路
/**
* this is a cat and that is a mice and where is the food?
* 统计每个单词出现的次数,这才是map强大的地方
*
* 存储到Map中
* key :String
* value:自定义类型
*
* "分拣" 思路
* 方法一 1、为所有key创建容器
* 之后容器中存放对应value
* 方法二 2、第一次创建容器,并存放值value
* 第二次之后,直接使用容器存放值
* @author Administrator
*
*/
public class Demo01 {
/**
* @param args
*/
public static void main(String[] args) {
//这里只看方法二
String str ="this is a cat and that is a mice and where is the food";
//分割字符串
String[] strArray=str.split(" ");
//存储到Map中,散列存在
Map<String,Letter> letters = new HashMap<String,Letter>();
for(String temp:strArray){
/*
//1、为所有key创建容器
if(!letters.containsKey(temp)){
Letter col = new Letter();
col.setCount(1); //第一次值存放容器中
letters.put(temp, col);
}else{
//2、 第二次之后,直接使用容器存放值
Letter col =letters.get(temp); //直接使用容器
col.setCount(col.getCount()+1);
}*/
//下面换一种写法,和上面大同小异
Letter col = null;
if(null==(col=letters.get(temp))){
col = new Letter();
col.setCount(1); //第一次值存放容器中
letters.put(temp, col);
}else{
//2、 第二次之后,直接使用容器存放值
col.setCount(col.getCount()+1);
}
}
// 遍历输出Map的值
Set<String> keys = letters.keySet();
for(String key:keys){
Letter col =letters.get(key);
System.out.println("字母:"+key+",次数"+col.getCount());
}
}
}
咕泡学院HashMap源码讲解JDK1.8
HashMap是java中最为常用的集合类之一,也是诸多集合类中实现最为复杂的,涉及的知识点最多。
HashMap没有排序,TreeMap才排序
JDK1.8相比于JDK1.7就HashMap来讲,多了很多东西。
准备点知识:
无符号右移(>>>)规则和右移运算是一样的,只是填充时不管左边的数字是正是负都用0来填充,无符号右移运算只针对负数计算,因为对于正数来说这种运算没有意义
无符号右移运算符>>> 只是对32位和64位的值有意义
https://www.cnblogs.com/blog-cq/p/5793529.html
重点的点:1、i = (n - 1) & hash] 做与运算。。。
2、为啥数组长度一定是2的N次幂。第一个是做与运算,会减少hash冲突几率,bit位为1的位置不会浪费。
第一:如果不是2的次幂,也就是低位不是全为1,这个时候相同的index就会有不同的hash值与之对应,这样的话hash冲突的几率又变大了(key找到对应的index的算法就是为了不冲突,这样的话又冲突了,相当于是增大了冲突的几率)。第二:同时,index对应的这个bit位无论如何不会等于1了,而对应的那些数组位置也就被白白浪费了。
第二个是扩容的时候效率更高,小于16的hash值就不动,大于16的才迁移。
扩容为啥效率高?见博客。
而扩容后只有一位差异,调换几率比较少,也就是多出了最左位的1,这样在通过 h&(length-1)的时候,只要h对应的最左边的那一个差异位为0,就能保证得到的新的数组索引和老数组索引一致,就不需要调换。这样就大大减少了之前已经散列良好的老数组的数据位置重新调换。
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
/**
先看看几个常量和成员变量
* The default initial capacity - MUST be a power of two.
*/
//左移4位是1的右面加四个零,变成10000,2的四次方=16.
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16.
//左移30位。int的最大取值是2的31次方-1;
static final int MAXIMUM_CAPACITY = 1 << 30;
、、负载因子,默认是0.75,作用是控制hashmap的容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
、、链表节点转成树形结构的阈值
static final int TREEIFY_THRESHOLD = 8;
、、反操作,树形结构转成链表的阈值
static final int UNTREEIFY_THRESHOLD = 6;
、、树的最小容量是64
static final int MIN_TREEIFY_CAPACITY = 64;
、、数组初始为0
transient Node<K,V>[] table;
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) { //经过这个方法之后,将会拿到一个int的hash值,然后再put
int h;
//从这里可以看出来key是可以为nul的
//key.hashCode()-- 这里有类重写就用重写,没有重写就用Object的,会拿到hashcode的一个值
//先执行左括号再执行右括号,再异或^ 为什么右移16位,这个很精髓。想象一个这个图。int是32位(分一半左边是高位右边是低位),右移16就是右边16位(低位)就没有了,高位全部用0来填充。
、、然后拿hashcode和移位后的数据 进行异或。相当于是数据的前半截和后半截去做异或运算。(把图画出来就知道了)
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
再到put putVal
Node是里面的一个静态内部类。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; //这里可以看出数组+链表格式。。
Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
//hashmap刚创建的时候这个是空的,并没有创建node数组,第一次put的时候才生成一个node数组(长度是16,见resize方法,也就是tab = resize()),类似于懒加载的思想
n = (tab = resize()).length; 第一次n是16
//计算出hash值要落在数组的哪个索引上呢? 这句经典的代码可以实现i = (n - 1) & hash
//n一定是二次幂,二次幂特征是1后面全部是0,那n-1就全部变成1了。(画二进制图理解)
//全部变成1的话,才能进行&运算(相同结果就是1),进而才能取模。所以通过i = (n - 1) & hash就找到table数组的下标了。
if ((p = tab[i = (n - 1) & hash]) == null) -- 第一次node肯定是空
tab[i] = newNode(hash, key, value, null); -- 第一次的next也是空,因为才一个元素,没有下一个
}else { -- 如果数组下标的这个位置不为null,就执行else了。
//这里挑重要的看
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode) //看这个节点是不是TreeNode的类型,现在还不是,它是一个普通的Node
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {//循环整个链表,直到发现next为空,就把元素放到链表最后
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//TREEIFY_THRESHOLD 是转换成红黑树 的阈值。当链表长度到达8就会转成一个树形结构
//为什么要转成红黑树?循环链表的话时间复杂度是O(n),而树是O(logN).对数阶的时间复杂度要比线性阶要好,为什么是8呢。8以内的O(N)可能还挺快,超过了就慢
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash); //很复杂。
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;//这里判断老的value值是不是等于当前写进来的value,等于就覆盖并把老值返回
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; --从这可以看出Node是一个链表结构,指向下一个相同的对象
}
//扩容。。扩容是个业内通用问题,resid啊,mysql啊,都用碰到
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap = 0;
newThr = 0; //阈值一开始是12,并赋给了threshold
newCap = DEFAULT_INITIAL_CAPACITY; 数组容量初始大小给16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); 容量的阈值=初始容量*负载因子。初始阈值是16*0.75=12.
扩容的话是左移一位,也就是*2,数组容量是翻倍(16-32-64),阈值也翻倍(12-24-)
扩容之后要重新计算索引值,就要对32取模了。newTab[e.hash & (newCap - 1)] = e;
存放的位置就要变了。这个过程叫做数据迁移,也叫做rebalance。
因为扩容的目的就是要把数据平均到不同的位置上去,这样才能提升检索速度。扩容相当于是横向扩展,链表转红黑树相当于是纵向扩展。 小于16的还是在左边,大于16的才搬家搬到右边。
以下是扩容的部分源码。
解释下:index小于16的和(16-1)去与运算的时候,结果都是0,那就不用动了。
index大于16的(高位肯定是1,否则怎么大于16呢??)和(16-1)去与运算的时候,结果就不为0了,就需要移动到新位置。
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
}
}
超过16*0.75=12就数组长度扩容,链表长度超过8也扩容变成tree。。
负载因子决定了扩容的阈值,最好别搞。。。负载因子变大,就不容易扩容,空间就小,那查找时间就慢了。时间换空间
负载因子变小,就经常扩容,空间就很大,那查找需要遍历几率就少了,就快了。空间换时间
concurrentHashMap就是上了锁,把锁的粒度变的比较小,做的挺好的。 总的来说,concurrentHashMap就是解决了hashmap线程安全的问题,而且锁的粒度做的比较小。
Hashtable源码分析以及和HashMap的区别:
见博客《【源码】Hashtable源码剖析》:
https://blog.csdn.net/chdjj/article/details/38581035
set
haspset的底层是通过hashmap实现的。
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
//haspset的底层是通过hashmap实现的。
private transient HashMap<E,Object> map;
public boolean add(E e) {
return map.put(e, PRESENT)==null; //set的不可重复就是利用了map里面键对象的不可重复!
}
}
set没有get,因为是无序的,拿数据出来只能遍历。
迭代器Iterator
集合类都实现了Collection接口的Iterator iterator();方法。返回一个Iterator帮你遍历。。。
public class Test01 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//通过索引遍历List
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//通过迭代器遍历List
for(Iterator iter2 = list.iterator();iter2.hasNext();){
String str = (String) iter2.next();
System.out.println(str);
iter2.remove();
iter2.remove();
}
System.out.println(list.size()+"******");
Set set = new HashSet();
set.add("高1");
set.add("高2");
set.add("高3");
//通过迭代器遍历Set,,遍历的时候用while和for都可以。
// Iterator iter = set.iterator();
// while(iter.hasNext()){
for(Iterator iter = set.iterator();iter.hasNext();){
String str = (String) iter.next(); //这个next就相当于i++,迭代的功能
System.out.println(str);
}
}
}
Iterator遍历map
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
Iterator<Map.Entry<Integer, Integer>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<Integer, Integer> entry = entries.next();
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}