集合
集合\数组都是对于多个数据进行存储的结构, 简称java容器.
说明: 此时的存储, 主要是指内存层的存储, 不涉及持久化的存储(,txt, .jpj, .avi ,数据库等等)
数组
优点:
一旦初始化以后, 长度就确定了.
数组一旦确定好, 其元素的类型就确定了 , 我们只能操作指定类型的数据了
缺点:
一旦初始化长度就不能改变
数组的方法非常有限, 增删改查都需要自己写, 非常不方便, 同时效率不高
获取数组中实际元素的个数, 数组没有现成的属性或方法可用
数据存储的特点: 有序, 可重复. 对于无序, 不可重复的需求, 不能满足
集合框架
|------ Collection接口 : 单例集合, 用来存储一个一个对象
|-----List接口: 存储无序的, 可以重复的数组 --> "动态"数组
|---> ArrayList,LinkedList,Vector
|-----Set 接口: 存储无序的, 不开重复的数组 --> 高中讲的"集合"
|---> HashSet,LinkedHashSet,TreeSet
|-------Map接口: 双列集合, 用来存储一对(key-value)一对数据 ---> 高中函数: y = f(x)
key相当于x, value相当于y, 一个key可以对应多个value, 但是一个key不能对应多个value
|----> HashMap, LinkedHashMap, TreeMap, HashTable, Properties
Collection接口的常用方法
例子:
@Test
public void test6() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person(23, "Tom"));
coll.add(new String("Hello"));
// hashCode() 获取当前对象的哈希值
System.out.println(coll.hashCode());
// 集合 --> 数组 toArray()
Object[] objects = coll.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.print(objects[i] + ", ");
}
// (扩展)数组 --> 集合
List<String> strings = Arrays.asList("AA", "BB", "CC");
System.out.println(strings);
List<int[]> ints = Arrays.asList(new int[]{1, 2, 34});
System.out.println(ints); // [[I@3d82c5f3] 这个被当成一个整体进行使用
System.out.println(ints.size());
}
@Test
public void test5() {
Collection coll = new ArrayList(); //这个是有序集合, equals()对顺序有要求
coll.add(123);
coll.add(456);
coll.add(new Person(12, "Bob"));
coll.add(new String("lihua"));
System.out.println(coll);
Collection coll1 = Arrays.asList(123, 456, 789);
// equals(Object obj) 要想返回ture, 要判断当前元素和形参集合需要一样(对于有序和无序集合要求不一样)
System.out.println(coll.equals(coll1));
}
@Test
public void test4() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person(12, "Bob"));
coll.add(new String("lihua"));
Collection coll1 = Arrays.asList(123, 456, 789);
//retainAll() 求交集, 把交集的值给了调用此方法的对象
coll.retainAll(coll1);
System.out.println(coll);
}
@Test
public void test3() {
// remove(Object o) 从当前集合中移除元素o
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person(12, "Bob"));
coll.add(new String("lihua"));
System.out.println(coll.remove(123));
System.out.println(coll);
System.out.println(coll.remove(new Person(23, "bob")));
System.out.println(coll);
// removeAll(Object obj) 从当前集合中移除所以的obj元素, 相当于移除两个集合的交集
Collection coll1 = Arrays.asList(123, 456, 789);
System.out.println(coll.removeAll(coll1));
System.out.println(coll);
}
/**
* 向Collection接口的实现类中添加obj时, 要求obj所在的类需要重写equals()
*/
@Test
public void test2() {
Collection coll = new ArrayList();
coll.add("ABC");
Person p = new Person(12, "Mike");
coll.add(p);
System.out.println(coll.contains(p));
coll.add(new Person(12,"bob"));
System.out.println(coll.contains(new Person(12, "bob")));
coll.add(new String("Tom"));
System.out.println(coll.contains(new String("Tom"))); // String重写了equals()方法
// containsAll(Collection coll) 判断形参coll是否全部存在与当前对象
// 方式一:
// Collection coll2 = new ArrayList();
// coll2.add("ABC");
// coll2.add("www");
// 方式二:
Collection coll2 = Arrays.asList(123, 456); // 多态
System.out.println(coll.containsAll(coll2)); // false
}
@Test
public void test1() {
Collection coll = new ArrayList();
// add(Object e) 将e添加到集合coll中
coll.add("AA");
coll.add("BB");
coll.add(123); // 自动装箱
coll.add(new Date());
// size() 添加元素的个数
System.out.println(coll.size());
// addAll()
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("CC");
coll1.addAll(coll);
System.out.println(coll1.toString()); // 这个是经过重写过得方法
// isEmpty() 是否为空
System.out.println(coll.isEmpty());
// clear() 清空对象,但是不是null
coll.clear();
System.out.println(coll.isEmpty());
System.out.println(coll);// []
}
Iterator方法
注意点:iterator不是容器, 元素还在原来的集合中
注意点:集合元素遍历操作iterator, 主要是用来遍历Collection ,而Map不用iterator来遍历
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person(23, "Tom"));
coll.add(new String("Hello"));
// next()方法, 可以输出对象的内容
/*System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
System.out.println(iterator.next());
// 超过了size()就会报错 NoSuchElementException
// System.out.println(iterator.next());*/
// 方式二: 不推荐
for (int i = 0; i < coll.size(); i++) {
System.out.println(iterator.next());
}
// 方式三, 推荐写法 iterator不是容器, 元素还在原来的集合中
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
iteratoe错误写法
错误方式一:
@Test
public void test4() {
double sin = Math.sin(1.2);
System.out.println(sin);
}
@Test
public void test3() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person(23, "Tom"));
coll.add(new String("Hello"));
Iterator iterator = coll.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
if ("Tom".equals(next)) {
iterator.remove();
}
}
// 需要重置指针
iterator = coll.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
错误方式二:
@Test
public void test2() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person(23, "Tom"));
coll.add(new String("Hello"));
Iterator iterator = coll.iterator();
// 错误写法一
/*if (iterator.next() != null) {
System.out.println(iterator.next());
}*/
// 错误写法二 匿名对象
while (coll.iterator().hasNext()) { // 指针每次都会重置在第一个上面
System.out.println(coll.iterator().next()); // 会不停的输出第一个值: 123
}
foreach循环遍历集合
@Test
public void test1() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new String("Tom"));
coll.add(new Person(23, "Tom"));
// 增强for循环
// 格式: for(被遍历元素的类型 自变量的命名 : 被遍历变量的名字)
for(Object obj: coll) {
System.out.println(obj);
}
int[] ints = {1, 34, 56};
for(int i: ints) {
System.out.println(i);
}
// 面试题
String[] strings = {"MM", "MM", "MM"};
// 方式一: 普通for
for (int i = 0; i < strings.length; i++) {
strings[i] = "GG";
}
// 方式二: foreach
for (String s: strings) { // 这个是把strings的每一个元素赋值给String类型的s, 然后把s修改, 并没有修改strings里面的元素
s = "KK";
}
for (int i = 0; i < strings.length; i++) {
System.out.println(strings[i]);
}
Collection的子接口之一List接口
* |------ Collection接口 : 单例集合, 用来存储一个一个对象
* |-----List接口: 存储无序的, 可以重复的数组 --> "动态"数组
* |-->ArrayList 作为List接口的主要实现类 线程不安全的, 效率高 底层使用Object[] elementData存储
* |-->LinkedList 底层使用双向链表存储, 对于频繁的插入或删除,效率会比ArrayList效率高
* |-->Vector 作为List接口的古老实现类 线程安全的, 效率不高 底层使用Object[] elementData存储
*
*
* 1.0 ArraysList源码分析
* jdk7.0
* ArrayList list = new ArrayList(); // 底层创建了一个长度为10的 Object[] elementData数组
* list.add(10); // 底层 elementData[0] = new Integer(10);
* .......
* list.add(11); // 如果此次添加导致低沉elementData长度不够, 那么就会扩容到原来的1.5倍, 同时将原有的数据复制到新建的数组
*
* 启发: 在开发, 如果明确需要很长的容量, 可以提前指定容量, 避免重复扩容影响效率
* ArrayList list = new ArrayList(int capacity);来创建指定长度
*
* jdk8.0
ArrayList的变化
* ArrayList list = new ArrayList(); // 底层创建了一个长度为0的 Object[] elementData数组 {}
*
* list.add(123); // 第一次调用add方法, 底层才创建一个长度为10的数组
*
* 小结: jdk7的创建方式, 类似于单例设计模式中的饿汉式
* jdk8的创建方式 类似于单例设计模式中的懒汉式
*
2.0
LinkedList源码分析
linkedList list = new LinkList() 内部声明了Node的first 和 last属性, 此时默认值为null
list.add(123); // 将123封装的Node对象之中
其中LinkedList中Node的定义体现了双向链表
3.0
jdk7.0和jdk8.0 的vector源码分许
vector底层都是创键了,一个长度为10的Object[]数组, 扩容默认为2倍
*
*面试题: ArraysList, LinkedList, Vector 三者的异同点
* 相同点 : 都实现了List接口, 都可以存储: 有序 可重复的数组
* 不同点 : 看上面
List接口方法
- void add(int index, Object ele):在index位置插入ele元素
- boolean addAll(int index, Collection eles):从index位置开始将else中的所有元素添加进来
- Object get(int index):获取指定index位置的元素
- int indexOf(Object obj):返回obj在集合中首次出现的位置
- int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
- Object remove(int index):移除指定index位置的元素,并返回此元素
- Object set(int index, Object ele):设置指定index位置的元素为ele
- List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex 位置的子集合
常用方法
- 增 add()
- 删 remove(int index) / remove(Object obj)
- 改 set(int index, Object ele)
- 查 get(int index)
- 插 add (int index, Object ele)
- 长度 size()
- 遍历 ① Iterator 迭代器遍历
- ② 增强for循环
- ③ 普通for循环
测试类
@Test
public void test1() {
ArrayList list = new ArrayList();
list.add("AA");
list.add(new Person(23, "Tom"));
list.add("AA");
list.add(123);
System.out.println(list);
// void add(int index, Object ele):在index位置插入ele元素
list.add(1, new Person(12, "Jack"));
System.out.println(list);
// boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素加入进来
List integers = Arrays.asList(1, 2, 33);
list.add(2, integers);
System.out.println(list);
System.out.println(list.size()); // 如果list调用add()那么, 结果为5
}
@Test
public void test2() {
ArrayList list = new ArrayList();
list.add("AA");
list.add(new Person(23, "Tom"));
list.add("AA");
list.add(123);
list.add(323);
System.out.println(list);
// int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置, 找不到返回-1
System.out.println(list.indexOf(123)); // 找到了3
System.out.println(list.indexOf(321)); // 没找到-1
System.out.println(list.lastIndexOf(323)); // 从后往前找
// Object get(int index):获取指定index位置的元素
System.out.println(list.get(0));
// Object remove(int index):移除指定index位置的元素,并返回此元素
Object remove = list.remove(2);
System.out.println(remove);
System.out.println(list);
// Object set(int index, Object ele):设置指定index位置的元素为ele,返回被修改的值
Object qiu = list.set(1, "QIU");
System.out.println(qiu);
System.out.println(list);
// List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex左闭右开的List, 不会修改原来的list
List list1 = list.subList(0, 3);
System.out.println(list1);
}
@Test
public void test3() {
ArrayList list = new ArrayList();
list.add("AA");
list.add(new Person(23, "Tom"));
list.add("AA");
list.add(123);
list.add(323);
// 方式一
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("*******************");
// 方式二
for(Object obj : list) {
System.out.println(obj);
}
System.out.println("*******************");
// 方式三
for (int i = 0; i < list.size() ;i++) {
System.out.println(list.get(i));
}
}
Person类
public class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
System.out.println("Person equals()....");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = age;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
Collection的子接口之一Set接口
* |------ Collection接口 : 单例集合, 用来存储一个一个对象
*
* |-----Set 接口: 存储无序的, 不开重复的数组 --> 高中讲的"集合"
* |--> HashSet :set接口的主要实现类, 线程不安全, 可以存储null值
* |-->LinkedHashSet :作为HashSet的子类 可以按照添加的顺序进行变量
* |-->TreeSet : 使用红黑数组, 添加的元素必须是同一类中对象, 可以按照对象的某些属性进行排序
* 1. set中没有定义额外的方法, 都是Collection中定义的方法
*
* 2. 要求:
* 向set中添加的数据,一定要重写hashCode()和 equals()方法 即相同的对象一定要有相同的哈希值
* 重写两方法的小技巧: 对象中用作equals()方法比较Field, 都应该用来计算hashCode值;
特征:
HashSet
无序性 : 不等于随机性. 存储的数据在底层数组中并非按照索引顺序添加的, 而是根据数据的哈希值确定的
不可重复性 : 保证元素添加的元素按照equals()来判断, 不能返回true; 即相同的元素只能添加一个
源码分析
问题HashSet如何保证没有重复的元素这一特性 ?
以HashSet为例
我们向HashSet中添加a, 首先调用元素啊所在类的hashCode()方法, 计算元素a的哈希值, 通果过默写算法把哈希值转化为HashSet底层数组中的存放位置(即为索引位置)
判断此位置上是否有元素:
若没有元素, 则添加成功; ---> 情况一
若此位置有其他元素b(或以链表形式存在多个元素), 则比较元素a与元素b的hash值:
若hash值不相同, 则元素a添加成功 --->情况二
若hash值相同, 进而判断a类中的equals()方法,看equals()的返回值
如果返回值为true, 那么添加失败
如果返回值为false, 那么添加成功 --->情况三
对于,情况二和情况三来说, 元素a与已存在指定索引位置上的数据以链表方式存储
对于以链表形式存放的顺序:
jdk7.0: 元素a放到数组中, 指向原来的元素
jdk8.0: 原来的元素在数组中指向元素a
总结: 7上8下
HashSet底层是数组 + 链表
链表形式存放的顺序

LinkedHashSet
LinkedHashSet作为HashSet的子类, 再添加数据时,每个数据还维护两个引用记录 前一个数据和后一个数据.
优点: 对于频繁的遍历效率比HashSet效率高
TreeSet
* 1.向TreeSet中添加的数据, 要求是相同类的对象
* 2.两种排序方式: 自然排序 和 定制排序
* 3. 自然排序中, 比较两个对象是否为相同标准为: CompareTo()返回0, 不再是equals().
4. 定制排序中, 比较两个对象是否为相同标准为: Compare()返回0, 不再是equals().
-
向TreeSet中添加的数据, 要求是相同类的对象
底层是用红黑树

自定义的类需要继承Comparable接口, 重写comapreTo()方法
public class User implements Comparable{
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Object o) {
if (o instanceof User) {
User user = (User) o;
if (user.name == this.name) {
return user.age - this.age;
} else {
return user.name.compareTo(this.name);
}
}
throw new RuntimeException("输入的对象不符合规范");
}
}
测试方法
@Test
public void test() {
set.add(new User(new String("Tom"), 12));
set.add(new User("Jack", 15));
set.add(new User("Mick", 10));
set.add(new User(new String("Tom"), 99));
System.out.println(set);
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
定制排序
@Test
public void test2() {
// 定制排序
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User) {
User user1 = (User) o1;
User user2 = (User) o2;
return Integer.compare(user1.getAge(), user2.getAge());
}
throw new RuntimeException("请输入正确的类型");
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom", 12));
set.add(new User("Jack", 15));
set.add(new User("Mick", 10)); // 这个和下面的年龄一只会添加一个, 这个利用重写Compare()来比较是否相同
set.add(new User("Micks", 10));
System.out.println(set);
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
结果:
[User{name='Mick', age=10}, User{name='Tom', age=12}, User{name='Jack', age=15}]
User{name='Mick', age=10}
User{name='Tom', age=12}
User{name='Jack', age=15}
链表图示
// 简单的链表演示
public class LinkListTest {
public static void main(String[] args) {
// 模拟一个简单的双向链表
Node jck = new Node("Jck");
Node tom = new Node("Tom");
Node hsp = new Node("hsp");
jck.next = tom;
tom.next = hsp;
hsp.pre = tom;
tom.pre = jck;
Node first = jck;
Node last = hsp;
// 输出数组中的元素
// 从头遍历
System.out.println("==从头遍历==");
while (true) {
if (first == null) {
break;
}
System.out.println(first);
first = first.next; // 把指针向后移
}
// 从后遍历
/*System.out.println("==从后遍历==");
while (true) {
if (last == null) {
break;
}
System.out.println(last);
last = last.pre; // 指针向前移动
}*/
}
}
// 定义一个双向链表节点的对象
class Node {
public Object itm; // 真正的数据
public Node next; // 指向下一个节点
public Node pre; // 指向上一个节点
public Node(Object name) {
this.itm = name;
}
@Override
public String toString() {
return "Node name = " + itm;
}
}