容器|集合:存储多个数据
集合:
存储任意引用类型数据
集合的长度可变,可以根据需求动态的增删数据,长度随之改变
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) & hashHashTable
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的路径。