菜鸡的Java笔记 第二十七 - java 链表基本概念
链表基本概念
1.链表的基本形式
2.单向链表的完整实现
认识链表
链表= 可变长的对象数组,属于动态对象数组的范畴
链表是一种最简单的线性数据结构,之所以会存在有数据结构问题主要是解决亮点:存储的数据不受限制,查找速度快
对象数组有那些问题呢?
对象数组可以保存一组对象方便开发
对象数组的长度固定,而且数据的删除,修改,增加处理麻烦
所有的开发之中都100%不可能避免掉对象数组的使用
正因为如此现在如果要想让其可以编写出便于维护的代码,那么就需要实现一个动态对象数组,那么就可以使用链表完成
但是现在如果要想实现动态的对象数组,要考虑两个问题:
为了适应于所有的开发要求,此对象数组要求可以保存所有的数据类型,那么一首选Object类型
为了可以保存多个数据,需要采用引用的方式来进行保存,但是数据本身是不可能保存顺序的
所以需要有一个可以负责保存顺序的类来包装这个数据
分析 结论:
保存数据为了方便使用 Object
数据本身不包含有先后的逻辑关系,所以将数据封装在一个 Node 类,负责关系的维护
范例:定义出如下的一个类
class Node{// b表示定义的节点
private Object data;// 要保存的数据
private Node next; // 保存下一个节点
public Node(Object data){ // 有数据才可以保存节点
this.data = data;
}
public void setNext(Node next){// 设置节点
this.next = next;
}
public Node getNext(){ // 取得节点
return this.next;
}
}
public class linkedList{
public static void main(String args[]){ }
}
完成节点之后,下面就可以进行节点的基本使用了
范例:采用循环的方式操作节点
class Node{// b表示定义的节点
private Object data;// 要保存的数据
private Node next; // 保存下一个节点
public Node(Object data){ // 有数据才可以保存节点
this.data = data;
}
public void setNext(Node next){// 设置节点
this.next = next;
}
public Node getNext(){ // 取得节点
return this.next;
}
public Object getData(){
return this.data;
}
}
public class linkedList{
public static void main(String args[]){
//1.定义各自独立的操作节点
Node root = new Node ("火车头");
Node n1 = new Node("车厢1");
Node n1 = new Node("车厢2");
//2.设置彼此间的关系
root.setNext(n1);
n1.setNext(n2);
// 3.输出
Node currentNode = root;// 从根节点开始取出数据
while(currentNode != null){
System.out.println(currentNode.getData());// 取出数据
currentNode = currentNode.getNext();//下一个节点
}
}
}
以上的操作如果使用循环并不方便。最好的做法是递归调用
范例:利用递归的方式实现内容的取得
class Node{// b表示定义的节点
private Object data;// 要保存的数据
private Node next; // 保存下一个节点
public Node(Object data){ // 有数据才可以保存节点
this.data = data;
}
public void setNext(Node next){// 设置节点
this.next = next;
}
public Node getNext(){ // 取得节点
return this.next;
}
public Object getData(){
return this.data;
}
}
public class linkedList{
public static void main(String args[]){
//1.定义各自独立的操作节点
Node root = new Node ("火车头");
Node n1 = new Node("车厢1");
Node n1 = new Node("车厢2");
//2.设置彼此间的关系
root.setNext(n1);
n1.setNext(n2);
// 3.输出 }
public static void print(Node node){
if(node == null){
return;// 结束方法调用
}
System.out.println(node.getData());
print(node,getNext());
}
}
}
通过以上的结构讲解,应该已经清楚了链表在整个实现的关键就是 Node 类,Node 类要保存数据与下一个节点
链表开发入门
虽然以上的代码已经实现了链的形式,但是以上的代码之中存在有两个问题:
用户需要自己手工定义Node 类,但是实际上Node 类对用户没用
Node 类的先后关系如果交由用户处理,那么整个代码就乱了
所以现在发现,需要有一个类,这个类可以负责所有的Node 的关系匹配,而用户只需要通过这个类保存数据或取得数据即可
那么就可以编写一个Link类完成
范例:基本结构
class Node{// b表示定义的节点
private Object data;// 要保存的数据
private Node next; // 保存下一个节点
public Node(Object data){ // 有数据才可以保存节点
this.data = data;
}
public void setNext(Node next){// 设置节点
this.next = next;
}
public Node getNext(){ // 取得节点
return this.naxt;
}
public Object getData(){
return this.data;
}
}
class link{ // 表示一个链表操作类,利用此类来隐藏Node的节点匹配
public void add(Object obj){// 向链表里面追加数据 }
public void print(){// 输出链表中的全部数据 }
}
public class linkedList{
public static void main(String args[]){
Link all = new Link();
all.add("商品1");
all.add("商品2");
all.add("商品3");
all.print();
}
}
用户不关心Node,用户只关心通过Link操作完成后可以取得数据
范例:完善程序
class Node{// b表示定义的节点
private Object data;// 要保存的数据
private Node next; // 保存下一个节点
public Node(Object data){ // 有数据才可以保存节点
this.data = data;
}
public void setNext(Node next){// 设置节点
this.next = next;
}
public Node getNext(){ // 取得节点
return this.next;
}
public Object getData(){
return this.data;
}
// 第一次调用:Link.root
// 第一次调用:Link.root.next
// 第一次调用:Link.root.next.next
public void addNode(Node newNode){
if(this.next == null){ // 当前节点之后没有节点
this.next = newNode;
}else{// 如果现在当前节点后有节点
this.next.addNode(newNode);
}
}
// 第一次调用:this = Link.root
// 第一次调用:this = Link.root.next
public void printNode(){
System.out.println(this.data);// 当前节点数据
if(this.next != null){ // 还有后续的节点
this.next.printNode();
}
}
}
class Link{ // 表示一个链表操作类,利用此类来隐藏Node的节点匹配
private Node root;// 需要有一根元素
public void add(Object obj){// 向链表里面追加数据
// 将操作的数据包装为Node类对象,这样才可以进行先后关系的排列
Node newNode = new Node(obj);
//x现在没有根节点
if(this.root == null){// this出现在Link类,表示Link类的当前对象
this.root = newNode;// 将第一个节点作为根节点
}else{// 根节点存在了
// this.root.setNext(newNode);
this.root.addNode(newNode); // 由根节点负责调用
}//(Node 负责排序Link 负责根)
}
public void print(){// 输出链表中的全部数据
if(this.root != null){ // 现在有数据
this.root.printNode(); // 输出节点数据
}
}
}
public class linkedList{
public static void main(String args[]){
Link all = new Link();
all.add("商品1");
all.add("商品2");
all.add("商品3");
all.print();
}
}
此时的代码就实现了链表的基本操作,整个过程之中,用户不关心Node的处理,只关心数据的保存和输出
开发可用链表
以上的代码只能够说是基本的链表结构形式,但是从另外一个方面,以上的代码给我们提供了链表的实现思路
可是如何才能设计一个好的链表呢?
链表的实现必须依靠于节点类Node类来实现,但是整个的过程之中一定要清楚,用户不需要操作Node
而且通过现在的代码可以发现Node类里的操作有特定的需要
但是这个时候Node类写在了外面,那么就表示用户可以直接操作Node类对象
所以程序现在的问题在于:如何可以让Node类只为Link类服务,但是有不让其他类所访问
那么自然就要想到使用内部类完成,而且内部类的好处在于:可以与外部类直接进行私有属性的访问
范例:合理的结构规划
class Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
}
//************************************
private Node root; // 根元素
}
public class linkedList{
public static void main(String args[]){ }
}
如果要开发程序,那么一定要创建自己的操作标准,那么一旦说到标准就应该想到使用接口来完成
interface Link{ }
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
}
//************************************
private Node root; // 根元素
}
public class linkedList{
public static void main(String args[]){ }
}
在随后完善代码的过程之中,除了功能的实现之外,实际上也属于接口功能的完善
实现数据增加操作 public void add(Object data)
1.需要在接口里面定义好数据增加的操作方法
interface Link{
public void add(Object data);//数据增加
}
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
}
//************************************
private Node root; // 根元素
}
public class linkedList{
public static void main(String args[]){ }
}
2.进行代码的实现,同样实现的过程之中 LinkImpl 类只关心根节点,而具体的子节点的排序都交由 Node 类负责处理
在Link类中实现add()方法:
interface Link{
public void add(Object data);//数据增加
}
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
}
//************************************
private Node root; // 根元素
public void add(Object data){
if(data == null){//现在没有要增加的数据
return;//结束调用
}
Node newNode = new Node(data);//创建新的节点
if(this.root == null){//保留有根节点
this.root = root;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
}
}
public class linkedList{
public static void main(String args[]){ }
}
在Node类中进行数据的追加操作:
interface Link{
public void add(Object data);//数据增加
}
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode;
}else{
this.next.addNode(newNode);
}
}
}
//************************************
private Node root; // 根元素
public void add(Object data){
if(data == null){//现在没有要增加的数据
return;//结束调用
}
Node newNode = new Node(data);//创建新的节点
if(this.root == null){//保留有根节点
this.root = root;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
}
}
public class linkedList{
public static void main(String args[]){ }
}
此时的代码实现过程与基本的实现是完全一样的
取得保存元素个数: public int size()
每个Link接口的对象都要保存各自的内容,所以为了方便控制保存个数,可以增加一个Link类中的属性,并且用此属性在数据成功追加之后实现自增操作
在Link类中定义一个 count 属性,默认值为:0
interface Link{
public void add(Object data);//数据增加
}
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode;
}else{
this.next.addNode(newNode);
}
}
}
//************************************
private Node root; // 根元素
private int count = 0;// 当数据已经成功添加完毕之后实现计数的统计
public void add(Object data){
if(data == null){//现在没有要增加的数据
return;//结束调用
}
Node newNode = new Node(data);//创建新的节点
if(this.root == null){//保留有根节点
this.root = root;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
this.count ++; // 当节点保存完毕之后就可以进行数据增加了
}
}
public class linkedList{
public static void main(String args[]){ }
}
而在Link接口里面追加 size() 的方法,同时在LinkImpl子类里面进行方法的覆写
interface Link{
public void add(Object data);//数据增加
public int size();// 取得保存元素的个数
}
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode;
}else{
this.next.addNode(newNode);
}
}
}
//************************************
private Node root; // 根元素
private int count = 0;// 当数据已经成功添加完毕之后实现计数的统计
public void add(Object data){
if(data == null){//现在没有要增加的数据
return;//结束调用
}
Node newNode = new Node(data);//创建新的节点
if(this.root == null){//保留有根节点
this.root = root;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
this.count ++;// 当节点保存完毕之后就可以进行数据增加了
}
public int size(){
return this.count;
}
} public class linkedList{
public static void main(String args[]){
Link all = new LinkImpl();
System.out.println(all.size());
all.add("商品1");
all.add("商品2");
all.add("商品3");
System.out.println(all.size());
}
}
此操作直接与最后的输出有关
判断是否为空集合: public boolean isEmpty()
所谓的空链表指的是链表之中没有任何的数据存在
如果要想判断集合是否为空,有两种方式:长度为 0 ,另外一个就是判断根元素是否为 null
范例:在Link 接口中追加一个新的方法: isEmpty
interface Link{
public void add(Object data);//数据增加
public int size();// 取得保存元素的个数
public boolean isEmpty();//判断是否为空集合
}
范例:在LinkImpl类中实现此方法
interface Link{
public void add(Object data);//数据增加
public int size();// 取得保存元素的个数
public boolean isEmpty();//判断是否为空集合
}
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode;
}else{
this.next.addNode(newNode);
}
}
}
//************************************
private Node root; // 根元素
private int count = 0;// 当数据已经成功添加完毕之后实现计数的统计
public void add(Object data){
if(data == null){//现在没有要增加的数据
return;//结束调用
}
Node newNode = new Node(data);//创建新的节点
if(this.root == null){//保留有根节点
this.root = root;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
this.count ++;
}
public int size(){
return this.count;
}
public boolean isEmpty()(
return this.count == 0;
// 或者 return this.root == null;
)
} public class linkedList{
public static void main(String args[]){
Link all = new LinkImpl();
System.out.println(all.isEmpty());
all.add("商品1");
all.add("商品2");
all.add("商品3");
System.out.println(all.isEmpty());
}
}
实际上此操作与 size() 几乎一脉相承
数据查询: public boolean contains(Object data)
任何情况下 Link 类只负责与根元素操作有关的内容,而所有额其他元素的数据的变更,查找,关系的匹配都应该交由 Node 类来负责处理
1.在Link接口里面创建一个新的方法
interface Link{
public void add(Object data);//数据增加
public int size();// 取得保存元素的个数
public boolean isEmpty();//判断是否为空集合
public boolean contains(Object data);// 判断是否有指定的元素
}
2.在LinkImpl 子类里面要通过根元素开始调用查询,所有的查询交由 Node 类负责
在LinkImpl 发出具体的查询要求之前,必须要保证有集合数据
public boolean contains(Object data){
if(this.root == null){// 没有集合数据
return false;
}
return this.root.containsNode(data);
// 根元素交给 Node 类完成
}
在Node 类中实现数据的查询
// 第一次:this.LinkImpl.root
// 第二次:this.LinkImpl.root.next
public boolean currentNode(Object data){
if(this.data.equals(data)){ // 该节点数据符合于查找数据
return true;
}else{// 继续向下查找
if(this.next != null){// 当前节点之后还有下一个节点
return this.next.containsNode(data);
}else{
return false;
}
}
}
这样查询的模式实质上也属于逐行的判断扫描
根据索引取得数据: public Object get(int index)
既然链表属于动态的对象数组,所以数组本身一定会提供有根据索引取得数据的操作支持,那么在链表中也可以定义与之类似的方法
但是在进行数据保存的时候并没有设置索引,那么现在有两个方案:
修改 Node 类的结构,为每一个节点自动匹配一个索引,数据的删除不方便
在操作索引时动态生存索引,适合集合的修改
1.修改Lnik 类为其增加一个 foot 的属性,之所以将foor属性定义在 LinkImpl 类之中,主要目的是方便多个 Node 共同进行属性的操作使用的,,同时内部类可以方便的访问外部类中的私有成员
private int foot = 0;//操作索引的脚标
2.在Link 接口里面首先定义出新的操作方法
public Object get(int index);//根据索引取得内容,索引从0开始
3.在LinkImpl 类里面定义功能实现:
在Node 类中应该提供有一个 getNode() 的方法,那么这个方法的功能是依靠判断每一个索引值的操作形式
public Object getNode(int index){// 传递索引的序号
if(LinkImpl.this.foot++ == index){
// 当前的索引为要查找的索引
return this.data;//返回当前节点对象
}else{
return this.next.getNode(index);
}
}
在Link 类中实现 get() 方法
public Object get(int index){
if(index >= this.count){ // 索引不存在
return null;
}
this.foot = 0;// 查询之前执行一次清零操作
return this.root.getNode(index); }
这种查询的模式与 contains() 最大的不同一个是数字索引,一个是内容
修改数据: public void set(int index,Object obj)
与get() 相比 set() 方法依然需要进行循环的判断,只不过 get() 索引判断成功之后会返回数据,而 set() 只需要用新的数据更新已有节点数据即可
1.在Link接口里面创建一个新的方法
public void set(int index,Object obj)
2.修改LnikImopl 子类,流程与 get() 差别不大:
在Node 类里面追加一个新的 setNode() 方法;
public void setNode(int index,Object obj){
if(LinkImpl.this.foot ++ == index){
this.obj = obj;// 重新保存数据
}else{
this.next.setNode(index,obj);
}
}
在LinkImpl 子类里面覆写 set() 方法,在 set() 方法编写的时候也需要针对于给定的索引进行验证
public void set(int index,Object obj){
if(index >= this.count){ // 索引不存在
return null;
}
this.foot = 0;// 查询之前执行一次清零操作
this.root.setNode(index,obj); }
set() 与 get() 方法实际上在使用时都有一个固定的条件:集合中的保存数据顺序应该为添加顺序
数据删除: public void remove(Object obj)
如果要进行数据的删除,那么对于整个链表而言就是节点的删除操作
而节点的删除操作过程之中需要考虑的问题是什么?
要删除的是根节点还是子节点问题
1.要删除的是根节点:Link 类处理,因为根节点有关的所有节点都应该交由 Link 类管理
Link.root = Link.root.next
2.要删除的是子节点:交由 Node 类负责处理
删除节点的上一个节点.next = 删除节点.next
1.在Link接口里面创建一个新的方法
public void remove(Object data);// 删除数据
2.修改LnikImopl 类的操作:
在Node 类中增加一个 removeNode() 的方法
//第一次:this.LinkImpl.root.next,previous = LinkImpl.root;
// 第二次:this.LinkImpl.root.next.next,previous = LinkImpl.root.next
public void remove(Node previous,Object data){
if(this.data.equals(data)){
previous.next = this.next;// 空出当前节点
}else{
this.next.removeNode(this,data);
}
}
在Link类中增加新的操作:
public void remove(Object data){
if(this.contains(data)){ // 数据如果存在则删除
if(this.root.equals(data)){// 根元素为要删除的元素
this.root = this.root.next; // 第二个元素作为根元素
}else{ // 不是根元素,根元素一斤判断完了
this.root.next.removeNode(this.root,data);
}
this.count --;
}
}
整个删除操作很好的体现了 this 的特性
contains() 和 remove() 方法必须有对象比较的支持,对象比较使用的就是 Object 类中的 equals() 方法
清空链表: public void clear()
当链表中的数据不需要在使用的时候,那么可以进行清空,而清空最简单的做法就是将 root设置为 null
1.在Link接口里面创建一个新的方法
public void clear();//清空链表
2.直接在 LinkImpl 类中修改清空操作
public void clear(){
this.foot = null;
this.count = 0; // 元素的保存个数清0
System.gc();//回收内存空间
}
实际上这种代码还欠缺一个很好的内存释放问题
返回数据: public Object[] toArray()
恒定的概念:链表就是动态对象数组,但是要想操作链表中的数据,那么最好的做法是将其转换为对象数组返回
所以这个时候就需要针对数据做递归处理
1.在Link 接口里面定义返回对象数组的方法
public Object[] toArray()
2.修改LnikImopl 子类
由于Node 类需要操作链表数据读取,所以应该在LinkImpl 子类里面应该提供有一个对象数组的属性
public Object retData[] = null;
在LinkImpl 子类里面覆写 toArray() 方法,并且要根据长度开辟数组空间
public Object[] toArray(){
if(this.root == null){
return null;
}
this.retData = new Object[this.count];
this.foot = 0;
this.root.toArrayNode();
return this.retData;
}
在Node类里面实现数据的保存操作
public void toArrayNode(){
LinkImpl.this.retData[LinkImpl.this.foot ++] = this.data;
if(this.next != null){
this.next.toArrayNode();
}
}
不过以上的设计都没有考虑过性能问题
interface Link{
public void add(Object data);//数据增加
public int size();// 取得保存元素的个数
public boolean isEmpty();//判断是否为空集合
public boolean contains(Object data);// 判断是否有指定的元素
public Object get(int index);//根据索引取得内容,索引从0开始
public void set(int index,Object obj);
public void remove(Object data);// 删除数据
public void clear();//清空链表
public Object[] toArray();
}
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next; public Node(Object data){
this.data = data;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode;
}else{
this.next.addNode(newNode);
}
}
public Object getNode(int index){// 传递索引的序号
if(LinkImpl.this.foot++ == index){ // 当前的索引为要查找的索引
return this.data;//返回当前节点对象
}else{
return this.next.getNode(index);
}
}
public void setNode(int index,Object data){
if(LinkImpl.this.foot ++ == index){
this.data = data;// 重新保存数据
}else{
this.next.setNode(index,data);
}
}
//第一次:this.LinkImpl.root.next,previous = LinkImpl.root;
// 第二次:this.LinkImpl.root.next.next,previous = LinkImpl.root.next
public void remove(Node previous,Object data){
if(this.data.equals(data)){
previous.next = this.next;// 空出当前节点
}else{
this.next.removeNode(this,data);
}
}
// 第一次:this.LinkImpl.root
// 第二次:this.LinkImpl.root.next
public boolean currentNode(Object data){
if(this.data.equals(data)){ // 该节点数据符合于查找数据
return true;
}else{// 继续向下查找
if(this.next != null){// 当前节点之后还有下一个节点
return this.next.containsNode(data);
}else{
return false;
}
}
}
public void toArrayNode(){
LinkImpl.this.retData[LinkImpl.this.foot ++] = this.data;
if(this.next != null){
this.next.toArrayNode();
}
}
}
//************************************
private Node root; // 根元素
private int count = 0;// 当数据已经成功添加完毕之后实现计数的统计
private int foot = 0;//操作索引的脚标
public Object retData[] = null;
public void add(Object data){
if(data == null){//现在没有要增加的数据
return;//结束调用
}
Node newNode = new Node(data);//创建新的节点
if(this.root == null){//保留有根节点
this.root = root;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
this.count ++;
}
public void remove(Object data){
if(this.contains(data)){ // 数据如果存在则删除
if(this.root.data.equals(data)){// 根元素为要删除的元素
this.root = this.root.next; // 第二个元素作为根元素
}else{ // 不是根元素,根元素一斤判断完了
this.root.next.removeNode(this.root,data);
}
this.count --;
}
}
public void clear(){
this.foot = null;
this.count = 0;
System.gc();//回收内存空间
}
public int size(){
return this.count;
}
public boolean isEmpty(){
return this.count == 0;
// 或者 return this.root == null;
}
public boolean contains(Object data){
if(this.root == null){// 没有集合数据
return false;
}
return this.root.containsNode(data);// 根元素交给 Node 类完成
}
public Object[] toArray(){
if(this.root == null){
return null;
}
this.retData = new Object[this.count];
this.foot = 0;
this.root.toArrayNode();
return this.retData;
}
public Object get(int index){
if(index >= this.count){ // 索引不存在
return null;
}
this.foot = 0;// 查询之前执行一次清零操作
return this.root.getNode(index); }
public void set(int index,Object data){
if(index >= this.count){ // 索引不存在
return null;
}
this.foot = 0;// 查询之前执行一次清零操作
this.root.setNode(index,data); }
}
public class linkedList{
public static void main(String args[]){
Link all = new LinkImpl();
System.out.println(all.isEmpty());
all.add("A");
all.add("B");
all.add("C");
Object obj[] = all.toArray();
for(int x = 0;x < obj.length; x++ ){
System.out.println(obj[x]);
}
}
}
数组形式返回
1.在Link接口里面追加有返回数据的方法:
public Object [] toArray(); // 以对象数组的形式返回链表数据
2.修改LnikImopl 子类:
需要追加一个进行返回数据数组下标控制,并且这一操作属性需要被Node内部类使用,那么必须将其定义为外部类属性
private int foot = 0;//操作索引的脚标
对于返回数据保存由于需要在内部类中处理,所以在外部类中定义属性
private Object retData[]; // 定义一个返回的数组
在进行 toArray() 方法覆写的时候由于该方法可能调用很多次,并且有可能调用过程之中链表中的数据个数发生了改变,以每一次都需要重新根据数组大小开辟空间
public Object [] toArray(){
if(this.root == null){ // 现在没有数据
return new Object[0]; // 没有数据返回
}
this.retData = new Object[this.count];// 根据已有的数据个数开辟数组个数
this.foot = 0;//脚标重置
this.root.toArrayNode();// 交给Node类负责
return this.retData;
}
3.真正获得数据的过程(节点迭代过程)应该有Node类负责
// 第1次调用: this = LinkImpl.root9
// 第2次调用: this = LinkImpl.root.naxt
public void toArrayNode(){ // 递归调用
LinkImpl.this.retData[LinkImpl.this.foot ++] = this.data;
if(this.naxt != null){ // 还有下一个节点
this.naxt.toArrayNode();
}
}
综合实战:宠物商店
接口实际上是属于某几类事物的抽象,也就是说在整个的定义结构上,接口标准应该优先于类定义出来,而后类按照指定的接口标准进行实现
如果说现在有这样的一个案例要求:定义一个宠物商店,在这个宠物商店里面可以实现宠物的上架,下架,关键字查询
现在假设宠物里面只包含两个信息(名字,年龄),而后要求通过类的结构关系描述出本程序
宠物商店应该是一个程序类,因为宠物商店可能有很多种,但是其具备的特征肯定只有一个,而后宠物商店只与宠物标准有关(符合此标准的可能有多种宠物类型)
而宠物商店里面需要保存有多中宠物信息(不知道个数的对象数组,应该采用链表实现)
1.应该建立宠物的标准服务接口
interface Pet{
public String getName();
public int getAge();
}
2.定义宠物商店,宠物尚待年不关心具体的宠物类型,只关心宠物标准
class PetShop{
private Ilink allPets = new LinkImpl();// 动态对象数组
public void add(Pet pet){ // 追加的是宠物
this.allPets.add(pet); // 宠物信息追加
}
public void delete(Pet pet){ // 删除宠物信息
this.allPets.remove(pet); // equals() 支持
}
public ILink search(String keyWord){ // 返回查询结果
ILink result = new LinkImpl(); // 查询结果
Object data[] = this.allPets.toArray(); // 变为对象数组返回
for(int x = 0; x < data.length; x++){
Pet tempPet = (Pet)data[x];
if(tempPet.getName().contains(keyWord)){// 有此关键字
result.add(tempPet); // 保存返回结果
}
return result;
}
}
}
3.定义宠物
class Dog{ // 狗
private String name;
private int age;
public Dog(String name,int age){
this.name = name;
this.age = age;
}
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj == null){
return false;
}
if(!(obj instanceof Dog)){
return false;
}
Dog pet = (Dog)obj;
return this.name.equals(pet.name) && this.age == pet.age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public String toString(){
return "【宠物狗】 name = "+ this,name +",age = "+this.age;
}
} class Cat{
private String name;
private int age;
public Cta(String name,int age){
this.name = name;
this.age = age;
}
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj == null){
return false;
}
if(!(obj instanceof Cat)){
return false;
}
Cat pet = (Cat)obj;
return this.name.equals(pet.name) && this.age == pet.age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public String toString(){
return "【宠物猫】 name = "+ this,name +",age = "+this.age;
}
}
4.测试
public class linkedList{
public static void main(String args[]){
PetShop shop = new PetShop(); // 宠物商店准备好了
shop.add(new Dog("狗子",1));
shop.add(new Dog("二狗子",1));
shop.add(new Cat("喵喵",1));
shop.add(new Cat("小瞄",1));
shop.delete(new Dog("二狗子",1)); // 删除操作
ILink result = shop.search("瞄");
Object data[] =result.toArray();
for(int x = 0; x< data.length; x++){
System.out.println(data[x]);
}
}
}
宠物商店和宠物之间没有任何的联系,依靠的是接口关键在一起,同时为了存储更多的数据使用了链表处理
利用此原则可以实现更毒的场景:
一个停车可以停放各种车辆,例如:小轿车,越野车,卡车
一个饭店不允许任何宠物进入 或 有多种菜品
interface ILink{
public void add(Object data);//数据增加
public int size();// 取得保存元素的个数
public boolean isEmpty();//判断是否为空集合
public boolean contains(Object data);// 判断是否有指定的元素
public Object get(int index);//根据索引取得内容,索引从0开始
public void set(int index,Object obj);
public void remove(Object data);// 删除数据
public void clear();//清空链表
public Object[] toArray();
}
class LinkImpl implements Link{ //外部的程序只关心此类
private class Node{//使用私有内部类,防止外部使用此类
private Object data;
private Node next;
public Node(Object data){
this.data = data;
}
public void addNode(Node newNode){
if(this.next == null){
this.next = newNode;
}else{
this.next.addNode(newNode);
}
}
public Object getNode(int index){// 传递索引的序号
if(LinkImpl.this.foot++ == index){ // 当前的索引为要查找的索引
return this.data;//返回当前节点对象
}else{
return this.next.getNode(index);
}
}
public void setNode(int index,Object data){
if(LinkImpl.this.foot ++ == index){
this.data = data;// 重新保存数据
}else{
this.next.setNode(index,data);
}
}
//第一次:this.LinkImpl.root.next,previous = LinkImpl.root;
// 第二次:this.LinkImpl.root.next.next,previous = LinkImpl.root.next
public void remove(Node previous,Object data){
if(this.data.equals(data)){
previous.next = this.next;// 空出当前节点
}else{
this.next.removeNode(this,data);
}
}
// 第一次:this.LinkImpl.root
// 第二次:this.LinkImpl.root.next
public boolean currentNode(Object data){
if(this.data.equals(data)){ // 该节点数据符合于查找数据
return true;
}else{// 继续向下查找
if(this.next != null){// 当前节点之后还有下一个节点
return this.next.containsNode(data);
}else{
return false;
}
}
}
public void toArrayNode(){
LinkImpl.this.retData[LinkImpl.this.foot ++] = this.data;
if(this.next != null){
this.next.toArrayNode();
}
}
}
//************************************
private Node root; // 根元素
private int count = 0;// 当数据已经成功添加完毕之后实现计数的统计
private int foot = 0;//操作索引的脚标
public Object retData[] = null;
public void add(Object data){
if(data == null){//现在没有要增加的数据
return;//结束调用
}
Node newNode = new Node(data);//创建新的节点
if(this.root == null){//保留有根节点
this.root = root;
}else{//应该交由Node类负责处理
this.root.addNode(newNode);
}
this.count ++;
}
public void remove(Object data){
if(this.contains(data)){ // 数据如果存在则删除
if(this.root.data.equals(data)){// 根元素为要删除的元素
this.root = this.root.next; // 第二个元素作为根元素
}else{ // 不是根元素,根元素一斤判断完了
this.root.next.removeNode(this.root,data);
}
this.count --;
}
}
public void clear(){
this.foot = null;
this.count = 0;
System.gc();//回收内存空间
}
public int size(){
return this.count;
}
public boolean isEmpty(){
return this.count == 0;
// 或者 return this.root == null;
}
public boolean contains(Object data){
if(this.root == null){// 没有集合数据
return false;
}
return this.root.containsNode(data);// 根元素交给 Node 类完成
}
public Object[] toArray(){
if(this.root == null){
return null;
}
this.retData = new Object[this.count];
this.foot = 0;
this.root.toArrayNode();
return this.retData;
}
public Object get(int index){
if(index >= this.count){ // 索引不存在
return null;
}
this.foot = 0;// 查询之前执行一次清零操作
return this.root.getNode(index); }
public void set(int index,Object data){
if(index >= this.count){ // 索引不存在
return null;
}
this.foot = 0;// 查询之前执行一次清零操作
this.root.setNode(index,data); }
}
interface Pet{
public String getName();
public int getAge();
}
class PetShop{
private Ilink allPets = new LinkImpl();// 动态对象数组
public void add(Pet pet){ // 追加的是宠物
this.allPets.add(pet); // 宠物信息追加
}
public void delete(Pet pet){ // 删除宠物信息
this.allPets.remove(pet); // equals() 支持
}
public ILink search(String keyWord){ // 返回查询结果
ILink result = new LinkImpl(); // 查询结果
Object data[] = this.allPets.toArray(); // 变为对象数组返回
for(int x = 0; x < data.length; x++){
Pet tempPet = (Pet)data[x];
if(tempPet.getName().contains(keyWord)){// 有此关键字
result.add(tempPet); // 保存返回结果
}
return result;
}
}
}
class Dog implements Pet{
private String name;
private int age;
public Dog(String name,int age){
this.name = name;
this.age = age;
}
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj == null){
return false;
}
if(!(obj instanceof Dog)){
return false;
}
Dog pet = (Dog)obj;
return this.name.equals(pet.name) && this.age == pet.age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public String toString(){
return "【宠物狗】 name = "+ this,name +",age = "+this.age;
}
}
class Cat implements Pet{
private String name;
private int age;
public Cta(String name,int age){
this.name = name;
this.age = age;
}
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj == null){
return false;
}
if(!(obj instanceof Cat)){
return false;
}
Cat pet = (Cat)obj;
return this.name.equals(pet.name) && this.age == pet.age;
}
public String getName(){
return this.name;
}
public int getAge(){
return this.age;
}
public String toString(){
return "【宠物猫】 name = "+ this,name +",age = "+this.age;
}
}
public class linkedList{
public static void main(String args[]){
PetShop shop = new PetShop(); // 宠物商店准备好了
shop.add(new Dog("狗子",1));
shop.add(new Dog("二狗子",1));
shop.add(new Cat("喵喵",1));
shop.add(new Cat("小瞄",1));
shop.delete(new Dog("二狗子",1)); // 删除操作
ILink result = shop.search("瞄");
Object data[] =result.toArray();
for(int x = 0; x< data.length; x++){
System.out.println(data[x]);
}
}
}
总结
1.以上只是简单的单向链表,要求清楚大概的原理
2.对于以下给出的方法一定要掌握
No | 方法名称 | 类型 | 描述 |
1 | public void add(Object data) | 普通 | 向集合追加数据 |
2 | public int size() | 普通 | 取得集合中保存的元素个数 |
3 | public boolean isEmpty() | 普通 | 判断是否为空集合 |
4 | public boolean contains(Object data) | 普通 | 判断是否存在有指定的元素,需要 equals() 支持 |
5 | public Object get(int index) | 普通 | 根据索引取得指定的数据 |
6 | public void set(int index,Object obj) | 普通 | 修改指定索引位置上的数据 |
7 | public void remove(Object obj) | 普通 | 数据删除操作,需要 equals() 支持 |
8 | public void clear() | 普通 | 清空链表 |
9 | public Object[] toArray() | 普通 | 链表转换为对象数组数据 |
*/
菜鸡的Java笔记 第二十七 - java 链表基本概念的更多相关文章
- 菜鸡的Java笔记 第二十三 - java 抽象类的概念
abstractClass 抽象类的概念 1.抽象类的基本定义 2.抽象类的使用原则 不会抽象类与接口,java = 没学 ...
- 菜鸡的Java笔记 第二十 - java 方法的覆写
1.方法的覆写 当子类定义了与父类中的完全一样的方法时(方法名称,参数类型以及个数,返回值类型)这样的操作就称为方法的覆写 范例:观察方法的覆写 class A{ public void ...
- “全栈2019”Java多线程第二十七章:Lock获取lock/释放unlock锁
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Java进阶(三十七)java 自动装箱与拆箱
Java进阶(三十七)java 自动装箱与拆箱 前言 这个是jdk1.5以后才引入的新的内容.java语言规范中说道:在许多情况下包装与解包装是由编译器自行完成的(在这种情况下包装称为装箱,解包装称为 ...
- 菜鸡的Java笔记 第二十八 - java 包的定义
包的主要作用以及定义 包的导入操作 系统常见的开发包 jar 程序命令 包的定义 在任何的操作系统之中都有一个统一的共识:同一个目录下不能够存在有相同的文 ...
- <<深入Java虚拟机>>-第二章-Java内存区域-学习笔记
Java运行时内存区域 Java虚拟机在运行Java程序的时候会将它所管理的内存区域划分为多个不同的区域.每个区域都有自己的用途,创建以及销毁的时间.有的随着虚拟机的启动而存在,有的则是依赖用户线程来 ...
- Java笔记(二十七)……IO流中 File文件对象与Properties类
File类 用来将文件或目录封装成对象 方便对文件或目录信息进行处理 File对象可以作为参数传递给流进行操作 File类常用方法 创建 booleancreateNewFile():创建新文件,如果 ...
- 【Java笔记】配置文件java.util.Properties类的使用
配置文件的路径:项目名/src/main/resources/mmall.properties mmall.properties的内容是键值对.例如假设写了ftp服务器的一些信息. ftp.serve ...
- Java笔记12:Java对象排序
代码: import java.util.Arrays; import java.util.Comparator; class Person { private String name; privat ...
随机推荐
- ApsNetCore打造一个“最安全”的api接口
Authentication,Authorization 如果公司交给你一个任务让你写一个api接口,那么我们应该如何设计这个api接口来保证这个接口是对外看起来"高大上",&qu ...
- SpringBoot下使用AspectJ(CTW)下不能注入SpringIOC容器中的Bean
SpringBoot下使用AspectJ(CTW)下不能注入SpringIOC容器中的Bean 在SpringBoot中开发AspectJ时,使用CTW的方式来织入代码,由于采用这种形式,切面Bean ...
- Java基础之(十三):类与对象
初识面向对象 面向对象 & 面向过程 面向过程思想 步骤清晰简单,第一步做什么,第二步做什么..... 面向过程适合处理一些较为简单的问题 面向对象思想 物以类聚,分类的思维模式,思考问题 ...
- kvm安装window系统及使用NFS动态迁移
验证是否开启虚拟化 # grep -E 'svm|vmx' /proc/cpuinfo - vmx is for Intel processors - svm is for AMD processor ...
- 互联网公司作息表「GitHub 热点速览 v.21.42」
作者:HelloGitHub-小鱼干 检测一家公司是否值得一去,除了高薪之外,还有时薪的算法.即便是同样的时薪,在一家能随时摸鱼的公司,岂不是人生快事.WorkingTime 便是上周很火的互联网作息 ...
- 更好的 java 重试框架 sisyphus 入门简介
What is Sisyphus sisyphus 综合了 spring-retry 和 gauva-retrying 的优势,使用起来也非常灵活. 为什么选择这个名字 我觉得重试做的事情和西西弗斯很 ...
- 【c++ Prime 学习笔记】目录索引
第1章 开始 第Ⅰ部分 C++基础 第2章 变量和基本类型 第3章 字符串.向量和数组 第4章 表达式 第5章 语句 第6章 函数 第7章 类 第 Ⅱ 部分 C++标准库 第8章 IO库 第9章 顺序 ...
- RabbitMQ设计原理解析
背景 RabbitMQ现在用的也比较多,但是没有过去那么多啦.现在很多的流行或者常用技术或者思路都是从过去的思路中演变而来的.了解一些过去的技术,对有些人来说可能会产生众里寻他千百度的顿悟,加深对技术 ...
- the Agiles Scrum Meeting 6
会议时间:2020.4.14 20:00 1.每个人的工作 今天已完成的工作 增量组:开发广播正文展开收起功能 issues:增量组:广播正文展开收起功能实现 完善组:修复冲刺部分的bug issue ...
- Canal Server发送binlog消息到Kafka消息队列中
Canal Server发送binlog消息到Kafka消息队列中 一.背景 二.需要修改的地方 1.canal.properties 配置文件修改 1.修改canal.serverMode的值 2. ...