Java Set 接口常见的实现类以及集合遍历方式

  摘记一下最近敲的关于集合方面的Demo,会陆续做成博客记录下来的。

  这次摘记的是:

  • Collections基本的遍历方式:
    • 传统的 foreach(T t : Collections c){ ... }
    • Collections自带的Lambda遍历方式: Collections c.forEach(obj-> ...)
    • Iterator 对象进行遍历
  • Set 接口的四种常见实现类:
    • HashSet 无序并且性能比TreeSet高效,适用于基本的添加、查询元素的场合。
    • TreeSet 采用的二叉树的数据结构,需要采用红黑树算法维护数据的排序,对Set的数据类型有要求(需要实现Comparable接口或者是在TreeSet构造的时候定义排序),性能较HashSet低效,比较适用于需要保持排序的场景。
    • LinkedHashSet 采用的链表维护插入元素的顺序,其他与HashSet无太大差异,性能较HashSet略低(链表开销)
    • EnumSet 这四种常见Set实现类中最高效的,采用的是位向量的数据结构存储元素(存储高效、紧凑),要求存储元素必须是一个Enum(约束大),适用于枚举值执行批量操作的场景

这里补充说明下,上述的四种Set都是非线程安全的,即需要使用代码同步机制保证多线程访问的安全性。想使用线程安全的Set,可采用_synchronizedSortedSet_(线程安全)。

  以下为笔者学习时采用的Demo,笔者将一些心得记录放在了Demo的注释中,如果有dalao可以指点下小弟,欢迎私信:[email protected]

public class SetDemo {
	static SetDemo mine = new SetDemo();

	/**
	 * 这是一般的Lambda表达式遍历集合
	 */
	public void LambdaEx() {
		Collection<Integer> i = new HashSet<Integer>();
		i.add(1);
		i.add(2);
		i.add(3);
		i.add(4);
		i.forEach(obj -> System.out.println(obj));
	}
	/**
	 * 这是Lambda遍历Iterator对象
	 */
	public void LambdaIterator() {
		Collection<Integer> i = new HashSet<Integer>();
		i.add(1);
		i.add(2);
		i.add(3);
		i.add(4);
		Iterator<Integer> it = i.iterator();
		// 这是传统的使用Iterator对象循环遍历集合
		// while(it.hasNext()) {
		// int num = (Integer)it.next();
		// if(num == 3) {
		// it.remove();
		// }
		// System.out.println(num);
		// }
		// 利用Lambda表达式循环输出对象
		it.forEachRemaining(obj -> System.out.println(obj));
	}
	/**
	 * 这个是用来显示Java 8中的IntStream LongStream DoubleStream等流式接口
	 */
	public void IntLongDoubleStream() {
		IntStream is = IntStream.builder().add(1).add(13).add(20).add(18).build();
		Collection<Integer> c = new HashSet<Integer>();
		c.add(1);
		c.add(2);
		c.add(3);
		c.add(4);
		// // 下面的聚集方法的代码每次只能被调用一,就是下面的max() min() 等只能同时调用一个
		// System.out.println("MAX: "+ is.max().getAsInt());
		// System.out.println("MIN: "+is.min().getAsInt());
		// // 这个是用于匹配is中的所有元素是否都满足所述的要求
		// System.out.println("is 中所有元素的平方是否大于20:"+is.allMatch(ele->ele*ele>20));

		// // 这是对is中所有的对象进行修改
		// IntStream newIs = is.map(ele->ele*2);
		// newIs.forEach(ele->System.out.println(ele));
		c.stream().filter(ele -> ele > 4);

	}
	/**
	 * TreeSet Demo代码 TreeSet
	 * 会自动对插入的元素进行排序,倘若数据是使用的自定义的类型,那么那个类型需要实现Comparable接口(compareTo方法) TreeSet
	 * 实际上应该是一颗完全二叉树,并且对插入的数据进行修改的话可能会导致Set混乱,因为会出现无序并且无法找到元素的情况
	 * 读出数据的时候会采用中序遍历的方式进行读出
	 */
	public void TreeSetDemo() {
		// 默认的,使用基本的数字类型的会按照递增的顺序排列
		TreeSet<M> tSet = new TreeSet<M>((o1, o2) -> {
			M m1 = (M) o1;
			M m2 = (M) o2;
			// 递减的排序
			return m1.age > m2.age ? -1 : m1.age < m2.age ? 1 : 0;
		});
		// 插入三条数据
		tSet.add(new M(5));
		tSet.add(new M(-3));
		tSet.add(new M(9));
		System.out.println(tSet);
	}
	/**
	 * HashSet Demo代码
	 * HashSet保存元素是无序且唯一的,如果企图修改其中的元素会导致Set进入混乱的状态,进而Set无法识别对应的元素,无法精确定位元素
	 * 倘若需要重写equals方法切近最后也重写hashCode方法,因为equals方法相等的对象,它们的hashCode也应该相同。而HashSet需要利用这两个函数进行重复的判断
	 */
	public void HashSetDemo() {
		Set<R> hSet = new HashSet<R>();
		hSet.add(new R(5));
		hSet.add(new R(-3));
		hSet.add(new R(9));
		hSet.add(new R(-2));
		hSet.add(new R(3));

		// 初始状态hSet
		System.out.println(hSet);
		// 这里可以给所引起加上一个type
		Iterator<R> it = hSet.iterator();
		// 做了一个很危险的操作,将Set的第一个元素的值修改为-3
		R first = (R) it.next();
		first.count = -3;
		// 修改后状态hSet
		System.out.println(hSet);
		System.out.println("hSet 是否包含-2的元素? " + hSet.contains(new R(-2)));
		// 试图移除这个R(-3)对象
		hSet.remove(new R(-3));
		System.out.println(hSet);
		System.out.println("hSet 是否包含-3的元素? " + hSet.contains(new R(-3)));
		System.out.println("hSet 是否包含-2的元素? " + hSet.contains(new R(-2)));
	}
	/**
	 * LinkedHashSet相较于HashSet的不同是,LinkedHashSet会以链表的形式维护插入元素的顺序
	 */
	public void LinkedHashSetDemo() {
		Set<String> lSet = new LinkedHashSet<String>();
		// 重点是观察其插入是具有有序性的,即LinkedHashSet会维持元素插入的顺序
		lSet.add("t1");
		lSet.add("t2");
		System.out.println(lSet);
		// 移除再重新插入,顺序会改变
		lSet.remove("t1");
		lSet.add("t1");
		System.out.println(lSet);
	}
	/**
	 * EnumSet Demo代码 EnumSet 采用的是位向量来存储变量,进行批量操作(ContainsAll retainAll()方法)特别合适
	 * EnumSet 不允许插入null值
	 */
	public void EnumSetDemo() {
		// 以一个枚举类型创建一个EnumSet,此时会以枚举的所有值填充此EnumSet
		EnumSet<Season> eSet = EnumSet.allOf(Season.class);
		// 以一个枚举类型创建一个空的EnumSet,这里只是单纯地指定了枚举的类型
		EnumSet<Season> eSet2 = EnumSet.noneOf(Season.class);
		// 比较下两种EnumSet的区别
		System.out.println(eSet);
		System.out.println(eSet2);
		// 试图插入一个枚举值
		eSet2.add(Season.SPRING);
		// 以指定的枚举值创建EnumSet
		EnumSet<Season> eSet3 = EnumSet.of(Season.SPRING,Season.SUMMER);
		// 以枚举类型中指定范围的值创建EnumSet
		EnumSet<Season> eSet4 = EnumSet.range(Season.SPRING,Season.WINTER);
		// eSet5 + eSet4 -> Season中所有的枚举值
		EnumSet<Season> eSet5 = EnumSet.complementOf(eSet4);
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// Lambda遍历集合的方式
		// mine.LambdaIterator();
		// 使用Stream对象遍历集合的方式
		// mine.IntLongDoubleStream();
		// 常见的三种Set接口实现类
		// mine.TreeSetDemo();
		// mine.HashSetDemo();
		// mine.LinkedHashSetDemo();
		// mine.EnumSetDemo();
	}
	/**
	 * 用于演示TreeSet的类,该类未实现Comparable接口,而是将比较的方式在构建TreeSet的时候以构造函数的形式注入了
	 * 
	 * @author Administrator
	 *
	 */
	class M {
		int age = 0;

		public M(int age) {
			this.age = age;
		}

		public String toString() {
			return String.format("M[ age: %d]", age);
		}
	}
	/**
	 * 用于演示HashSet的类,关键在于重写equals和hashCode方法
	 * @author Administrator
	 *
	 */
	class R {
		int count;

		public R(int count) {
			this.count = count;
		}

		@Override
		public String toString() {
			// TODO Auto-generated method stub
			return String.format("R[count:%d]", count);
		}

		@Override
		public boolean equals(Object obj) {
			// TODO Auto-generated method stub
			if (this == obj) {
				return true;
			}
			if (obj != null && obj.getClass() == R.class) {
				R r = (R) obj;
				return this.count == r.count;
			}
			return false;
		}

		@Override
		public int hashCode() {
			// TODO Auto-generated method stub
			return this.count;
			// return super.hashCode();
		}
	}
	/**
	 * 用于演示EnumSet的类
	 * @author Administrator
	 *
	 */
	enum Season{
		SPRING,SUMMER,FAIL,WINTER
	}
}

猜你喜欢

转载自my.oschina.net/u/3744313/blog/1824751