此篇为记录Java基础中集合章节的有关知识点,主要包括Collection接口、Map接口下的实现类,以及他们之间的联系和区别
1、Java的集合类很多,主要有Collection 接口和 Map 接口两大类,它们都继承了 Iterable 接口。
2、Collection接口 最常见的是Set 和List 接口;
3、Map接口中常见的实现类有HashMap、TreeMap、HashTable
//以hashSet为例:
HashSet hashSet = new HashSet();
Iterator iterator = hashSet.iterator();//1. 先得到 col 对应的 迭代器
//使用 while 循环遍历,此循环可以使用 itit 快捷键
//显示所有的快捷键的的快捷键 ctrl + j
while (iterator.hasNext()) {//判断是否还有数据
Object obj = iterator.next();//返回下一个元素,类型是 Object
System.out.println(obj);
}
//当退出 while 循环后 , 这时 iterator 迭代器,指向最后的元素 iterator.next();
//NoSuchElementException
//如果希望再次遍历,需要重置我们的迭代器
iterator = hashSet.iterator();
注意:
//以HashSet为例
HashSet hashSet = new HashSet();
for (Object obj : hashSet) {//此时可用快捷键 hashSet.I
System.out.println(obj);
}
List 接口是Collection接口的子接口。
一、注意事项
二、底层操作机制源码分析
扩容机制结论:
ArrayList 底层维护了一个Object类型的数组elementData。
当创建ArrayList对象时它有两种构造器,一种是无参构造,另一种是有参构造。当它创建时的长度为0时使用无参构造,则扩容elementData为10,如需要再次扩容,则是elementData的1.5倍。
当创建elementData给定一个长度时用有参构造,如需要扩容,则直接扩容为elementData的1.5倍。
在添加元素的时候,先调用特定的方法来确定是否要扩容(每次都要看是否需要扩容)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
确定要扩容之后把需要扩容的大小设置为10,然后表示扩容的次数的变量自增1,如果大小不够,就调用grow()扩容(扩容到原来的1.5倍(length + length >> 1)),容量够了就不会调用grow()方法了。
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
grow()方法源码:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
一、注意事项
二、debug源码
//底层同样跟ArrayList是elementData数组
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
/**二倍扩容机制
*/
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
一、注意事项
二、DebugLinkedList的添加元素 源代码
//DebugLinkedList的添加元素 源代码
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
三、DebugLinkedList的插入元素 源代码
public void add(int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
/**
* Inserts element e before non-null Node succ.
*/
void linkBefore(E e, Node<E> succ) {
// assert succ != null;
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
一、注意事项
一、注意事项
二、HashSet添加元素的扩容机制
1.HashSet的底层是HashMap,添加一个元素时,先得到它的hash值,然后通过算法转化成索引值。第一次添加时,table数组扩容到16,临界值是12(个元素),到达临界时就会进行第二次扩容(临界值 = 容量 * 0.75),扩容时按两倍容量扩容.
2. 找到存储数据表table(是一个Node类型的数组),看这个索引位置是否已经存放的有元素,如果没有则直接添加;如果有,则调用equals方法比较(equals方法是程序员自己设置的比较方法(重写)),如果相同,则放弃添加,反之则加到最后。
3. 在Java8中,如果一条链表的元素到达8个,并且table的大小达到64个,那么会树化(形成红黑树).如果一条链表达到8个但是table的大小还没到64,则会继续扩容table表,并且在同一条链表上,那么每加一个就会扩容一次(因为都大于8),直到满足table表大于64,才会树化。
三、HashSet的源码解读
1、HashSet底层实际上是HashMap
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
2、底层调用的是HashMap的put()
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
hash(key)的值是通过hash方法得来的
而hash方法里又有hashXCode()以及相应的算法(h >>> 16)
*/
//hash()源码:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//hashCode()源码:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
3、关键点在putVal()上:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;//这是一些临时变量
/**
如果数组为空那么就给数组一个初始值,这个默认值为16
*/
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
/**
判断数组表中的位置是否为空,如果为空,则在数组table表中添加这个新的元素(结点)
*/
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
/**
否则就定义一个节点,
1、如果要添加元素的hash值与p的hash值(当前table表的某个Node,它的链表的头节点位置的hash值)相等
或者这个要添加元素的key值与当前链表的头结点的key值相等(equals方法是程序员自己重写的,具体通过什么来判断是不确定的)
那么就把当前结点赋值给e
2、如果p是一个红黑树的节点,就执行树的添加机制
*/
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);
/**
3.1否则(不是同一个元素),循环判段table表的这个元素的这个链表,
如果有相同的就放弃添加,
如果判断完了没有相同的就加在链表的最后。
如果链表的长度大于等于8以后,就执行treeifyBin(),也就是会再看table表的长度有没有64(会进入判断是否树化的方法中).
*/
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;
}
}
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);
return null;
}
一、注意事项
二、底层源码分析
3. table是一个多态数组,它的编译类型是Node,但是里面存放的是Entry,说明Entry继承了Node(事实上他们都是内部类,Entey内部类也确实继承了Node内部类)
/**
* HashMap.Node subclass for normal LinkedHashMap entries.
*/
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
Entry(int hash, K key, V value, Node<K,V> next) {
super(hash, key, value, next);
}
}```
```java
一、注意事项
TreeSet treeSet = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//按照K(string)的大小进行排序
return ((String)o1).compareTo((String)o2);
//也可按照K(string)的长度进行排序
//return ((String)o1).length - ((String)o2).length;
}
});
二、源码分析(以添加元素为例)
在add时底层也是调用put()
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
//而关键就在于put(),它里面有一个compare方法,用来判断两个Key是否相等,然后执行相应的添加机制。
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
一、注意事项
一、注意事项:
二、底层机制
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
2、添加时调用put()方法,里面实际上是一个putVal() 和一个hash算法(hash(key)是为了得到key对应得hash值,以此来确定索引的位置)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
3、详细看代码的注释:
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是判断当前的table是否为空,如果为空则默认扩容为16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//这里是判断当前hash值对应的索引位置是否为空,如果为空,直接将元素放在这。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {//到这里说明这个hash值对应table的索引位置有元素了,所以要开始进一步判断
Node<K,V> e; K k;
//①如果(它们的hash值相等)&&(要存放元素的key与当前的Key相等 || 它们的equals相等)
//此时相当于用新的元素来替换掉当前元素,当然Key是不变的(key值唯一,替换value值)
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);
//以上①②都不满足,说明要添加的元素要放的位置是当前链表
else {
//循环判断当前链表的所有元素,如果没有相同的,就把它添加到链表最后
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果链表的元素 >= TREEIFY_THRESHOLD(8) - 1,
//则进入到treeifyBin(判断table的大小有没有64,再决定要不要树化)
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果链表中的元素与要添加的元素相同,那么就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;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)//这是加载因子,如果达到临界值就按2倍扩容
resize();
afterNodeInsertion(evict);
return null;
}
一、注意事项
二、底层源码
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
一、注意事项
二、使用入门
public class PropertiesTest {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();//1、先创建properties对象
properties.load(new InputStreamReader(//2、加载配置文件,注意编码格式
new FileInputStream("src\\mysql.properties"),"GBK"));
//这里要设置编码,编码格式要与properties的格式一致,这里Properties的编码是GBK,所以也将它设置为GBK
properties.list(System.out);//可以输出配置文件
String name = properties.getProperty("name");//也可以根据键值对 取出自己想要的值
String id = properties.getProperty("id");
String pwd = properties.getProperty("pwd");
System.out.println("name = " + name +"\nid = "+ id +"\npwd = "+ pwd);
Properties properties1 = new Properties();
properties1.setProperty("基本信息","内容");//设置信息
properties1.setProperty("姓名","韩顺平");
properties1.setProperty("年龄","21");
//将上述内容创建一个目标文件并保存在里面。
//这里的null是注释,如果有的话,在创建的时候会把注释写在文件上面,一般置空
properties1.store(new FileWriter("src\\mysql2.properties"),null);
properties1.list(System.out);
}
}
一、数据是单列存放的(选择Collection)
1、允许重复(选择List)
2、不允许重复(选择Set)