Java路径-31-Java数据结构(java数据结构详解)
1 枚举(Enumeration)
1.1 Enumeration
源码:
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。
这种传统接口已被迭代器取代,虽然Enumeration 还未被遗弃,但在现代代码中已经被很少使用了。尽管如此,它还是使用在诸如Vector和Properties这些传统类所定义的方法中
实例1 :
import java.util.Vector;
import java.util.Enumeration;
public class EnumerationTester {
public static void main(String args[]) {
Enumeration<String> days;
Vector<String> dayNames = new Vector<String>();
dayNames.add("Sunday");
dayNames.add("Monday");
dayNames.add("Tuesday");
dayNames.add("Wednesday");
dayNames.add("Thursday");
dayNames.add("Friday");
dayNames.add("Saturday");
days = dayNames.elements();
while (days.hasMoreElements()){
System.out.println(days.nextElement());
}
}
}
运行结果:
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
1.2 枚举类
Java中的枚举其实是一种语法糖,在 JDK 1.5之后出现,用来表示固定且有限个的对象。比如一个季节类有春、夏、秋、冬四个对象;一个星期有星期一到星期日七个对象。这些明显都是固定的,且有限个。
枚举类和普通类的区别
- 使用 enum 定义的枚举类默认继承 java.lang.Enum 类,即枚举类是不能再继承别的类了。而普通类的一般父类默认是 Object
- 枚举类的构造器只能使用 private 定义,而普通类的还可以用 public 修饰
- 枚举类的所有实例必须在枚举类中显示列出(,分隔 ;结尾),列出的实例系统会默认自动添加 public static final 修饰
- 所有的枚举类都提供了一个 values() 方法,可以用来遍历枚举值
实例:
package cn.fage.enums;
/**
* @author lin
* @version 1.0
* @date 2020-06-19 17:11
* @Description TODO
*/
public enum SeasonTwoArgs {
/**
* 春天
*/
SPRING(1, "春天"),
SUMMER(2, "夏天"),
AUTUMN(3, "秋天"),
WINTER(4, "冬天");
int key;
String msg;
SeasonTwoArgs(int key, String season) {
this.key = key;
this.msg = season;
System.out.println("初始化:" + this.name() + "," + this.msg + "," + season);
}
// 很多情况,我们可能从前端拿到的值是枚举类的 key ,然后就可以通过以下静态方法获取到对应枚举值
public static SeasonTwoArgs valueofKey(Integer key) {
for (SeasonTwoArgs season : SeasonTwoArgs.values()) {
if (season.key == key) {
return season;
}
}
throw new IllegalArgumentException("No element matches " + key);
}
public String getMsg() {
return msg;
}
public int getKey() {
return key;
}
public void setKey(int key) {
this.key = key;
}
public void setMsg(String msg) {
this.msg = msg;
}
public static void main(String[] args) {
SeasonTwoArgs autumn = SeasonTwoArgs.AUTUMN;
System.out.println("autumn.getKey() = " + autumn.getKey());
System.out.println("autumn.getMsg() = " + autumn.getMsg());
System.out.println("autumn = " + autumn);
}
}
运行结果:
初始化:SPRING,春天,春天
初始化:SUMMER,夏天,夏天
初始化:AUTUMN,秋天,秋天
初始化:WINTER,冬天,冬天
autumn.getKey() = 3
autumn.getMsg() = 秋天
autumn = AUTUMN
枚举类是最完美的单例模式
2 位集合(BitSet)
一个Bitset类创建一种特殊类型的数组来保存位置。Bitset中数组大小会随需要增加。这和位向量(vector of bits)比较类似。这是一个传统的类,但它在Java 2中完全重新设计。
Bitset两个构造方法。
BitSet() //创建一个默认的对象
BitSet(int size) //指定初始大小,所有位初始化为0
BitSet常用方法
序号 | 方法 | 解释 |
1 | void and(BitSet set) | 对此目标位 set 和参数位 set 执行逻辑与操作。 |
2 | void or(BitSet bitSet) | 对此位 set 和位 set 参数执行逻辑或操作。 |
3 | void clear( ) | 将此 BitSet 中的所有位设置为 false。 |
4 | void set(int index) | 将指定索引处的位设置为 true。 |
5 | int size( ) | 返回此 BitSet 表示位值时实际使用空间的位数。 |
BitSet不常用方法
序号 | 方法 | 解释 |
– | – | – |
1 | void andNot(BitSet set) | 清除此 BitSet 中所有的位,其相应的位在指定的 BitSet 中已设置。 |
2 | void xor(BitSet bitSet) | 对此位 set 和位 set 参数执行逻辑异或操作。 |
3 | void clear( ) | 将此 BitSet 中的所有位设置为 false。 |
4 | void clear(int index) | 将索引指定处的位设置为 false。 |
5 | void clear(int startIndex, int endIndex) | 将指定的 8startIndex(包括)到指定的 toIndex(不包括)范围内的位设置为 false。 |
6 | boolean equals(Object bitSet) | 将此对象与指定的对象进行比较。 |
7 | void set(int index, boolean v) | 将指定索引处的位设置为指定的值。 |
8 | void set(int startIndex, int endIndex) | 将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的位设置为 true。 |
9 | void set(int startIndex, int endIndex, boolean v) | 将指定的 fromIndex(包括)到指定的 toIndex(不包括)范围内的位设置为指定的值。 |
10 | int size( ) | 返回此 BitSet 表示位值时实际使用空间的位数。 |
11 | String toString( ) | 返回此位 set 的字符串表示形式。 |
示例:
import java.util.BitSet;
public class BitSetDemo {
public static void main(String args[]) {
BitSet bits1 = new BitSet(16);
BitSet bits2 = new BitSet(16);
// set some bits
for(int i=0; i<16; i++) {
if((i%2) == 0) bits1.set(i);
if((i%6) != 0) bits2.set(i);
}
System.out.println("Initial pattern in bits1: ");
System.out.println(bits1);
System.out.println("\nInitial pattern in bits2: ");
System.out.println(bits2);
// AND bits
bits2.and(bits1);
System.out.println("\nbits2 AND bits1: ");
System.out.println(bits2);
// OR bits
bits2.or(bits1);
System.out.println("\nbits2 OR bits1: ");
System.out.println(bits2);
// XOR bits
bits2.xor(bits1);
System.out.println("\nbits2 XOR bits1: ");
System.out.println(bits2);
}
}
运行结果
Initial pattern in bits1:
{0, 2, 4, 6, 8, 10, 12, 14} 这些数字表示第几位为1
Initial pattern in bits2:
{1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15}
bits2 AND bits1:
{2, 4, 8, 10, 14}
bits2 OR bits1: //bits2:{2, 4, 8, 10, 14} or bits1:{0, 2, 4, 6, 8, 10, 12, 14}
{0, 2, 4, 6, 8, 10, 12, 14}
bits2 XOR bits1: //bits2:{0, 2, 4, 6, 8, 10, 12, 14} or bits1:{0, 2, 4, 6, 8, 10, 12, 14}
{}
3 向量(Vector)
Vector底层是用数组实现的,其容量是可以动态扩展的,默认初始容量是10,默认增长因子是0,详细的扩容方式会在构造方法中讲述。
Vector对象和ArrayList一样可以随意插入不同类的对象,因为其动态扩展的特性,不需要考虑容量限制。事实上Vector与ArrayList的实现方式非常相似,但是ArrayList中的方法没有synchronized关键字修饰,而Vector类中的方法都有synchronized关键字修饰,其中的单个方法都属于原子操作,是线程安全的。
注意: 多个原子操作组成的复合操作并不是线程安全的,必须将该复合操作变成原子操作,才能保证线程安全。
然而正因为Vector类中每个方法中都添加了synchronized的关键字来保证同步,使得它的效率大大的降低了,比ArrayList的效率要慢,因此一般情况下都不使用Vector对象,而会选择使用ArrayList。
3.1 Vector类的四种构造方法
public vector() //自动对向量进行管理
public vector(int initialcapacity,int capacityIncrement) //
public vector(int initialcapacity)
public Vector(Collection c)//创建一个包含集合 c 元素的向量:
频繁地扩容容易引起效率问题,因此对于第二种和第三种构造方法,可以通过指定参数来优化向量的存储空。其中initialcapacity设定向量对象的容量(即向量对象可存储数据的大小),当真正存放的数据个数超过容量时。系统会扩充向量对象存储容量。此时参数capacityincrement就给定了每次扩充的扩充值。当capacityincrement为0的时候,则每次扩充一倍。
3.2 Vector类常用的插入方法
//将obj对象插入到向量末尾
public final synchronized boolean add(Object obj)
public final synchronized void addElement(Object obj)
//在index指定的位置插入obj,原来对象以及此后的对象依次往后顺延。
public final synchronized void add(int index,Object obj)
//将index处的对象设置成obj,原来的对象将被覆盖。
public final synchronized void setElementAt(Object obj,int index)
3.3 Vector类常用的删除功能
//从向量中删除obj,若有多个存在,则从向量头开始,删除找到的第一个与obj相同的向量成员
public final synchronized void removeElement(Object obj)
//删除向量所有的对象
public final synchronized void clear();
public final synchronized void removeAllElement();
//删除index所指的地方的对象
public fianl synchronized void removeElementAt(int index)
3.4 Vector类常用的查询搜索功能
//返回向量中指定位置的元素
public final synchronized Object get(int index)
//从向量头开始搜索obj,返回所遇到的第一个obj对应的下标,若不存在此obj,返回-1.
public final int indexOf(Object obj)
//从index所表示的下标处开始搜索obj
public final synchronized int indexOf(Object obj,int index)
//从向量尾部开始逆向搜索obj.
public final int lastindexOf(Object obj)
//从index所表示的下标处由尾至头逆向搜索obj
public final synchornized int lastIndex(Object obj,int index)
//获取向量对象中的首个obj
public final synchornized firstElement()
//获取向量对象的最后一个obj
public final synchornized Object lastElement()
3.5 Vector类的其他方法
//获取向量元素的个数。它们返回值是向量中实际存在的元素个数,而非向量容量。可以调用方法capacity()来获取容量值。
public final int size();
//此方法用来定义向量的大小,若向量对象现有成员个数已经超过了newsize的值,则超过部分的多余元素会丢失。
public final synchronized void setSize(int newsize);
//此方法将向量对象对应到一个枚举类型,进而可以对向量中的元素进行遍历
public final synchronized Enumeration elements();
3.6 Vector对象中元素常用的遍历方式
Vector<String> hs=new Vector<>();//向量类可以指定泛型
//遍历方式1
public void printSet1(Vector<String> hs) {
Enumeration<String> elements = hs.elements();
while (elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}
}
//遍历方式2
public void printSet2(Vector<String> hs){
for(int i=0;i<hs.size();i++){
System.out.println(hs.get(i));
}
}
//遍历方式3
public void printSet3(Vector<String> hs){
for(String s:hs){
System.out.println(s);
}
}
//遍历方式4
public void printSet4(Vector<String> hs) {
Iterator<String> it = hs.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
遍历方式示例:
public static void main(String[] args) {
Vector hs=new Vector();
User user=new User();
user.setAge(12);
user.setName("haha");
hs.add(user);//1、先插入user对象
boolean f=hs.add(1);//2、插入Integer对象(JDK1.5之后可以自动装箱与拆箱)
hs.addElement("w");//3、插入String对象
hs.add(1,"3");//4、在索引位置1处插入String对象
System.out.println(f);//第2步操作返回boolean类型结果
Iterator it = hs.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
/*for(int i=0;i<hs.size();i++){
System.out.println(hs.get(i));
}*/
/*Enumeration<String> elements = hs.elements();
while (elements.hasMoreElements()) {
System.out.println(elements.nextElement());
}*/
}
输出如下所示:
true
prac.User@7291c18f
3
1
w
4 栈(Stack)
4.1 栈(Stack)的介绍
栈是一个先入后出(FILO:First In Last Out)的有序列表。
栈(Stack)是限制线性表中元素的插入和删除只能在同一端进行的一种特殊线性表。
允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。
根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶
而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除
4.2 栈(Stack)的定义
栈是Vector的一个子类,它实现了一个标准的后进先出的栈。
堆栈只定义了默认构造函数,用来创建一个空栈。堆栈除了包括由Vector定义的所有方法,也定义了自己的一些方法。
import java.util.EmptyStackException;
import java.util.Stack;
/**
* @author lin
* @version 1.0
* @date 2020-07-10 15:00
* @Description TODO
*/
public class TestStackDemo {
static void showpush(Stack<Integer> st, int a) {
st.push(new Integer(a));
System.out.println("push(" + a + ")");
System.out.println("stack: " + st);
}
static void showpop(Stack<Integer> st) {
System.out.print("pop -> ");
Integer a = (Integer) st.pop();
System.out.println(a);
System.out.println("stack: " + st);
}
public static void main(String args[]) {
Stack<Integer> st = new Stack<Integer>();
System.out.println("stack: " + st);
showpush(st, 42);
showpush(st, 66);
showpush(st, 99);
showpop(st);
showpop(st);
showpop(st);
try {
showpop(st);
} catch (EmptyStackException e) {
System.out.println("empty stack");
}
}
}
运行结果:
stack: []
push(42)
stack: [42]
push(66)
stack: [42, 66]
push(99)
stack: [42, 66, 99]
pop -> 99
stack: [42, 66]
pop -> 66
stack: [42]
pop -> 42
stack: []
4.3 栈的自定义实现
栈的实现有两种,一种是顺序栈,底层是数组;另一种是链式栈,是用链表实现的。栈的主要功能有:push(入栈)、pop(出栈)、peek(获取栈顶元素,不删除)、empty(判断栈是否为空)
4.3.1 顺序栈
使用数组实现,定义curSize记录当前数据的个数,如果空间满了,通过copyOf方法扩容
public class myStack<E> {
public Object[] arr;//存放数据的数组
public int curSize;//当前数据的个数
public myStack() {
this.arr = new Object[10];
}
//入栈
public E push(E val) {
//满了,扩容
if (curSize == arr.length) {
this.arr = Arrays.copyOf(this.arr, 2 * this.arr.length);
}
arr[curSize] = val;
curSize++;
return val;//返回入栈的元素
}
//出栈
public E pop() {
if (empty()) {
//如果栈中为空
return null;
}
Object ret = this.arr[curSize - 1];
curSize--;
return (E) ret;//返回出栈的元素
}
//获取栈顶元素,不删除
public E peek() {
if (curSize == 0) {
return null;
}
Object ret = this.arr[curSize - 1];
return (E) ret;//返回栈顶元素
}
//判断是否为空
public boolean empty() {
return curSize == 0;
}
}
4.3.2 链式栈
如果用单链表实现栈,只能在链表的头部进行操作,这样时间复杂度才为O(1); 如果使用的是双向链表,头部和尾部都可以,这里我们使用单链表实现
public class myStack<E> {
//链表的结点
static class ListNode {
public Object val;//数据
public ListNode next;//下一个结点
public ListNode(Object val) {
this.val = val;
}
}
public ListNode head;//(头结点)第一个结点
//入栈
public E push(E val) {
ListNode newNode = new ListNode(val);
if (head != null) {
newNode.next = head;
}
head = newNode;
return val;
}
//出栈
public E pop() {
if (head == null) {
return null;
}
Object ret = head.val;
if (head.next == null) {
head = null;
return (E) ret;
}
head = head.next;
return (E) ret;
}
//获取栈顶元素
public E peek() {
if (head == null) {
return null;
}
return (E) head.val;
}
//判断栈是否为空
public boolean empty() {
if (head == null) {
return true;
}
return true;
}
}
4.4 应用场景
1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。
2)处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。
3)表达式的转换中缀表达式转后缀表达式与求值(实际解决)。
4)二叉树的遍历。
5)图形的深度优先(depth-first)搜索法。
5 字典(Dictionary)
在Java中,Dictionary是一个抽象类,用于存储键值对数据。它提供了一种将键映射到值的数据结构。Dictionary允许通过键来访问和修改对应的值,类似于Java中的Map接口。
Dictionary类具有put(key, value)、get(key)、remove(key)等方法,可以用来存储和检索键值对数据。它是一种简单的数据结构,通常被用来实现类似于Map的功能。
需要注意的是,Dictionary是一个抽象类,不能直接实例化,需要使用它的子类Hashtable或Properties来创建对象。Hashtable是Dictionary的具体子类,实现了基本的键值对存储功能。而Properties是Hashtable的子类,主要用于处理属性文件。
Dictionary类已经过时了。在实际开发中,你可以实现Map接口来获取键/值的存储功能。
6 哈希表(Hashtable)
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
哈希表(Hashtable)是一种数据结构,可以提供快速的插入操作、删除操作和查找操作。在Java中,哈希表是通过Hashtable类实现的,它是一个老旧的类,现在已经被HashMap所取代,但Hashtable仍可以使用。
public class HashTableDemo {
public static void main(String args[]) {
// Create a hash map
Hashtable balance = new Hashtable();
Enumeration names;
String str;
double bal;
balance.put("Zara", new Double(3434.34));
balance.put("Mahnaz", new Double(123.22));
balance.put("Ayan", new Double(1378.00));
balance.put("Daisy", new Double(99.22));
balance.put("Qadir", new Double(-19.08));
// Show all balances in hash table.
names = balance.keys();
while(names.hasMoreElements()) {
str = (String) names.nextElement();
System.out.println(str + ": " +
balance.get(str));
}
System.out.println();
// Deposit 1,000 into Zara's account
bal = ((Double)balance.get("Zara")).doubleValue();
balance.put("Zara", new Double(bal+1000));
System.out.println("Zara's new balance: " +
balance.get("Zara"));
}
}
运行结果:
以上实例编译运行结果如下:
Qadir: -19.08
Zara: 3434.34
Mahnaz: 123.22
Daisy: 99.22
Ayan: 1378.0
Zara's new balance: 4434.34
7 属性(Properties)
7.1 概述
Properties 类是 Java 中用于处理配置文件的工具类,它继承自 Hashtable 类,实现了 Map 接口。
- 主要用于读取和写入属性文件,以键值对的形式存储数据。
- 配置文件通常以 .properties 为扩展名,其中每一行表示一个属性或配置项。
使用该类可以更好的处理文件:
- 配置文件管理: 主要用于读取和保存应用程序的配置信息,例如数据库连接信息、用户设置等。
- 国际化: 用于加载不同语言的资源文件,方便国际化应用程序。
- 持久化: 可以将配置信息持久化到文件,以便下次程序启动时重新加载。
构造方法如下:
方法 | 表述 |
Properties() | 创建一个没有默认值的空属性列表。 |
Properties(int initialCapacity) | 创建一个没有默认值的空属性列表,并且初始大小容纳指定数量的元素,而无需动态调整大小。 |
Properties(Properties defaults) | 创建具有指定默认值的空属性列表。 |
常用的方法如下:
返回类型 | 方法 | 表述 |
String | getProperty(String key) getProperty(String key, String defaultValue) | 在此属性列表中搜索具有指定键的属性。 |
void | list(PrintStream out) list(PrintWriter out) | 将此属性列表打印到指定的输出流。 |
void | load(InputStream inStream) | 从输入字节流中读取属性列表(键和元素对)。 |
void | load(Reader reader) | 以简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 |
Object | setProperty(String key, String value) | 调用 Hashtable方法 put 。 |
void | store(OutputStream out, String comments) | 将此 Properties表中的此属性列表(键和元素对)以适合使用 load(InputStream)方法加载到 Properties表的格式写入输出流。 |
void | store(Writer writer, String comments) | 将此 Properties表中的此属性列表(键和元素对)以适合使用 load(Reader)方法的格式写入输出字符流。 |
7.2 示例
当使用 Properties 类时,你可以使用上述方法来读取和写入属性。以下是这些方法的一些简单的代码示例:
1. getProperty(String key)
Properties properties = new Properties();
try (InputStream input = new FileInputStream("config.properties")) {
properties.load(input);
// 获取属性值
String value = properties.getProperty("key");
System.out.println("Value for key 'key': " + value);
} catch (IOException e) {
e.printStackTrace();
}
2. getProperty(String key, String defaultValue)
Properties properties = new Properties();
try (InputStream input = new FileInputStream("config.properties")) {
properties.load(input);
// 获取属性值,如果不存在则使用默认值
String value = properties.getProperty("nonexistentKey", "default");
System.out.println("Value for key 'nonexistentKey': " + value);
} catch (IOException e) {
e.printStackTrace();
}
3. list(PrintStream out) / list(PrintWriter out)
Properties properties = new Properties();
try (InputStream input = new FileInputStream("config.properties")) {
properties.load(input);
// 打印属性列表到控制台
properties.list(System.out);
} catch (IOException e) {
e.printStackTrace();
}
4. load(InputStream inStream)
Properties properties = new Properties();
try (InputStream input = new FileInputStream("config.properties")) {
// 从输入流中读取属性列表
properties.load(input);
// 遍历所有键值对
for (String key : properties.stringPropertyNames()) {
String value = properties.getProperty(key);
System.out.println(key + ": " + value);
}
} catch (IOException e) {
e.printStackTrace();
}
5. store(OutputStream out, String comments)
Properties properties = new Properties();
properties.setProperty("key1", "value1");
properties.setProperty("key2", "value2");
try (OutputStream output = new FileOutputStream("output.properties")) {
// 将属性列表写入输出流
properties.store(output, "Comments for the output file");
} catch (IOException e) {
e.printStackTrace();
}
6. store(Writer writer, String comments)
Properties properties = new Properties();
properties.setProperty("key1", "value1");
properties.setProperty("key2", "value2");
try (Writer writer = new FileWriter("output.properties")) {
// 将属性列表写入输出字符流
properties.store(writer, "Comments for the output file");
} catch (IOException e) {
e.printStackTrace();
}
7.3 Demo
上述的API方法可适当选择,完整的Demo可充分了解这个类的整体逻辑:
import java.io.*;
import java.util.Properties;
public class PropertiesDemo {
public static void main(String[] args) {
// 创建 Properties 对象
Properties properties = new Properties();
// 设置属性值
properties.setProperty("username", "码农研究僧");
properties.setProperty("password", "123456789");
properties.setProperty("server", "https://blog.csdn.net/weixin_47872288");
// 将属性列表写入输出流
try (OutputStream output = new FileOutputStream("config.properties")) {
properties.store(output, "Sample Configuration");
System.out.println("Properties written to config.properties");
} catch (IOException e) {
e.printStackTrace();
}
// 从输入流中读取属性列表
try (InputStream input = new FileInputStream("config.properties")) {
properties.load(input);
// 获取属性值
String username = properties.getProperty("username");
String password = properties.getProperty("password");
String server = properties.getProperty("server");
System.out.println("Username: " + username);
System.out.println("Password: " + password);
System.out.println("Server: " + server);
} catch (IOException e) {
e.printStackTrace();
}
// 打印属性列表到控制台
/**
* -- listing properties --
* password=123456789
* server=https://blog.csdn.net/weixin_47872288
* username=码农研究僧
*/
properties.list(System.out);
}
}
终端输出结果如下:
Properties written to config.properties
Username: aiowang
Password: 123456789
Server: http://baidu.com
-- listing properties --
password=123456789
server=http://baidu.com
username=aiowang