Java集合类

一、Java类集位于Java.util包下

二、Collection接口及其子接口List与Set。

1、Collection接口(JDK1.2)的继承实现结构

Collection结构

1)Collection继承了Iterable接口(JDK1.5)

可使用for-each循环(增强型for循环)

继承了该接口的核心方法Iterator<T> iterator()(返回值为Iterator类、集合遍历类)该方法在JDK1.5以前直接在Collection接口中直接定义的

2)Collection接口提供了两个最重要的方法

add();//给集合添加元素

iterator();//遍历集合:迭代器

2、子接口List(80%)(对Collection接口进行了扩充:get()由List接口提供)

最常用实现子类ArrayList的使用

List<String> list = new ArrayList<>();
list.add("3");
list.add("2");
list.add("5");
System.out.println(list.size());//3
System.out.println(list.get(2));//5
System.out.println(list.contains("3"));//true
System.out.println(list.contains(new String("3")));//true:String类覆写了equals()
System.out.println(list.remove(1));//2:remove()返回移除的值

//增强for循环遍历
for (String string : list) {
	System.out.println(string);//3 5
}

List集合与简单Java类(POJO):对于remove()、contains()等方法需要equals():覆写equals()比较属性值

class Person{
	private String name;
	private int age;
	
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
}
public class Test {

	public static void main(String[] args) {		
		List<Person> list = new ArrayList<>();
		list.add(new Person("123", 20));
		list.add(new Person("122", 20));
		list.add(new Person("125", 20));
		System.out.println(list.size());//3
		System.out.println(list.get(2));//Person [name=125, age=20]
		//修改:返回被修改前的值
		System.out.println(list.set(1, new Person("1", 12)));//Person [name=122, age=20]
		System.out.println(list.contains(new Person("123", 10)));//false
		//移除
		System.out.println(list.remove(1));//Person [name=1, age=12]
		
		//迭代器遍历
		Iterator<Person> iterator = list.iterator();
		while (iterator.hasNext()) {
			System.out.println(iterator.next());	
		}
	}
}

1)ArrayList与Vector的区别

a、Vector是JDK1.0提供的;ArrayList是JDK1.2提供的;

b、 Vector采用同步处理,效率低,线程安全,

ArrayList采用异步处理,效率高,线程不安全;

c、ArrayList支持Iterator、ListIterator、foreach输出,

Vector支持Iterator、ListIterator、foreach、Enumeration输出。

使用时,优先考虑ArrayList。

(虽ArrayList是线程不安全的,在JDK1.2提供,可利用Collections.synchronizedList()将ArrayList包装为线程安全的List)

2)ArrayList与LinkedList的区别

a、ArrayList底层采用数组实现,LinkedList底层采用双链表;

b、ArrayList适用于频繁查找元素的场景,

LinkedList适用于频繁修改元素的场景。

3)ArrayList与Vector(底层实现均为数组)的扩容策略

a、ArrayList:初始化时为空数组;

扩容策略:增加50%(即扩容后容量为当前容量的1.5倍)

源码实现:int newCapacity = oldCapacity + (oldCapacity >> 1);

b、Vector:初始化时容量大小为10;

扩容策略:增加一倍(即扩容后容量为当前容量的2倍),可自定义扩容:设置capacityIncrement,默认为0

源码实现:int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);

动态扩容的本质:调用Arrays.copyOf(elementData, newCapacity)创建新数组,底层使用System提供的一个native静态方法arraycopy():浅复制(对对象引用的复制)

3、子接口Set(20%)(本质为没有value的Map)(对Collection父接口无扩充)

1)实现子类HashSet

无序存储,实际上就是HashMap:底层实现为hashMap

    public HashSet() {
        map = new HashMap<>();
    }


    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

允许存储对象为null,且不能重复;

判断重复依据:hashCode()(返回对象地址的hash码)+equals()

HashSet的使用:

存储JDK提供的类对象

Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("A");//不可重复
set.add(null);//允许为null
for (String string : set) {//乱序
	System.out.println(string);//null A B
}

存储自定义类对象

class Person{
	private String name;
	private int age;
	
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
}

public class Test {

	public static void main(String[] args) {		
		Set<Person> set = new HashSet<>();
		set.add(new Person("张三", 20));
		set.add(new Person("张三", 20));
		set.add(new Person("张三", 18));
		set.add(new Person("张四", 20));
		set.add(null);
		for (Person person : set) {
			System.out.println(person);
		}
                /*null
                  Person [name=张三, age=18]
                  Person [name=张四, age=20]
                  Person [name=张三, age=20] 
                */
	}
}

 2)实现子类TreeSet

有序存储(顺序自定义),实际上为TreeMap;

不允许存储对象为null,且不能重复;

自定义类对象使用TreeSet存储,必须覆写CompareTo接口

判断重复元素:实现CompareTo()接口(Java.lang包),覆写该接口的唯一方法compareTo(T o)方法

public int compareTo(T o);//返回值:大于0:当前对象 > 目标对象

                                                           等于0:当前对象 = 目标对象

                                                           小于0:当前对象 < 目标对象

TreeSet的使用:

存储JDK提供的类对象

Set<String> set = new TreeSet<>();
set.add("D");
set.add("B");
set.add("A");
set.add("A");//不可重复
//set.add(null);//不能为null
for (String string : set) {//有序
	System.out.println(string);//A B D
}

存储自定义类对象 

class Person implements Comparable<Person>{
	private String name;
	private int age;
	
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}

	@Override
	public int compareTo(Person o) {
		if(this.age > o.age) {
			return 1;
		}else if(this.age < o.age) {
			return -1;
		}else {
			return this.name.compareTo(o.name);
		}
	}
	
}

public class Test {

	public static void main(String[] args) {
		//自定义类必须要实现Comparable接口
		Set<Person> set = new TreeSet<>();
		set.add(new Person("张三", 20));
		set.add(new Person("张三", 10));
		set.add(new Person("张三", 18));
		set.add(new Person("张四", 20));
		for (Person person : set) {
			System.out.println(person);
		}
		/*Person [name=张三, age=10]
		  Person [name=张三, age=18]
		  Person [name=张三, age=20]
		  Person [name=张四, age=20] 
		 */
	}

}

二、集合输出的四种形式

1、Iterator迭代器输出(只能从前往后遍历输出)

1)方法:

hasNext():判断是否有下个元素;

next():取得当前元素;

remove():删除当前元素

//迭代器遍历
Iterator<Person> iterator = list.iterator();
while (iterator.hasNext()) {
	System.out.println(iterator.next());	
}

2)迭代器的快速失败(fail-fast)行为

a、是什么?

在遍历一个集合的过程中,当集合数据被修改(并发修改)时,就会抛出java.util.ConcurrentModificationException异常,该异常的抛出就是迭代器的快速失败行为。

b、产生原因:对集合的并发修改

并发修改:当一个或多个线程正在遍历一个集合Collection,此时另一个线程修改了这个集合的内容(增删改)。

源码实现:

首先,在代码中存在一个变量modCount,该变量用来描述当前集合被修改(结构)的次数;

然后,在调用iterator取得迭代器时,存在一个变量expectedModCount,此时被赋值为当前的modCount;

当在迭代器内部发生modCount  != expectedModCount时,迭代器就会发生快速失败行为,抛出异常。

c、会产生该异常的类集:ArrayList、Vector等

d、解决:使用fail-safe类集(CopyOnWriteList、ConCurrentHashMap)(线程安全集合:CopyOnWriteList与JDK1.7之前的ConCurrentHashMap使用Loak锁实现线程安全)

fail-safe机制的内容:任何对集合结构的修改都会在一个复制的集合上进行修改,由于集合根本就没变,所以不会发生并发错误

该机制存在的两个问题:

由于需要复制集合,所以会产生大量的无效对象,开销大;

无法保证读取的数据是目前原数据结构的数据。

2、双向迭代器ListIterator(List接口独有,可以从前往后遍历输出,也可从后往前遍历输出)

方法:

hasNext():判断是否有下个元素;

next():取得当前元素;

remove():删除当前元素;

hasPrevious():判断是否有上个元素;

previous():取得上个元素。

要想从后向前输出,必须先从前往后走一遍

3、枚举输出Enumeration(只有Vector类才有)

4、for-each输出

三、Map集合及其子类

1、Map的特点

一次保存两个对象,针对键值对(K=V)对象的处理

可通过key值找到value

2、常用方法:

put(K key, V value):向Map中追加数据

get(K key):根据key值取得对应的value,没找到返回null

keySet():返回值类型为Set(Key值不能重复)

values():返回值类型为Collection(value可重复)

3、Map集合的内部接口:Entry<K,V>(保存键值对对象)

目的:方便Map集合输出以及元素保存

entrySet():Set<Map.Entry<K,V>>:将Map集合变为Set集合

getKey():取得Key值

getValue():取得value值

代码实现:Map集合的标准输出

Map<Integer, String> map = new HashMap<>();
map.put(1, "123");
map.put(2, "234");
map.put(3, "345");

//Map的标准输出
//将Map->Set
//Entry:interface Entry<K,V>:Map的内部接口
Set<Map.Entry<Integer, String>> set = map.entrySet();
//取得迭代器输出
Iterator<Map.Entry<Integer, String>> iterator = set.iterator();
while(iterator.hasNext()) {
	Map.Entry<Integer, String> tmp = iterator.next();
	System.out.println(tmp.getKey() + " = " +tmp.getValue());
}

4、子类HashMap底层数据结构的实现

k=v:k(Set,可为null,不能重复),v(Collection,可为null,可重复)

线程不安全

基于哈希表的实现

源码解析

1)内部实现:数组+链表

数组被分成一个一个的桶,通过hash值决定键值对在数组的位置;

hash值相同的键值对,以链表的形式存储;

当链表长度超过阈值(默认为8)-1时,将其树化。

static final int TREEIFY_THRESHOLD = 8;

2)构造方法

无参构造:初始化默认负载因子0.75f

static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

 3)putVal()源码

对数组大小的初始化(HashMap采用lazy-load懒加载:在初次使用(put())时对table初始化)

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}

putVal()部分代码:

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

a、可看出,当table为null时,会调用resize()进行初始化数组大小

b、具体键值对在哈希表中的位置取决于下方的运算,避免hash碰撞

i = (n - 1) & hash

 4)resize()源码

初始化时,桶大小为16

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

门限值 = 负载因子*容量

newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

 门限值通常以倍数进行调整

newThr = oldThr << 1;

扩容后将老数组元素拷贝到新数组

Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;

 5)负载因子和容量

预设容量大小需要满足:大于预估元素数量 / 负载因子,同时是2的幂次方

负载因子:不推荐改变

设置超过0.75会明显增加哈希冲突,降低HashMap的性能

设置太小,会导致频繁的扩容,影响访问性能

6)树化

JDK1.8引入:安全问题

当简直对对象冲突时,会放在同一个桶中以链表形式存储,链表过长,严重影响存取性能

同一个桶中的元素个数 >= 7时,会调用treeifyBin尝试树化

static final int TREEIFY_THRESHOLD = 8;
if (binCount >= TREEIFY_THRESHOLD - 1)
          treeifyBin(tab, hash);

容量小于MIN_TREEIFY_CAPACITY(默认为64),会调用resize()进行扩容

否则(else if),才会树化

static final int MIN_TREEIFY_CAPACITY = 64;
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

 当链表被树化后变成红黑树后,元素删除N次后,如果红黑树节点个数 < UNTREEIFY_THRESHOLD(默认6),在下一次resize()后,又会将红黑树变为链表

final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit)

5、子类Hashtable (JDK1.0,第一个存储二元偶对象的类)

k,v均不能为null

线程安全:在增删改等方法上加锁synchronized实现,同Vector

HashMap与Hashtable的区别:

1)Hashtable是早期Java类库(JDK1.0)提供的一个哈希表实现,

同步;

不支持null键和值;

由于同步导致的性能开销,所以已经很少被推荐使用;

2)HashMap:JDK1.2

异步;

支持null键和值;

通常情况下,进行put和get操作,可以达到常数时间的性能,所以它是绝大部分利用键值对存取场景的首选。

6、子类TreeMap

7、子类ConcurrentHashMap

所在包:JUC:java.util.concurrent(所有与并发有关的类均在此包)

1)ConcurrentHashMap产生的原因?

JDK1.5以前效率较低;Collections提供的线程安全包装方法本质上和Hashtable、vector实现线程安全的核心思想一致;

线程安全包装方法在各种修改方法(add、remove、set等方法)中使用内键锁实现同步,没有真正意义下的改进,因此效率较低

2)JDK1.7的实现

分离锁将内部结构分段(Segment),存放的HashEntry数组,hash相同的条目也按照链表存储,在进行并发操作时,只锁条目对应的Segment段,有效避免类似的Hashtable整体同步的问题,大大提高性能

HashEntry内部使用volatile的value字段来保证可见性

构造时,Segment数量由concurrentcyLevel决定,默认为16,必须为2的幂

3)JDK1.8的变化

总体结构上与HashMap非常相似,同样是大的桶数组,虽然内部还有Segment,但仅仅是为了保证序列化的兼容问题,不再有结构上的用处,因此不再使用Segment;

数据存储用volatile来保证可见性;

使用CAS(compare and swap)等操作,在特定场景进行无锁并发操作。

四、属性类Properties的5大常用操作方法

Properties(extends Hashtable)专门做属性文件处理

Java中的资源文件:*.properties,内容保存形式为:"key = value"(字符串形式)

1、设置属性:setProperty(String key, String value);

2、取得属性:getProperty(String key);

根据指定key取得value,没找到返回null

3、取得属性:getProperty(String key, String defaultValue);

根据指定key取得value,没找到返回默认值

4、保存属性到目标终端:store(OutptStream out, String comments);//注释

5、从文件读取属性:load(InputStream in);

代码操作:

Properties properties = new Properties();
properties.setProperty("creator", "zhang");
properties.setProperty("test", "ok");

File file = new File("pro.properties");
try {
	properties.store(new FileOutputStream(file), "test");
} catch (IOException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

//读取文件属性值
try {
	properties.load(new FileInputStream(file));
} catch (IOException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

System.out.println(properties.getProperty("test"));

文件内容: 

#test
#Sat Aug 25 18:57:19 CST 2018
creator=zhang
test=ok

5、(编程题)

现有自定义类Person,其中有age和name属性。
在主方法中使用Person作为key,String作为value进行保存。要求使用Iterator输出Map集合。

class Person{
	private String name;
	private int age;
	
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name:" + name + ", age:" + age + "]";
	}
}

public class Test {
	public static void main(String[] args) {
		
		Map<Person, String> map = new HashMap<>();
		map.put(new Person("zhang", 18), "编号1");
		map.put(new Person("张三", 23), "编号2");
		
		Set<Map.Entry<Person, String>> set = map.entrySet();
		Iterator<Map.Entry<Person, String>> iterator = set.iterator();
		while(iterator.hasNext()) {
			Map.Entry<Person, String> res = iterator.next();
			System.out.println(res.getKey()+ " = " + res.getValue());
		}
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值