容器(ArrayList)

容器|集合:存储多个数据 

 集合:
        存储任意引用类型数据
        集合的长度可变,可以根据需求动态的增删数据,长度随之改变

        Collection        (单一值)                             Map  (键值对)

           /       \                                                      /             \

         List      Set                                           HashMap     HashTable

ArrayList      HashSet (无须的  不重复)

LinkedList  (有序的   重复的)

1.Collection

        容器中的数据层次结构:

                Collection: 集合层次结构中的根节口。集合表示以组对象。称为元素

                遍历方式:

                        foreach

                        iterator迭代器

2.泛型

泛型:jdk1.5

        参数化类型:数据类型作为参数传递

                                只能配置引用数据类型

        <>定义泛型

        泛型的行为发现在编译期间,运行期间泛型配置的内容无效,泛型擦除4

        要求先定义泛型,才能使用泛型

        泛型类:类型的后面定义泛型,在使用类型的时候可以通过泛型传递具体的类型,类中可以进行使用

        泛型方法:

                代码简单简洁

                增强程序健壮性,避免类型转换异常的出现

                增强稳定性和可读性

//定义
CollectionA<String> coll = new ArrayList<>;

 3.List

            List:有序,可重复

            Set:无序,不可重复的|唯一的

            List:有序集合(序列)。该接口的用户可以精确控制列表中每个元素的插入位置。

            新增: 一些列根据索引操作的方法(对于Collection)
                    遍历方式 :
                                for普通for循环
                                foreach 增强for循环
                                iterator迭代器
                                listIterator 迭代器             

        ArrayList

有序,可重复的
     ArrayList : 实现所有可选列表操作,并允许所有元素,包括null 。
        底层结构: 数组  Object[] elementData;
        特点: 根据索引查询遍历效率较高,增删效率低
        应用场景: 适合应用在大量做查询,少量做增删的位置
        扩容问题:
            初始容量 : 默认初始容量为10 ,在第一次添加add的时候进行构建  private static final int DEFAULT_CAPACITY = 10;
            扩容机制 : 每次扩容原容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1);
        新增功能: void forEach(Consumer<? super E> action)
        遍历 :
            普通for
            增强for
            iterator迭代器
            listIterator迭代器
     Vector :
        底层结构 : 数组
        特点 : 与ArrayList相同
        区别:
            1)  Vector是同步的|线程安全
                ArrayList不同步|线程不安全,相对效率较高
            2) Vector每次扩容原容量的2倍
               ArrayList每次扩容原容量的1.5倍,相对ArrayList更有利于节省内存

     总结 :  如果不需要线程安全实现,建议使用ArrayList代替Vector

       //空构造器  不会有初始容量的数组 会默认给一个空数组,因为我们不一定会使用他
     public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
     //在add添加元素上
        public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
//elementData 当前数组   size当前数组存了多少个值
//调用add(e,elementData,size)
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)  //如果超过数组的存储范围  开始扩容
            elementData = grow(); 
        elementData[s] = e;
        size = s + 1;
    }
//扩容
    private Object[] grow() {
        return grow(size + 1);
    }
    private Object[] grow(int minCapacity) {
        //进行数组的拷贝  多余的用默认值填充
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }
//计算新数组的容量
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //扩容规则 利用位运算达到原数组的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //初始化数组  newCapacity=0
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                //默认大小16
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        //Integer.MAX_VALUE - 8  当新数组的长度 > 最大数组长度时
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //返回
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
    }

        List<String> aaa = List.of("aaa", "bbb", "ccc");
        aaa.set(0,"bb");

LinkedList

 LinkedList: 实现所有可选列表操作,并允许所有元素(包括null )。
        底层结构: 双向链表
        特点: 查询效率低,增删效率高
        应用场景:单个数据值的集合中,允许数据有序,且可重复,在大量做增删,少量做查询的时候时候适合使用链表
        新增功能: 新增了一些与链表头尾相关的方法

  不同步。

Iteator迭代器

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("aa");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            if (next.equals("aa")){
                list.add("ii");
            }
        }
    }

 

在使用iterator遍历集合时,对集合进行进行修改操作,出抛出并发异常

相当与list和iterator 相当于两个用户同时对集合进行操作,对于iterator来说list的增删操作可能影响我iterator的遍历结构,对于新增的数据我可能读取不到,所以就抛出异常。

可以使用List 新增的listIterator迭代器进行处理

        ListIterator<String> iterator = list.listIterator();
        while (iterator.hasNext()){
            String next = iterator.next();
            if (next.equals("aa")){
                iterator.add("ii");
            }
        }

4.Set

    Set : 无序,不可重复|去重
        无序: 存放的顺序与内部真实存储的顺序不一致
        去重: 集合不包含元素对e1和e2 ,使得e1.equals(e2)和最多一个null元素。

        新增功能: static <E> Set<E> of(E... elements) 返回包含任意数量元素的不可修改集。
        遍历方式:
            foreach
            iterator迭代器

TreeSet

TreeSet :
        底层结构 : 红黑树 (二叉搜索树+平衡数)
        特点 : 查询效率较高,自动把数据做升序排序
        底层是由TreeMap维护的
        新增功能: 新增了一些与比较大小相关的方法
        遍历方式 :
            foreach
            iterator迭代器

    注意 : TreeSet需要存储相同类型的数据,因为会默认存在比较排序

        TreeSet<Double> tree = new TreeSet();
        tree.add(3.2);
        tree.add(2.2);
        tree.add(5.2);
        tree.add(1.2);
        tree.add(.2);
        System.out.println(tree);
        //返回tree中大于或等于给定元素的  如果没有该元素返回null
        System.out.println(tree.ceiling(2.3));
        //返回tree中小于或等于给定元素的  如果没有该元素返回null
        System.out.println(tree.floor(2.3));
        // 返回此集合中当前的第一个(最低)元素。
        System.out.println(tree.first());
        //返回此集合中的最小元素严格大于给定元素,如果没有这样的元素,则 null 。
        System.out.println(tree.higher(2.2));
        //E lower(E e) 返回此集合中的最大元素严格小于给定元素,如果没有这样的元素,则 null 。
        //E pollFirst() 检索并删除第一个(最低)元素,如果此组为空,则返回 null 。
        //E pollLast() 检索并删除最后一个(最高)元素,如果此集合为空,则返回 null 。
        System.out.println(tree.pollFirst());
        System.out.println(tree);

 对于TreeSet 无序的原因是因为内部存在着比较机制,会对加入的数进行比较排序,所以导致数据的结果是无序的。

对于自定义的引用类型我们需要添加比较器,内部比较器或外部比较器。

比较规则:

        内部比较器|内部比较规则|自然排序

                javabean类型实现Compareable接口,重写compareTo(T o)方法、

        内部比较器|外部比较规则|定制规则: 比较规则定义在javabean类型的外部

                定义一个实现类,实现Comparator接口,重写int compare(T o1, T o2)

对于内部比较器和外部比较器来说:

        如果没有外部比较器就选择内部比较器,否则选择外部比较器

TreeSet<Person> set = new TreeSet<>((o1,02) -> o1.age-o2.age); //外部比较器
//内部比较器
class Person implements Compareble<Person>{
@Override
    public int compareTo(Employee o) {
        return this.name.compareTo(o.name);
    }
}

 HashSet

Set:

        TreeSet:默认升序排列

        HashSet:

                        底层结构:哈希表(数组+链表+红黑树)

                        特点:查询,增删效率高,去重,无序

                        底层是由HashMap维护的

                        遍历:foreach  iterator迭代器

                        无新增方法

                        注意:

                                此类允许null元素

                                线程不同步

 5.Map

 Map:

        无序的,去重的

        键值对->影射关系

        键值对:K-V

        K键 : 无序的,去重的|唯一的 ---> Set
        V值 : 无序的,可重复 ---> Collection
        K-V可以为任意引用数据类型

        特点:

        一个key只能对应一个Value
        key相同value覆盖

         遍历方式:
                1.values 获取所有键值对的值
                    Collection<V> values() 返回此映射中包含的值的Collection视图。
                2.keySet 获取所有键值对的key,根据key获取value
                    Set<K> keySet() 返回此映射中包含的键的Set视图。
                3.entrySet 获取所有的键值对,每一个键值对都是一个Entry类型->表示一个键值对
                    Set<Map.Entry<K,V>> entrySet() 返回此映射中包含的映射的Set视图。

TreeMap

 TreeMap :
        底层: 红黑树
        存储键值对类型的数据,自动升序排序,去重的
        去重,排序: 根据键值对的key实现,与value本身无关
        TreeSet底层是由TreeMap
        不同步

        去重|排序: 根据key的类型的比较规则
                    key的数据类型实现内部比较器
                    传递外部比较规则

HashMap

基于哈希表的Map接口实现。此现实提供了所有可选的映射操作,并允许null和null键

        HashSet 底层是由HashMap

        底层结构:哈希表(数组+链表+红黑树)

哈希表

        数组:节点数组Node[] table --> 要求数组的长度为2的整数次幂

         Node:

//key是唯一的且不可以更改所以hash和key都是常量
final int hash;
final K key;
//值可以更改
V value;
//指向下一个节点  采用尾插法
Node<K,V> next;

        当链表的长度>8 且数组的长度>64时,会把链表优化成红黑树

         当链表的长度>8 但数组的长度小于<64时,这时候会进行数组的扩容

 初始容量:

 加载因子:0.75

对于HashMap()  没有进行初始化

		
		Node<K,V>[] oldTab = table;
		//初始化时 oldTab为空
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
		//初始化时0
        int oldThr = threshold;
        int newCap, newThr = 0;
		//是否有值
        if (oldCap > 0) {
			//数量不能大于2的30次幂
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
			//如果小于并且大于初始容量16  进行扩容操作  (16*0.75)<<1 24
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
		//当初始化指定容器大小时
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
		//空构造器   初始化容器大小为16  阈值为12	
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
		//当新阈值为0时  确定阈值  对于给定容器初始化大小
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
		//
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
		//数组扩容
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
		//copy工作

        默认加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f;

扩容阀值:

           threshold:扩容的临界值  数据的个数>数据的长度*加载因子就会扩容

 扩容机制:

        用容量的两倍  newCap = oldCap << 1

HashMap的哈希表存储数据的过程:

        1.根据key计算哈希值

            通过key的hashCode方法的返回值进一步进行hash算法的运算,得到的整数
            int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

        2.调用putVal方法实现添加数据(hash,key,value)

         1)判断是否是第一次调用put方法做添加 if ((tab = table) == null || (n = tab.length) == 0)
              如果是第一次添加,直接调用resize()实现扩容
         2)计算位桶的索引 int index = (n - 1) & hash
         3)判断哈希表结构的数组table[index]是否存在数据,
           如果不存在数据,证明没有头节点,创建新节点,放入当前数组的对应索引位置作为头节点
           table[index] = new Node<>(hash, key, value, next);
           size数据的个数+1,判断是否>扩容的阀值,如果大于需要调用resize方法进行扩容,如果不大于,不需要扩容直接返回null
            if (++size > threshold) resize();
            return null;
            如果存在数据,作为链表的头结点,遍历这个链表,拿到每一个节点的key与hash值判断是否与要添加的key和hash相同,如果相同,value覆盖
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                    value覆盖之后,返回被覆盖的value
                    V oldValue = e.value;
                    e.value = value;
                    return oldValue;

if ((tab = table) == null || (n = tab.length) == 0)  //初始化Node[]数组
            n = (tab = resize()).length;
			//p = hash值与n-1进行运算得到 对于数组位置的下标索引
        if ((p = tab[i = (n - 1) & hash]) == null)
			//如果是第一次插入
            tab[i] = newNode(hash, key, value, 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)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            //遍历整个链表 判断是否有key一样的通过hash和equals方法 没有的化进行尾插
			else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        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;
                }
            }
			//说明存在key相等的情况  进行vlaue替换
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
				//返回被替换的值
                return oldValue;
            }
        }
        ++modCount;
		//如果大于阈值进行扩容操作
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
		//不存在相等的返回null
        return null;

对于HashMap因为无序是因为存在着自己的存储模式,在存储自定的数据类型时,需要重写equals和hashCode()方法,定义自己的相等类型的数据模式是什么,否则按照地址来看,每一个对象都是不同的。对于自定义的hashCode()来说,不同对象的hashCode()是可以相等的,但equals()不一定是相等的。但equals相等的hashCode也一定是相等的。equals比较的要比hashCode()计算的多或者相等。

//名字 薪水 和 编号查看是不是同一个员工    
@Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return id == employee.id &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(name, employee.name);
    }
    //按照name和salary 计算hashCode
    @Override
    public int hashCode() {
        return Objects.hash(name, salary);
    }

HashTable

HashTable 与HashMap直接的区别:

        共同点:都是Map接口的实现类,底层结构都是哈希表

        异同点:

                1.继承体系不同

                2.线程是否安全不同

                        HashMap 线程不安全|不同步

                        HashTable  线程安全的|同步的

                3.扩容机制不同

                        HashMap扩容机制:每次扩容原容量的两倍

                                int newCap = oldCap << 1

                        HashTable 扩容机制:原容量的2倍+1

                                int newCapacity = (oldCapacity << 1) + 1;

                4.键值对数据null值的要求不同

                        HashMap 可以存储null值的key和value

                        HashTable key 与value都不为null

                5.计算hash值与位桶索引index的算法不同

                        HashMap:

                                 int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
                                 int index = (n - 1) & hash

                        HashTable

                                int hash = key.hashCode();
                                int index = (hash & 0x7FFFFFFF) % tab.length;

如何处理HashMap线程不安全问题:

                1.使用HashTable
                 2.使用Collections工具类中static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) 返回由指定映射支持的同步(线程安全)映射。

                3.juc高级并发编程包 ConcurrentHashMap<K,V> 线程安全的哈希表

 Properties

//Properties类表示一组持久的属性。 Properties可以保存到流中或从流中加载。 属性列表中的每个键及其对应的值都是一个字符串。
//可以通过Properties实现软编码,便于后期维护  继承HashTable
public class PropertiesTest {
    public static void main(String[] args) throws IOException {
        Properties properties = new Properties();
        properties.setProperty("sex","男");
        System.out.println(properties.getProperty("sex"));
        properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("login.properties"));
        System.out.println(properties.getProperty("username"));
        properties.store(new FileOutputStream("./login.properties"),"登录信息");

    }
}
对于使用Properties去读取持久化数据或者是去持久化数据,需要提供字节流或字符流,可以通过自定义路径或者是通过Thread.currentThread().getContextClassLoader().getResourceAsStream()方法获取到,当前classpath的路径。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值