JAVA集合

JAVA集合

由若干个确定的元素所构成的整体就是集合

数据就是集合

java中的数组就是符合这个条件的 多个确定的元素的一个整体
既然有了数组为什么还要提供其他的集合呢。PHP里就只有数组的概念

原因:数组有缺点如下: 简单说就是必须确定数组的长度,显然不是很实用,获取方式不方便
1. 数组初始化后大小不可变;
2. 数组只能按索引顺序存取。

为了解决这两个问题:要出现一个可变长的数组 和 一个利于存取的 并且提供更多特性的东西 所以出现了集合

Collection

集合在java标准库的java.util包下
Collection是除map外其他所有包的根接口
集合一共分为三种:
List  一种有序列表的集合
Set   一种保证没有重复元素的集合
Map   一种通过键值(key-value)查找的映射表集合

集合的JAVA设计原理:
1. 实现了接口和类分离 例如,有序表的接口是List,具体的实现类有ArrayList,LinkedList等,
2. 支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素
3. Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的

已经将被废弃的类,不要使用
Hashtable:一种线程安全的Map实现;
Vector:一种线程安全的List实现;
Stack:基于Vector实现的LIFO的栈。
还有一小部分接口是遗留接口,也不应该继续使用:
Enumeration<E>:已被Iterator<E>取代。

List

list是一种有序列表.
List有两种实现:ArrayList 和 LinkedList
                     ArrayList	    LinkedList
获取指定元素	          速度很快	        需要从头开始查找元素
添加元素到末尾	      速度很快	        速度很快
在指定位置添加/删除	  需要移动元素	    不需要移动元素
内存占用	                少	            较大
内存状态:
┌───┬───┬───┬───┬───┬───┐
│ A │ B │   │ C │ D │ E │
└───┴───┴───┴───┴───┴───┘

        ┌───┬───┐   ┌───┬───┐   ┌───┬───┐   ┌───┬───┐
HEAD ──>│ A │ ●─┼──>│ B │ ●─┼──>│ C │ ●─┼──>│ D │   │
        └───┴───┘   └───┴───┘   └───┴───┘   └───┴───┘
通常情况下,我们总是优先使用ArrayList。

所以重点介绍ArrayList
在末尾添加一个元素:void add(E e)
在指定索引添加一个元素:void add(int index, E e)
删除指定索引的元素:int remove(int index)
删除某个元素:int remove(Object e)
获取指定索引的元素:E get(int index)
获取链表大小(包含元素的个数):int size()


创建List的第三种方式:
List<Integer> list = List.of(1, 2, 5);


遍历List
第一种是:for循环,不推荐这么使用。原因:1. 代码复杂 2. get(int)方法只有ArrayList的实现是高效的换成LinkedList后,索引越大,访问速度越慢
第二种: 我们要始终坚持使用迭代器Iterator来访问List。
Iterator本身也是一个对象,这个对象有两个方法hasNext() next()
这个对象由List的实例调用iterator()方法的时候创建的
Iterator it = list.iterator()
例如:
List<String> list = List.of("iterator", "for", "foreach");
for (Iterator<String> it = list.iterator(); it.hasNext();) {
    System.out.println(it.next);
}
是不是这样代码也很啰嗦。没关系java提供了foreach
for(String s : list)

实际上,只要实现了Iterable接口的集合类都可以直接用for each循环来遍历,
Java编译器本身并不知道如何遍历集合对象,
但它会自动把for each循环变成Iterator的调用,
原因就在于Iterable接口定义了一个Iterator<E> iterator()方法,
强迫集合类必须返回一个Iterator实例

List和Array相互转换

List转Array三种方式一个方法toArray
1. toArray()直接返回一个Object[]数组
List<String> list = List.of("apple", "pear", "banana");
Object[] array = list.toArray();
for (Object s : array) {
    System.out.println(s);
}
2. toArray(T[])传入一个类型相同的Array,List内部自动把元素复制到传入的Array中
Integer[] array = list.toArray(new Integer[list.size()]);
3. Integer[] array = list.toArray(Integer[]::new);

Array转变成List
Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);

对于JDK 11之前的版本,可以使用Arrays.asList(T...)方法把数组转换成List。
Arrays.asList()


要注意的是,List不一定就是ArrayList或者LinkedList,
因为List只是一个接口,如果我们调用List.of(),它返回的是一个只读List:
对只读List调用add()、remove()方法会抛出UnsupportedOperationException。

编写equals方法

为什么要编写equals  当List调用contains,indexOf的时候可能找不到。是因为对象无法用==来判断是否相等,这个时候要覆写equals
在哪编写equals      在进行比较的对象里覆写。一般编辑器都有快捷键自动生成代码
例子:
List<String> list = List.of("A", "B", "C");
System.out.println(list.contains(new String("C"))); // true  因为String里已经覆写了
System.out.println(list.indexOf(new String("C"))); // 2  因为String里已经覆写了

List<Person> list = List.of(
    new Person("Xiao Ming"),
    new Person("Xiao Hong"),
    new Person("Bob")
);
System.out.println(list.contains(new Person("Bob"))); // 如果没有覆写就是false

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }

    public boolean equals(Object o) {
        if (o instanceof Person) {
            Person p = (Person) o;
            return Objects.equals(this.name, p.name) && this.age == p.age;
        }
        return false;
    }
}

使用Map

Map这种键值(key-value)映射表的数据结构  作用就是能高效通过key快速查找value(元素)
实现是HashMap
boolean containsKey(K key)
get put

遍历Map
for (Map.Entry<String, Integer> entry : map.entrySet()) {
for (String key : map.keySet()) {

注意:遍历时map是无序的

使用EnumMap

Map 属于key value格式的
当key是枚举类型是,java提供了一个EnumMap,
为什么要单独提供一个类,是为了是效率更高:
它在内部以一个非常紧凑的数组存储value
并且根据enum类型的key直接定位到内部数组的索引
并不需要计算hashCode(),不但效率最高,而且没有额外的空间浪费

Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class);
map.put(DayOfWeek.MONDAY, "星期一");
map.put(DayOfWeek.TUESDAY, "星期二");
map.put(DayOfWeek.WEDNESDAY, "星期三");
map.put(DayOfWeek.THURSDAY, "星期四");
map.put(DayOfWeek.FRIDAY, "星期五");
map.put(DayOfWeek.SATURDAY, "星期六");
map.put(DayOfWeek.SUNDAY, "星期日");
System.out.println(map);
System.out.println(map.get(DayOfWeek.MONDAY));

TreeMap

TreeMap的key是有序的,HashMap是无序的

       ┌───┐
       │Map│
       └───┘
         ▲
    ┌────┴─────┐
    │          │
┌───────┐ ┌─────────┐
│HashMap│ │SortedMap│
└───────┘ └─────────┘
               ▲
               │
          ┌─────────┐
          │ TreeMap │
          └─────────┘
SortedMap保证遍历时以Key的顺序来进行排序。
例如,放入的Key是"apple"、"pear"、"orange",
遍历的顺序一定是"apple"、"orange"、"pear",
因为String默认按字母排序:

Map<String, Integer> map = new TreeMap<>();
map.put("orange", 1);
map.put("apple", 2);
map.put("pear", 3);
for (String key : map.keySet()) {
    System.out.println(key);
}
// apple, orange, pear

因为是有序的所以key必须实现Comparable接口
上边的例子是String类型的key已经实现了
如果是自己定义的类,
第一种方法就是就要实现Comparable接口
第二种方法就是
Map<Person, Integer> map = new TreeMap<>(new Comparator<Person>() {
    public int compare(Person p1, Person p2) {
        return p1.name.compareTo(p2.name);
    }
})
map.put(new Person("Tom"), 1);
map.put(new Person("Bob"), 2);
map.put(new Person("Lily"), 3);
for (Person key : map.keySet()) {
    System.out.println(key);
}
// {Person: Bob}, {Person: Lily}, {Person: Tom}
System.out.println(map.get(new Person("Bob"))); // 2
看到了吗 返回的是2,说明找到了怎么找的呢,
不是HashMap那种equals和hashcode方式了
因为是有序的,所以是用过compare的比较name值找到的。如果compare不返回0的话就肯定找不到啊

使用Properties

读文件或读流
Properties props = new Properties();
//从文件读
String f = "setting.properties";
props.load(new java.io.FileInputStream(f));
//读流
props.load(getClass().getResourceAsStream("/common/setting.properties"));
//从内存读
String settings = "# test" + "\n" + "course=Java" + "\n" + "last_open_date=2019-08-07T12:35:01";
ByteArrayInputStream input = new ByteArrayInputStream(settings.getBytes("UTF-8"));
props.load(input);
//如上可以多次使用load,读多个文件

写文件
Properties props = new Properties();
props.setProperty("url", "http://www.liaoxuefeng.com");
props.setProperty("language", "Java");
props.store(new FileOutputStream("C:\\conf\\setting.properties"), "这是写入的properties注释");

编码
早期版本的Java规定.properties文件编码是ASCII编码(ISO8859-1),
如果涉及到中文就必须用name=\u4e2d\u6587来表示,非常别扭。
从JDK9开始,Java的.properties文件可以使用UTF-8编码了。

props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));

Java集合库提供的Properties用于读写配置文件.properties。.properties文件可以使用UTF-8编码。
可以从文件系统、classpath或其他任何地方读取.properties文件。
读写Properties时,注意仅使用getProperty()和setProperty()方法,不要调用继承而来的get()和put()等方法。因为Hashtable已经废弃

set

我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set

将元素添加进Set<E>:boolean add(E e)
将元素从Set<E>删除:boolean remove(Object e)
判断是否包含元素:boolean contains(Object e)

Set实际上相当于只存储key、不存储value的Map。我们经常用Set用于去除重复元素

HashSet是无序的,因为它实现了Set接口,并没有实现SortedSet接口;
TreeSet是有序的,因为它实现了SortedSet接口。
使用TreeSet和使用TreeMap的要求一样,添加的元素必须正确实现Comparable接口,
如果没有实现Comparable接口,那么创建TreeSet时必须传入一个Comparator对象
Set<MyObj> set = new TreeSet<>(){new Comparator<MyObj>() {
 public int compare(MyObj p1, MyObj p2) {
     return p1.name.compareTo(p2.name);
 }
}

queue

队列 跟超时排队买东西,后来的只能在队尾加进去

获取队列长度:size()
boolean add(E) (这个添加失败抛异常)/ boolean offer(E)(这个添加失败返回false):添加元素到队尾
E remove(这个娶不到抛出异常)/E poll() 这个返回为null:                                                 获取并删除
E element()/E peek():                                                获取但不删除 

注意:不要把null添加到队列中,否则poll()方法返回null时,很难确定是取到了null元素还是队列为空。

// 这是一个List:
List<String> list = new LinkedList<>();
// 这是一个Queue:
Queue<String> queue = new LinkedList<>();

PriorityQueue

PriorityQueue和Queue的区别在于,它的出队顺序与元素的优先级有关,
对PriorityQueue调用remove()或poll()方法,返回的总是优先级最高的元素

Queue<User> q = new PriorityQueue<>(new UserComparator());
class UserComparator implements Comparator<User> {
    public int compare(User u1, User u2) {
        if (u1.number.charAt(0) == u2.number.charAt(0)) {
            // 如果两人的号都是A开头或者都是V开头,比较号的大小:
            return u1.number.compareTo(u2.number);
        }
        if (u1.number.charAt(0) == 'V') {
            // u1的号码是V开头,优先级高:
            return -1;
        } else {
            return 1;
        }
    }
}

Deque

允许两头都进,两头都出,这种队列叫双端队列(Double Ended Queue),学名Deque
                            Queue	                    Deque
添加元素到队尾	    add(E e) / offer(E e)	    addLast(E e) / offerLast(E e)
取队首元素并删除	    E remove() / E poll()	    E removeFirst() / E pollFirst()
取队首元素但不删除	E element() / E peek()	    E getFirst() / E peekFirst()
添加元素到队首	        无	                    addFirst(E e) / offerFirst(E e)
取队尾元素并删除	        无	                    E removeLast() / E pollLast()
取队尾元素但不删除	    无	                    E getLast() / E peekLast()

对于添加元素到队尾的操作,Queue提供了add()/offer()方法,而Deque提供了addLast()/offerLast()方法。
添加元素到对首、取队尾元素的操作在Queue中不存在,在Deque中由addFirst()/removeLast()等方法提供。

注意到Deque接口实际上扩展自Queue:

public interface Deque<E> extends Queue<E> {
    ...
}

Deque<String> d2 = new LinkedList<>();
d2.offerLast("z");

LinkedList全能小助手

Deque<String> d2 = new LinkedList<>();
Queue<String> d2 = new LinkedList<>();
List<String> d2 = new LinkedList<>();

Stack(栈)

栈(Stack)是一种后进先出(LIFO:Last In First Out)的数据结构

把元素压栈:push(E);
把栈顶的元素“弹出”:pop(E);
取栈顶元素但不弹出:peek(E)。

在Java中,我们用Deque可以实现Stack的功能:
当我们把Deque作为Stack使用时,注意只调用push()/pop()/peek()方法,
不要调用addFirst()/removeFirst()/peekFirst()方法,这样代码更加清晰。

使用Iterator

public class Main {
    public static void main(String[] args) {
        ReverseList<String> rlist = new ReverseList<>();
        rlist.add("Apple");
        rlist.add("Orange");
        rlist.add("Pear");
        for (String s : rlist) {
            System.out.println(s);
        }
    }
}

class ReverseList<T> implements Iterable<T> {

    private List<T> list = new ArrayList<>();

    public void add(T t) {
        list.add(t);
    }

    @Override
    public Iterator<T> iterator() {
        return new ReverseIterator(list.size());
    }

    class ReverseIterator implements Iterator<T> {
        int index;

        ReverseIterator(int index) {
            this.index = index;
        }

        @Override
        public boolean hasNext() {
            return index > 0;
        }

        @Override
        public T next() {
            index--;
            return ReverseList.this.list.get(index);
        }
    }
}

使用Collections

创建空集合

创建空List:List<T> emptyList()
创建空Map:Map<K, V> emptyMap()
创建空Set:Set<T> emptySet()
要注意到返回的空集合是不可变集合,无法向其中添加或删除元素。
List<String> list1 = List.of();
List<String> list2 = Collections.emptyList();
以上这两个空集合是等价的

创建单元素集合

Collections提供了一系列方法来创建一个单元素集合:
创建一个元素的List:List<T> singletonList(T o)
创建一个元素的Map:Map<K, V> singletonMap(K key, V value)
创建一个元素的Set:Set<T> singleton(T o)
要注意到返回的单元素集合也是不可变集合,无法向其中添加或删除元素。

排序

import java.util.*;
public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add("pear");
        list.add("orange");
        Collections.sort(list);
        System.out.println(list);
    }
}

洗牌
Collections.shuffle(list);

不可变集合
Collections还提供了一组方法把可变集合封装成不可变集合:
封装成不可变List:List<T> unmodifiableList(List<? extends T> list)
封装成不可变Set:Set<T> unmodifiableSet(Set<? extends T> set)
封装成不可变Map:Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> m)

import java.util.*;
public class Main {
    public static void main(String[] args) {
        List<String> mutable = new ArrayList<>();
        mutable.add("apple");
        mutable.add("pear");
        // 变为不可变集合:
        List<String> immutable = Collections.unmodifiableList(mutable);
        // 立刻扔掉mutable的引用:
        mutable = null;
        System.out.println(immutable);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰明子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值