目录
一、集合
1.1集合的架构
1.2集合框架的作用
对象用于封装特有数据,对象多了需要存储,如果对象的个数不确定,数组无法满足。 就使用集合容器进行存储。集合容器因为内部的数据结构不同,有多种具体容器。 不断的向上抽取,就形成了集合框架。 框架的顶层是Collection接口,定义了集合框架中共性的方法。
1.3集合框架的特点
1)集合、数组都是存储对象的容器,但是集合可以存储不同的类型,只要是对象类型就可以存进集合框架中
2)集合中不可以存储基本数据类型
3)集合的长度是可变的。
二、List
2.1 简易写List方法
public class ArrayListTet {
//创建一个长度为10的对象数组
private Object[] arrObj = new Object[10];
//数组长度
private int size = 0;
//*************添加*************
public void add(Object o){
//长度为10的数组 最高下标是arr.length-1;
if(size< arrObj.length){
arrObj[size]=o;//如果数组没满,将对象添加进该数组
}else{
//此时数组的长度不够,需要对数组进行扩容
int oldLength = arrObj.length;
int newLength = oldLength + (oldLength >> 1);//向右移一位(二进制)
//数组扩容时,扩容到大约1.5倍(/2之后,偶数刚好是1.5,奇数只取整数部分,所以会小1)
Object[] objects = Arrays.copyOf(arrObj,newLength);
arrObj=objects;
arrObj[size]=o;//如果数组没满,将对象添加进该数组
}
size++;//将size++
}
//*****获取数组中内容的长度********
public int size(){
return size;
}
//*********根据索引取值***************
//第一种方法
public Object get (int index){
Object o = null;
try{//如果索引大于当前数组的长度,就会产生异常
o = arrObj[index];
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("数组下标越界");
}finally {
return o;
}
}
//第二种方法
/* public Object get(int index){
Object o = null;
if (index >= size){//如果索引大于当前数组中内容的长度,就会产生异常
System.out.println("数组下标越界");
throw new ArrayIndexOutOfBoundsException();
}else {
o = arrObj[index];
return o;
}
}*/
//*********根据元素获取索引**********找到返回索引,找不到返回-1
public int indexOf(Object o){
int index = -1;//如果数组中没有该值,返回-1
for (int i = 0; i < arrObj.length; i++) {
//equals对于引用数据类型比较的是 内存地址
if (o.equals(arrObj[i])){//如果arrObj[i]处的元素与o的元素相同
index = i;
break;
}
}
return index;
}
//*********根据索引删除数据,并返回删除的值**************
public Object remove(int index){
Object o = null;
if (index>0 && index< arrObj.length-1){
for (int i = index; i < arrObj.length-1; i++) {//从索引处开始,让下一位替代索引位,让下下一位替代下一位
o = arrObj[index];
arrObj[i] = arrObj[i+1];//i最大为arrObj.length-2;
}
size--;
}
return o;
}
//************根据元素的值进行删除*********************
public void remove(Object o){
//获取该值的索引
int index = indexOf(o);//调用上面的根据元素获取索引
remove(index);//调用上一个根据索引删除数据
}
//**********根据索引进行赋值*************
public void set(int index , Object o){
//需要在索引的范围之内进行赋值
if (index<=arrObj.length-1 && index >=0){
arrObj[index]=o;
}
}
}
2.2ArrayList类
ArrayList要跟Array数组相关。
ArrayList的底层就是数组。
特点:
查找快,插入删除慢(因为会引起其他元素位置改变)
自动扩容(我们只管存取即可 如果长度不够 底层会自动的扩容)
可以存储所有对象(对象类型 或者说是Object的子类 )
可以存储null
存储的数据是有序的 ( 取出的顺序就是存储的顺序 )
数据可以重复
2.2.1 ArrayList的常用方法
法名 | 含义 |
---|---|
size() | 数组中数据的个数并不是长度 |
add(E e) | 追加 |
get(index) | 根据下标获取值 |
isEmpty() | 判断ArrayList中是否有数据 |
indexOf(数据 ) | 获取aa在ArrayList中首次出现的索引,有返回,没有-1 |
lastIndexOf(数据) | 返回数据最后一次出现的索引 |
contains(数据) | 判断数据在ArrayList中是否存在 |
remove(index)/remove(obj) | 根据下标删除,或者直接删除数据 |
toArray() | 将List转换成数组 |
clear() | 清空List列表 |
set(索引,“数据”) | 将指定索引的数据修改 |
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();//最大值是2^31-1
//add()方法 这里的不是基本数据类型,自动装箱变成包装类
arrayList.add(12);
arrayList.add("ab");
arrayList.add('a');
arrayList.add('x');
arrayList.add(124);
arrayList.add(123);
//size()
// System.out.println(arrayList.size());
//根据索引(下标)取值
// System.out.println(arrayList.get(1));
//根据值找索引,可以返回多个相同值的索引
// System.out.println(arrayList.indexOf('a'));
//返回值的最后一次出现的索引
// System.out.println(arrayList.lastIndexOf('a'));
//根据索引删除该值,并且返回该值的内容
// System.out.println(arrayList.remove(2));
// System.out.println(arrayList.size());
// System.out.println(arrayList.indexOf(2));
//根据值删除
// arrayList.remove(124);
//判断ArrayList中是否有数据
// boolean empty = arrayList.isEmpty();
// System.out.println(empty);
//将List转换成数组
// Object[] objects = arrayList.toArray();
// System.out.println(objects.toString());//[Ljava.lang.Object;@1b6d3586
//清空List列表
// arrayList.clear();
// System.out.println(arrayList.size());//0
}
2.2.2遍历
1) 普通for循环
for (int i = 0; i < list1.size(); i++) {
System.out.println(list1.get(i));
}
2)增强for循环
for (Object o : list1) {
System.out.println(o);
}
3)迭代
迭代器(iterator)有时又称光标(cursor)是程序设计的软件设计模式,可在容器对象(container,例如链表或数组上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。
// @SuppressWarnings({"all"})//关闭警告
Iterator iterator = arrayList.iterator();
//hasNext() :如果迭代具有更多元素,则返回 true 。
while(iterator.hasNext()){//判断是否还有下一个数据
//next() :返回迭代中的下一个元素。
Object next = iterator.next();
System.out.println(next);
}
扩容原理:
ArrayList其实就是数组,创建的时候给它一个默认的长度10,当需要进行扩容的时候长度是变为原来的1.5倍左右。长度的最大值只能是int的最大值-1。
2.3LinkedList
链表是最简单的动态数据结构,数据存储在节点(Node)中,其节点的数据结构如下:
class Node{
E e;//数据存储的地方
Node next;//也是一个节点,他指向当前节点的下一个节点
}
2.3.1链表与数组的对比
链表 | 数组 |
---|---|
动态数据结构 | 静态数据结构 |
无需关心创建的空间是否太大 | 需要在设计时初始化使用的长度 |
从前往后查找索引的值 | 根据索引直接得到值 |
有序,查找慢,添加删除快 | 有序,查找快,添加删除慢 |
2.3.2链表的分类
1)单向链表
单向链表也叫单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
2)单向循环列表
单链表的一个变形是单向循环链表,链表中最后一个节点的next
域不再为null
,而是指向链表的头节点。
3)双向链表
一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
4)双向循环链表
双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。而LinkedList就是基于双向循环链表设计的。
2.3.3链表的方法使用
这里使用的是LinkedList,LinkedList的底层是双向循环链表,是线程不安全的。
类名 | LinkedList |
---|---|
成员方法 | 1.public void clear():清空链表 2.publicboolean isEmpty():判断链表是否为空,是返回true,否返回false 3.public int length():获取链表中元素的个数 4.public T get(int i):读取并返回链表中的第i个元素的值 5.public void insert(T t):往链表中添加一个元素; 6.public void insert(int i,T t):在链表的第i个元素之前插入一个值为t的数据元素。 7.public T remove(int i):删除并返回链表中第i个数据元素。 8.public int indexOf(T t):返回链表中首次出现的指定的数据元素的位序号,若不存在,则返回-1 |
成员内部类 | private class Node:结点类 |
成员变量 | 1.private Node head:记录首结点 2.private int N:记录链表的长度 |
public static void main(String[] args) {
//LinkedList是基于双向循环链表的,是线程不安全的。
LinkedList<Object> linkedList = new LinkedList<>();
//添加指定元素到链表末尾
linkedList.add("a");
linkedList.add("q");
linkedList.add("w");
linkedList.add("e");
linkedList.add("r");
linkedList.add("t");
linkedList.add("y");
//添加指定元素到链表指定位置
linkedList.add(5,23432151);
//查询数组长度
System.out.println("当前链表的长度是:"+linkedList.size());
//查询索引处的值
System.out.println("该索引处的值是:"+linkedList.get(2));
//对列表进行遍历
for (Object o : linkedList) {
System.out.println(o);
}
//清空链表
linkedList.clear();
}
LinkeList由于使用了链表的结构,因此不需要维护容量的大小 , 在List的尾端插入数据与在任意位置插入数据是一样的,不会因为插入的位置靠前而导致插入的方法性能降低。
ArrayList是基于数组实现的,而数组是一块连续的内存空间,如果在数组的任意位置插入元素,必然导致在该位置后的所有元素需要重新排列,因此,其效率相对会比较低。
2.3.4链表的遍历
在JDK1.5之后,至少有3中常用的列表遍历方式:forEach操作,迭代器和for循环。
对于链表,简单for循环的执行时间是很长的,对于链表,应尽量使用foreach和迭代器遍历。
public static void main(String[] args) {
List list = addArr();
System.out.println("这是ArrayList");
timeTest(list);
List addlink = addlink();
System.out.println("这是linkedList");
timeTest(addlink);
}
public static void timeTest(List list){
long start = System.currentTimeMillis();
Object x ;
for (Object o : list) {
x = o;
}
long end = System.currentTimeMillis();
System.out.println("foreach花费时间"+(end - start)+"ms");
long start1 = System.currentTimeMillis();
Iterator iterator = list.iterator();
while (iterator.hasNext()){
x = iterator.next();
}
long end1 = System.currentTimeMillis();
System.out.println("iterator花费时间"+(end1 - start1)+"ms");
long start2 = System.currentTimeMillis();
int size = list.size();
for (int i = 0; i < size; i++) {
x = list.get(i);
}
long end2 = System.currentTimeMillis();
System.out.println("for花费时间"+(end2 - start2)+"ms");
}
public static List addlink(){
LinkedList linkedList = new LinkedList();
for (int i = 0; i < 100000; i++) {
linkedList.add(i);
}
return linkedList;
}
public static List addArr(){
ArrayList arrayList = new ArrayList();
arrayList.ensureCapacity(100000);
for (int i = 0; i < 100000; i++) {
arrayList.add(i);
}
return arrayList;
}
2.4vector
ArrayList和Vector实现原理都一样,都是基于数组的。使用的是一片连续的内存空间。
vector与ArrayList的区别
vector | ArrayList |
---|---|
线程安全,效率低 | 线程不安全,效率高 |
扩容2倍 | 扩容1.5倍左右 |
三、泛型
确定存储数据的类型。 列表中只能存储固定的类型。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//限定当前的集合中只能存储String类型的数据;
list.add("142");
list.add("142");
System.out.println(list.get(1));
//集合里面添加Person
List<Person> people = new ArrayList<>();
//第一种
Person person1 = new Person("lwl",18);
people.add(person1);
//第二种
people.add(new Person("lll",23));
System.out.println(people.get(0).getName());
//集合里面添加List<Person>类型的数据
List<List<Person>> per = new ArrayList<>();
List<Person> personList1 = new ArrayList<>();
Person person = new Person("lwl",23);
personList1.add(person);
per.add(personList1);
System.out.println(per.get(0).get(0).getName());
}
四、set接口
HashSet集合存储顺序无序,不可以保存重复元素;
TreeSet是可以保持自然顺序(12数字顺序或abc字母顺序)的,也可以保持定义的比较器比较结果顺序;
LinkedHashSet维护的是一个双重列表,能保持元素添加时的顺序。
4.1HashSet
1)可以存储任意对象类型的数据 包括null。
2)无序(存储时的顺序跟遍历时的顺序不一定一样)
3)数据不能重复(自动去重)
4)数据结构跟List不同,没有下标
5)查找慢,插入删除快。(插入和删除不会引起元素位置改变。红黑二叉树)
4.1.1HashSet方法
返回值类型 | 方法及其描述 |
---|---|
boolean | add(E e) 将指定的元素添加到此集合(如果尚未存在)。 |
void | clear() 从此集合中删除所有元素。 |
Object | clone() 返回此 HashSet 实例的浅层副本:元素本身不被克隆。 |
boolean | contains(Object o) 如果此集合包含指定的元素,则返回 true 。 |
boolean | isEmpty() 如果此集合不包含元素,则返回 true |
Iterator | iterator() 返回此集合中元素的迭代器。 |
boolean | remove(Object o) 如果存在,则从该集合中删除指定的元素。 |
int | size() 返回此集合中的元素数(其基数)。 |
Spliterator | spliterator() 在此集合中的元素上创建late-binding和故障快速 Spliterator 。 |
public void main(String[] args) {
//实现set接口
HashSet set = new HashSet();
//在集合末尾添加指定元素
set.add("23");
set.add(453523);
set.add(634);
set.add(654);
set.add(14324321);
//判断此集合中是否包含指定的元素
// boolean contains = set.contains("23");
// System.out.println(contains);
//获取集合中元素的个数
// int size = set.size();
// System.out.println(size);
//删除数组中指定元素,删除成功返回boolean型变量
// boolean remove = set.remove(634);
// System.out.println(remove);
//在此集合中的元素上创建late-binding和故障快速Spliterator
// Spliterator spliterator = set.spliterator();
// System.out.println(spliterator);//输出结果:java.util.HashMap$KeySpliterator@1b6d3586
//返回HashSet的浅层副本,元素本身不被克隆。
//浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
//深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
System.out.println(set.clone());//想要克隆,需要是子类的实例化,如果是父类指向子类,不能实现该功能
//foreach遍历该集合
for (Object o:set) {
System.out.println(o);
}
//迭代器遍历该集合
// Iterator iterator = set.iterator();
// while (iterator.hasNext()){
// System.out.println(iterator.next());
// }
}
}
HashSet类中没有提供根据集合索引获取索引对应的值的⽅法,
因此遍历HashSet时需要使⽤Iterator迭代器。Iterator的主要⽅法如下
返回类型 | 方法 | 描述 |
---|---|---|
boolean | hasNext() | 如果有元素可迭代 |
Object | next() | 返回迭代的下⼀个元素 |
hashCode:散列码是由对象导出的一个整型值。散列码是没有规律的。类的hashCode()方法继承自Object类,因此每个对象都有一个默认的散列码,他的值为对象的存储地址(由对象的物理存储地址通过散列转换来的)。
4.1.2 HashSet的特点
HashSet的底层就是HashMap
HashSet内部的数据结构是哈希表,是线程不安全的。
HashSet中元素不可以重复,是无序的(这里无序是指存入元素的先后顺序与输出元素的先后顺序不一致)
HashSet 中保证集合中元素是唯一的方法:通过对象的hashCode和equals方法来完成对象唯一性的判断。
hashcode的值相同对象不一定是同一个但是hashcode的值不相同对象一定不一样
1)如果对象的hashCode值不同,则不用判断equals方法,就直接存到HashSet中。
2)如果对象的hashCode值相同,需要用equals方法进行比较,如果结果为true,则视为相同元素,不存,如果结果为false,视为不同元素,进行存储。
注意:如果对象元素要存储到HashSet中,必须覆盖hashCode方法和equals方法。才能保证从对象中的内容的角度保证唯一。
类:
public class Student {
private String name;
private Integer age;
/*
hashcode的值相同对象不一定是同一个但是hashcode的值不相同对象一定不一样
1)如果对象的hashCode值不同,则不用判断equals方法,就直接存到HashSet中。
2)如果对象的hashCode值相同,需要用equals方法进行比较,如果结果为true,则视为相同元素,不存,如果结果为false,视为不同元素,进行存储。
**注意**:如果对象元素要存储到HashSet中,必须覆盖hashCode方法和equals方法。才能保证从对象中的内容的角度保证唯一。
*/
@Override
public boolean equals(Object o) {
Student student = new Student();
if (o instanceof Student){
student = (Student) o;
}
if(student.getAge()==this.age&&student.getName().equals(this.name)){
return true;
}
return false;
}
@Override
public int hashCode() {
return name.hashCode()+age.hashCode();//重写equals方法,如果name+age的hashcode值相等初步判定是同一个元素,再去调用equals方法比较内容。
}
/* @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name) && Objects.equals(age, student.age);
}
@Override
public int hashCode() {
//此处调用的是父类的hashcode方法,返回的是new出来内存的地址值。
return Objects.hash(name, age);//返回name,age的hashcode值
}
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试:
public static void main(String[] args) {
Set<Student> set = new HashSet<>();
set.add(new Student("lwl",18));
set.add(new Student("lwl",18));
System.out.println(set.size());
for (Student student : set) {
System.out.println(student);
}
}
public static void main(String[] args) {
String str1 = "OK";
StringBuffer str2 = new StringBuffer(str1);
String str3 = new String(str1);
StringBuilder str4 = new StringBuilder(str1);
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
System.out.println(str3.hashCode());
System.out.println(str4.hashCode());
}
其中str1和str3拥有相同的散列码,这是因为字符串的散列码是由内容导出的。
而字符串穿缓冲str2和str4的却不相等,这是因为他俩没有hashCode方法,他们的散列码由Object的hashCode方法导出的对象的存储地址。
4.2TreeSet
TreeSet实际上是TreeMap实现的。当我们构造TreeSet时;若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。需要实现比较器 ,主要是用来比较。
TreeSet是有序的数据集合, 可以对Set集合中的元素进行排序,是线程不安全的。
TreeSet对元素进行排序的方式:
如果是基本数据类型和String类型,无需其它操作,可以直接进行排序。
对象类型元素排序,需要实现Comparable接口,并覆盖其compareTo方法。并按照方法中的定义的规则排序
public class Student implements Comparable{
//对象需要实现Comparable接口
int age;
String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Object o) {
Student student = new Student();
if (o instanceof Student){
student = (Student) o;
}
if (this.age>student.getAge()){
return 1;
}else if (this.age<student.getAge()){
return -1;
}else {
return 0;
}
}
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;
}
}
public static void main(String[] args) {
TreeSet set = new TreeSet();
//因为重写了CompareTo方法,只要年龄不同返回的值就不同,就不是同一个数据
set.add(new Student(12,"l"));
set.add(new Student(22,"l"));
set.add(new Student(33,"l"));
set.add(new Student(44,"l"));
System.out.println(set);
//没有重写CopareTo方法
// TreeSet tree = new TreeSet();
// tree.add(12);
// tree.add("12");
// tree.add("aaa");
// System.out.println(tree.size());
}
五、Map
5.1HashMap
存取方式是 key-value形式
Key和value都可以是任意对象类型。
Key不能重复,value可以重复,可以为null。
HashMap是无序的。
如果put重复的key,后⾯的覆盖前⾯的。
5.2 HashMap创建和使用
containsKey("key")获取map中是否包含某个key。
containsValue("value")判断map中是否包含某个value。
public static void main(String[] args) {
//操作不方便但是扩展方便
HashMap map = new HashMap();
map.put("name","ll");
map.put("age",23);
map.put("phone","123453125");
map.put("null","aa");
map.put("zz",null);//HashMap接收空键空值
System.out.println(map.get("phone"));//根据key取值,123453125
System.out.println(map.get("zz"));// null
System.out.println("*******************");
HashMap map1 = new HashMap();
map1.put("name","ls");
map1.put("address","xxx");
map1.put("age",22);
// 遍历所有的值
Collection coll = map1.values();
for (Object o : coll) {
System.out.println(o+"\t");
/*
xxx
ls
22
*/
}
System.out.println("***************");
//遍历key--可以根据key取值
Set set = map1.keySet();
for (Object o : set) {
System.out.println("key:"+o+"\t"+map1.get(o)+"\t");
/*
key:address xxx
key:name ls
key:age 22
*/
}
System.out.println("***************");
//遍历所有节点:HashMap的底层有链表
Set<Map.Entry> set1 = map1.entrySet();
for (Map.Entry entry : set1) {
System.out.println(entry.getKey()+":"+entry.getValue()+"\t");
/*
address:xxx
name:ls
age:22
*/
}
}
HashMap的初始容量是16,默认加载因子是0.75,扩容按照原始容量的2倍扩容,可以存储null值和null键,因为没有加锁,当多个线程同时操作一个hashmap就可能出现不安全的情况,所以线程也是不安全的,底层是由数组+链表+红黑树实现。
当添加一个元素(key-value)时,就首先计算元素key的hash值,以此确定插入数组中的位置,但是可能存在同一hash值的元素,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,但是形成了链表,同一各链表上的Hash值是相同的,所以说数组存放的是链表。而当链表长度大于8时,链表就转换为红黑树,以及当数组的长度大于64时,也会自动转换成红黑树,这样大大提高了查找的效率。
通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node(节点)添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着key和链表上每个节点的key进行equals。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals的结果返回了true,那么这个节点的value将会被覆盖