这里将自己学习java及其应用的一些笔记、积累分享一下,如果涉及到了文章、文字侵权,请联系我删除或调整。
一、LinkedList
1.1 概述
- 双向列表,两端操作效率高
1.2 方法
- add(数据)
- add(i, 数据)
- get(i)
- remove(i)
移除指定位置数据,返回被移除的值
- remove(数据)
找到第一个相等数据移除
返回布尔值,表示是否找到数据并移除
- size()
元素的数量
- iterator()
辅助创建迭代器对象的方法。通过下标遍历双向链表,效率低;通过迭代器遍历双向链表,效率高。
迭代器实例.hasNext(),判断是否有下一个元素;
迭代器实例.Next(),获取下一个元素;
- addFirst(), addLast()
- getFirst(), getLast()
- removeFirst(), removeLast()
1.3 队列Queue,FIFO
基于LinkedList实现的队列具备以下方法:
- offer(), 等同于addLast()
- peek(), 等同于getFirst()
- poll(), 等同于removeFirst()
1.4 栈 Stack,LIFO
基于LinkedList实现的栈具备以下方法:
- push(), 等同于addFirst()
- pop(), 等同于removeFirst()
1.5 练习:双向链表
package 练习LinkedList;
import java.rmi.Remote;
import java.util.Iterator;
import java.util.LinkedList;import javax.print.attribute.Size2DSyntax;
/*
* 双向链表
*/
public class Test1 {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
list.add("eee");
list.add("fff");
list.add("ggg");
System.out.println("链表的数据元素个数:"+list.size());
System.out.println(list); // 默认toString
System.out.println("链表的第一个数据元素:"+list.get(0));
System.out.println("链表的最后一个数据元素:"+list.get(list.size()-1));
System.out.println("链表删除下标2的返回值(即被删除值):"+list.remove(2));
System.out.println("链表删除下标2后的的数据元素:"+list);
System.out.println("链表删除“aaa”的返回值(boolean值):"+list.remove("aaa"));
System.out.println("链表删除“aaa”后的的数据元素:"+list);
// 双向链表下标遍历效率低
for(int i = 0; i < list.size();i++) {
System.out.printf("%s, ",list.get(i));
}
// 迭代器遍历链表效率高
/* 迭代器有两个方法:
* next()
* hasnext()
* 迭代器不能自己创建对象,只能通过类来辅助创建对象
*/
// 新建list的迭代器对象,迭代器对象是Iterator<E>类型
Iterator<String> it = list.iterator();
while(it.hasNext()) {
System.out.printf("%s, ",it.next());
}
}
}
1.6 练习:双向链表的下标遍历和迭代器遍历
package 练习LinkedList;
/*
* 双向链表的下标遍历与迭代器遍历对比
*/
import java.util.Iterator;
import java.util.LinkedList;
public class Test2 {
public static void main(String[] args) {
/*
* Integer.valueOf(1),这一个对象重复放入双向列表10万次
*/
// 创建双向链表list
LinkedList<Integer> list = new LinkedList<Integer>();
for(int i = 0;i<100000;i++) {
list.add(1);
}
//
System.out.println("------------下标遍历------------");
f1(list);
System.out.println("------------迭代器遍历-----------");
f2(list);
}
private static void f2(LinkedList<Integer> list) {
long t = System.currentTimeMillis();
// 创建迭代器
Iterator<Integer> it = list.iterator();
// 迭代器遍历
while(it.hasNext()) {
it.next();
}
// 计算遍历时间
t = System.currentTimeMillis()-t;
System.out.println(t);
}
private static void f1(LinkedList<Integer> list) {
long t = System.currentTimeMillis();
// 下标遍历
for(int i =0;i<list.size();i++) {
list.get(i);
}
// 计算遍历时间
t = System.currentTimeMillis()-t;
System.out.println(t);
}
}
1.7 练习:丑数
丑数指的是只包含质因子2,3和5的数。在此,我们要编写一段代码,求第n个丑数。
package 丑数;
/*
* 第999个丑数 51200000
*/
import java.util.LinkedList;
import java.util.Scanner;
/*
* 丑数:只包含质因子2,3和5的数称作丑数(Ugly Number)
*/
public class Test1 {
public static void main(String[] args) {
System.out.println("求第几个丑数:");
int n = new Scanner(System.in).nextInt();
// 调用方法求取第n个丑数
long r = f(n);
System.out.printf("第"+n+"个丑数是:"+r);
}
private static long f(int n) {
/* <tile>丑数计算算法解析</title>
* ------------------------------
* 2 4 6 8
* ------------------------------
* 3 6 9 12
* ------------------------------
* 5 10 15 20
* ------------------------------
* 2 3 4 5
*
* 1. 准备3个集合,用来存放2,3,5的倍数;
* 2. 初始状态,先放入2,3,5;
* 循环{3. 从头部移除最小值,不同集合的等值最小值也移除;
* 4. 最小值分别乘2,3,5,放入3个集合;}
*/
// 创建3个链表,存放2,3,5的倍数
LinkedList<Long> list2 = new LinkedList<Long>();
LinkedList<Long> list3 = new LinkedList<Long>();
LinkedList<Long> list5 = new LinkedList<Long>();
// 初始状态,放入2,3,5
list2.add(2L);
list3.add(3L);
list5.add(5L);
long r = 0;// 用于保存结果
// 从第1个开始,求到第n个为止
for(int i = 1;i <= n;i++) {
// 取出最小值并移除
long a = list2.getFirst();// 涉及自动拆箱
long b = list3.getFirst();
long c = list5.getFirst();
// Math.min(number1, number2)求得两数中的最小数
r = Math.min(a, Math.min(b, c));// 取出三者的最小值
if(r==a)list2.removeFirst();// 删除三个集合中的最小值
if(r==b)list3.removeFirst();
if(r==c)list5.removeFirst();
// r 乘2,3,5放入集合
list2.add(r*2);
list3.add(r*3);
list5.add(r*5);
}
// 返回第n个丑数
return r;
}
}
1.8 练习:自己手写双向列表
package 自己手写双向列表;
public class SXLianBiao<T> {
private Node first;// 为了体现双向列表头尾访问的高效性,添加头节点与尾节点
private Node last;// 初始为null
private int size;// 链表中数据的数量
// 添加数据
public void add (T value) {
Node tmp = new Node();
tmp.value = value;// 数据封装入节点
// 链表为空,无数据
if(this.size==0) {
tmp.prev = tmp;
tmp.next = tmp;
first = tmp;
last = tmp;
}else {// 链表不为空,已经有数据
tmp.prev = last;
last.next = tmp;
tmp.next = first;
first.prev = tmp;
last = tmp;// 修改完对应的指针后,更新last节点为新添加的节点
}
this.size++;
}
// 获取链表数据
public T get (int i) {
// i越界,亦或没有数据,则无法访问
// 取头尾部数据
// 取中间位置的节点
Node tmp = getNode(i);
return tmp.value;
}
private SXLianBiao<T>.Node getNode(int i) {
// i越界,亦或没有数据,则无法访问
if(i<0 || i >= size) {
throw new IndexOutOfBoundsException(""+i);
}
// 取头尾部数据
if(i ==0) {
return first;
}else if (i == size - 1) {
return last;
}
/* 1 2 3 4 5 6 7 8
* i
*/
// 取中间位置的节点,为了提高访问效率,将双向列表分为两部分分别进行
if(i < size / 2){// 访问下标i位于列表的前半部分
Node tmp = first;
for(int j = 1;j<=i;j++) {
tmp = tmp.next;
}
return tmp;
}else {// 访问下标i位于列表的前后部分
Node tmp = last;
for(int j = size-2;j>=i;j--) {
tmp = tmp.prev;
}
return tmp;
}
}
// 返回链表数据个数
public int size () {
return this.size;
}
// 内部类来封装节点数据
// Node类,辅助外部双向列表对象来封装局部数据
private class Node{
T value;// 节点中封装的数据
Node prev;// 引用,指向前一个节点
Node next;// 引用,指向后一个节点
}
}
1.9 练习:手写双向列表迭代器
package 手写双向链表迭代器;
import java.util.Iterator;
/*
* for-each语法,要求欲使用for-each方法的类必须实现Iterable接口
* Iterable接口,中包含一个Iterator<T> iterator();抽象方法,必须实现
*/
public class SXLianBiao<T> implements Iterable<T>{
private Node first;// 为了体现双向列表头尾访问的高效性,添加头节点与尾节点
private Node last;// 初始为null
private int size;// 链表中数据的数量
// 添加数据
public void add (T value) {
Node tmp = new Node();
tmp.value = value;// 数据封装入节点
// 链表为空,无数据
if(this.size==0) {
tmp.prev = tmp;
tmp.next = tmp;
first = tmp;
last = tmp;
}else {// 链表不为空,已经有数据
tmp.prev = last;
last.next = tmp;
tmp.next = first;
first.prev = tmp;
last = tmp;// 修改完对应的指针后,更新last节点为新添加的节点
}
this.size++;
}
// 获取链表数据
public T get (int i) {
// i越界,亦或没有数据,则无法访问
// 取头尾部数据
// 取中间位置的节点
Node tmp = getNode(i);
return tmp.value;
}
private SXLianBiao<T>.Node getNode(int i) {
// i越界,亦或没有数据,则无法访问
if(i<0 || i >= size) {
throw new IndexOutOfBoundsException(""+i);
}
// 取头尾部数据
if(i ==0) {
return first;
}else if (i == size - 1) {
return last;
}
/* 1 2 3 4 5 6 7 8
* i
*/
// 取中间位置的节点,为了提高访问效率,将双向列表分为两部分分别进行
if(i < size / 2){// 访问下标i位于列表的前半部分
Node tmp = first;
for(int j = 1;j<=i;j++) {
tmp = tmp.next;
}
return tmp;
}else {// 访问下标i位于列表的前后部分
Node tmp = last;
for(int j = size-2;j>=i;j--) {
tmp = tmp.prev;
}
return tmp;
}
}
// 返回链表数据个数
public int size () {
return this.size;
}
// 辅助创建迭代器的方法
// Iterable接口抽象方法的实现
public Iterator<T> iterator(){
return new Itr();// 对象调用该方法时,返回一个对象的迭代器
}
// 内部类来封装节点数据
// Node类,辅助外部双向列表对象来封装局部数据
private class Node{
T value;// 节点中封装的数据
Node prev;// 引用,指向前一个节点
Node next;// 引用,指向后一个节点
}
// 迭代器类(内部类),封装双向链表的局部运算逻辑
private class Itr implements Iterator<T>{
Node tmp = null;// 初始遍历变量为null,同时也便于判断hasNext()
@Override
public boolean hasNext() {
if(size == 0)return false;// 链表无数据,直接返回false
/*
* 遍历变量tmp,初始为null,在第一次next时被赋值first,但
* 很快在当前调用轮回中,又被first.next覆盖;因此,当tmp又
* 取值为first时,tmp已依次遍历链表
*/
if(tmp != first)return true;
return false;
}
@Override
public T next() {
if(tmp == null) {
tmp = first;
}
T value = tmp.value;// 取当前节点的value存入临时变量,用于返回
tmp = tmp.next;// 取值后,迭代器tmp更新为下一个节点
return value;
}
}
}
package 手写双向链表迭代器;
import java.util.Iterator;
public class Test1 {
public static void main(String[] args) {
SXLianBiao<String> list = new SXLianBiao<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println(list.size());
System.out.println(list.get(1));
System.out.println(list.get(list.size()-2));
// 创建迭代器,因为迭代器对象是由iterator方法在类内部new的,所以此处仅调用方法即可
Iterator<String> it = list.iterator();
System.out.println(it);
while(it.hasNext()) {
System.out.println(it.next());
}
// 基于迭代器,使用for-each方法遍历
for(String w : list) {
System.err.println(w);
}
}
}
二、ArrayList和LinkedList
2.1 Collection 接口
- List 接口
ArrayList
LinkedList
- ArrayList
访问任意位置效率高
增删数据,效率可能降低
- LinkedList
两端操作效率高
- 如果仅在两端操作数据,使用 LinkedList
- 当数据量较小时(<10),频繁增删数据,使用LinkedList