java private list_Java基础知识回顾之四 ----- 集合List、Map和Set

前言

在上一篇中回顾了Java的三大特性:封装、继承和多态。本篇则来介绍下集合。

集合介绍

我们在进行Java程序开发的时候,除了最常用的基础数据类型和String对象外,也经常会用到集合相关类。

集合类存放的都是对象的引用,而非对象本身,出于表达上的便利,我们称集合中的对象就是指集合中对象的引用。

集合类型主要有3种:List、Set、和Map。

它们之间的关系可用下图来表示:

0cb41b7d632b212b032978fd73633d9a.png

注:Map不是collections的子类,但是它们完全整合在集合中了!

List

List 接口是继承于 Collection接口并定义 一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。

一般来说,我们在单线程中主要使用的List是ArrayList和LinkedList来实现,多线程则是使用Vector或者使用Collections.sychronizedList来装饰一个集合。

这三个的解释如下:

ArrayList:内部是通过数组实现的,它允许对元素进行快随机访问。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。

LinkedList: 则是链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

Vector: 通过数组实现的,不同的是它支持线程的同步。访问速度ArrayList慢。

它们的用法如下:

List list1 = new ArrayList();

List list2 = new LinkedList();

List list3 = new Vector();

List list4=Collections.synchronizedList(new ArrayList())

在了解了它们的用法之后,我们来看看为什么说使用ArrayList比LinkedList查询快,使用LinkedList比ArrayList新增和删除快!

这里也用以下代码来进行说明,顺便也将Vector进行比较。

代码示例:

private final static int count=50000;

private static ArrayList arrayList = new ArrayList<>();

private static LinkedList linkedList = new LinkedList<>();

private static Vector vector = new Vector<>();

public static void main(String[] args) {

insertList(arrayList);

insertList(linkedList);

insertList(vector);

System.out.println("--------------------");

readList(arrayList);

readList(linkedList);

readList(vector);

System.out.println("--------------------");

delList(arrayList);

delList(linkedList);

delList(vector);

}

private static void insertList(List list){

long start=System.currentTimeMillis();

Object o = new Object();

for(int i=0;i

list.add(0, o);

}

System.out.println(getName(list)+"插入"+count+"条数据,耗时:"+(System.currentTimeMillis()-start)+"ms");

}

private static void readList(List list){

long start=System.currentTimeMillis();

Object o = new Object();

for(int i = 0 ; i < count ; i++){

list.get(i);

}

System.out.println(getName(list)+"查询"+count+"条数据,耗时:"+(System.currentTimeMillis()-start)+"ms");

}

private static void delList(List list){

long start=System.currentTimeMillis();

Object o = new Object();

for(int i = 0 ; i < count ; i++){

list.remove(0);

}

System.out.println(getName(list)+"删除"+count+"条数据,耗时:"+(System.currentTimeMillis()-start)+"ms");

}

private static String getName(List list) {

String name = "";

if(list instanceof ArrayList){

name = "ArrayList";

}

else if(list instanceof LinkedList){

name = "LinkedList";

}

else if(list instanceof Vector){

name = "Vector";

}

return name;

}

输出结果:

ArrayList插入50000条数据,耗时:281ms

LinkedList插入50000条数据,耗时:2ms

Vector插入50000条数据,耗时:274ms

--------------------

ArrayList查询50000条数据,耗时:1ms

LinkedList查询50000条数据,耗时:1060ms

Vector查询50000条数据,耗时:2ms

--------------------

ArrayList删除50000条数据,耗时:143ms

LinkedList删除50000条数据,耗时:1ms

Vector删除50000条数据,耗时:137ms

从上述结果中,可以明显看出ArrayList和LinkedList在新增、删除和查询性能上的区别。

在集合中,我们一般用于存储数据。不过有时在有多个集合的时候,我们想将这几个集合做合集、交集、差集和并集的操作。在List中,这些方法已经封装好了,我们无需在进行编写相应的代码,直接拿来使用就行。

代码示例如下:

/**

* 合集

* @param ls1

* @param ls2

* @return

*/

private static List addAll(List ls1,Listls2){

ls1.addAll(ls2);

return ls1;

}

/**

* 交集 (retainAll 会删除 ls1在ls2中没有的元素)

* @param ls1

* @param ls2

* @return

*/

private static List retainAll(List ls1,Listls2){

ls1.retainAll(ls2);

return ls1;

}

/**

* 差集 (删除ls2中没有ls1中的元素)

* @param ls1

* @param ls2

* @return

*/

private static List removeAll(List ls1,Listls2){

ls1.removeAll(ls2);

return ls1;

}

/**

* 无重复的并集 (ls1和ls2中并集,并无重复)

* @param ls1

* @param ls2

* @return

*/

private static List andAll(List ls1,Listls2){

//删除在ls1中出现的元素

ls2.removeAll(ls1);

//将剩余的ls2中的元素添加到ls1中

ls1.addAll(ls2);

return ls1;

}

当然,经常用到的还有对List进行遍历。

List数组遍历主要有这三种方法,普通的for循环,增强for循环(jdk1.5之后出现),和Iterator(迭代器)。

代码示例:

List list=new ArrayList();

list.add("a");

list.add("b");

list.add("c");

for(int i=0;i

System.out.println(list.get(i));

}

for (String str : list) {

System.out.println(str);

}

Iterator iterator=list.iterator();

while(iterator.hasNext())

{

System.out.println(iterator.next());

}

说明:普通的for循环和增强for循环区别不大,主要区别在于普通的for循环可以获取集合的下标,而增强for循环则不可以。但增强for循环写起来方法,如果不需要获取具体集合的下标,推荐使用增强for循环。至于Iterator(迭代器)这种也是无法获取数据下标,但是该方法可以不用担心在遍历的过程中会集合的长度发生改变。也就是在遍历的时候对集合进行增加和删除。

在中,对于集合操作也有这种说明。

不要在 foreach 循环里进行元素的 remove / add 操作。 remove 元素请使用Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

那么为什么不要使用 foreach 循环进行元素的 remove / add 操作呢?

我们这里可以简单的做下验证。

代码示例:

List list = new ArrayList();

list.add("1");

list.add("2");

System.out.println("list遍历之前:"+list);

for (String item : list) {

if ("2".equals(item)) {

list.remove(item);

//如果这里不适用break的话,会直接报错的

break;

}

}

System.out.println("list遍历之后:"+list);

List list1 = new ArrayList();

list1.add("1");

list1.add("2");

System.out.println("list1遍历之前:"+list1);

Iterator iterator = list1.iterator();

while (iterator.hasNext()) {

String item = iterator.next();

if ("2".equals(item)) {

iterator.remove();

}

}

System.out.println("list1遍历之后:"+list1);

输出结果:

list遍历之前:[1, 2]

list遍历之后:[1]

list1遍历之前:[1, 2]

list1遍历之后:[1]

注意:上述代码中,在对list进行for循环遍历的时候,加了break,

上述示例中,都正确的打印我们想要的数据,不过在foreach循环中,我在其中是加上了break。如果不加break,就会直接抛出ConcurrentModificationException异常!

Map

Map 接口并不是 Collection 接口的继承。Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。

Map接口主要由HashMap、TreeMap、LinkedHashMap、Hashtable和ConcurrentHashMap这几个类实现。

它们的解释如下:

HashMap: HashMap的键是根据HashCode来获取,所以根据键可以很快的获取相应的值。不过它的键对象是不可以重复的,它允许键为Null,但是最多只能有一条记录,不过却是可以允许多条记录的值为Null。因为HashMap是非线程安全的,所以它的效率很高。

TreeMap:可以将保存的记录根据键进行排序,默认是按键值的升序排序(自然顺序)。也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。它也是不允许key值为空,并且不是线程安全的。

LinkedHashMap:LinkedHashMap基本和HashMap一致。不过区别在与LinkedHashMap是维护一个双链表,可以将里面的数据按写入 的顺序读出。可以认为LinkedHashMap是HashMap+LinkedList。即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。它也不是线程安全的。

Hashtable:Hashtable与HashMap类似,可以说是HashMap的线程安全版。不过它是不允许记录的键或者值为null。因为它支持线程的同步,是线程安全的,所以也导致了Hashtale在效率较低。

ConcurrentHashMap: ConcurrentHashMap在Java 1.5作为Hashtable的替代选择新引入的。使用锁分段技术技术来保证线程安全的,可以看作是Hashtable的升级版。

在工作中,我们使用得最多的Map应该是HashMap。不过有时在使用Map的时候,需要进行自然顺序排序。这里我们就可以使用TreeMap,而不必自己实现这个功能。TreeMap的使用和HashMap差不多。不过需要注意的是TreeMap是不允许key为null。 这里简单的介绍下TreeMap的使用。

代码示例:

Map hashMap=new HashMap();

hashMap.put("a", 1);

hashMap.put("c", 3);

hashMap.put("b", 2);

System.out.println("HashMap:"+hashMap);

Map treeMap=new TreeMap();

treeMap.put("a", 1);

treeMap.put("c", 3);

treeMap.put("b", 2);

System.out.println("TreeMap:"+treeMap);

输出结果:

HashMap:{b=2, c=3, a=1}

TreeMap:{a=1, b=2, c=3}

上述中可以看出HashMap是无序的,TreeMap是有序的。

在使用Map的时候,也会对Map进行遍历。一般遍历Map的key和value有三种方式:

第一种通过Map.keySet遍历;

第二种通过Map.entrySet使用iterator遍历;

第三种是通过Map.entrySet进行遍历。

使用如下:

Map map = new HashMap();

for (String key : map.keySet()) {

System.out.println("key= "+ key + " and value= " + map.get(key));

}

Iterator> it = map.entrySet().iterator();

while (it.hasNext()) {

Map.Entry entry = it.next();

System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());

}

for (Map.Entry entry : map.entrySet()) {

System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());

}

如果只想获取Map中value的话,可以使用foreach对Map.values()进行遍历。

for (String v : map.values()) {

System.out.println("value= " + v);

}

在上述遍历中,我们最多使用的是第一种Map.keySet,因为写起来比较简单。不过在容量大的时候,推荐使用第三种,效率会更高!

Set

Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。因为Set是一个抽象的接口,所以是不能直接实例化一个set对象。Set s = new Set() 这种写法是错误的。

Set接口主要是由HashSet、TreeSet和LinkedHashSet来实现。

它们简单的使用如下:

Set hashSet = new HashSet();

Set treeSet = new TreeSet();

Set linkedSet = new LinkedHashSet();

因为Set是无法拥有重复元素的,所以也经常用它来去重。例如在一个list集合中有两条相同的数据,想去掉一条,这时便可以使用Set的机制来去重。

代码示例:

public static void set(){

List list = new ArrayList();

list.add("Java");

list.add("C");

list.add("C++");

list.add("JavaScript");

list.add("Java");

Set set = new HashSet();

for (int i = 0; i < list.size(); i++) {

String items = list.get(i);

System.out.println("items:"+items);

if (!set.add(items)) {

System.out.println("重复的数据: " + items);

}

}

System.out.println("list:"+list);

}

输出结果:

items:Java

items:C

items:C++

items:JavaScript

items:Java

重复的数据: Java

list:[Java, C, C++, JavaScript, Java]

注意:如果是将对象进行去重的话,是需要重写set中的equals和hashcode方法的。

总结

关于集合中List、Map、Set这三个的总结如下:

List:List和数组类似,可以动态增长,根据实际存储的数据的长度自动增长List的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变

ArrayList:非线程安全,适合随机查找和遍历,不适合插入和删除。

LinkedList : 非线程安全,适合插入和删除,不适合查找。

Vector : 线程安全。不过不推荐。

Map:一个key到value的映射的类 。

HashMap:非线程安全,键和值都允许有null值存在。

TreeMap:非线程安全,按自然顺序或自定义顺序遍历键(key)。

LinkedHashMap:非线程安全,维护一个双链表,可以将里面的数据按写入的顺序读出。写入比HashMap强,新增和删除比HashMap差。

Hashtable:线程安全,键和值不允许有null值存在。不推荐使用。

ConcurrentHashMap:线程安全,Hashtable的升级版。推荐多线程使用。

Set:不允许重复的数据 。检索效率低下,删除和插入效率高。

HashSet: 非线程安全、无序、数据可为空。

TreeSet: 非线程安全、有序、数据不可为空。

LinkedHashSet:非线程安全、无序、数据可为空。写入比HashSet强,新增和删除比HashSet差。

到此,本文结束,谢谢阅读。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的 Java 实现 k 近邻算法: ```java import java.util.*; public class KNN { private List<Data> trainSet; public KNN(List<Data> trainSet) { this.trainSet = trainSet; } public String classify(Data testData, int k) { List<Data> nearestNeighbors = findNearestNeighbors(testData, k); String label = getMostFrequentLabel(nearestNeighbors); return label; } private List<Data> findNearestNeighbors(Data testData, int k) { PriorityQueue<Data> pq = new PriorityQueue<>(k, new Comparator<Data>() { @Override public int compare(Data d1, Data d2) { return Double.compare(d2.distance, d1.distance); } }); for (Data trainData : trainSet) { double distance = getDistance(testData, trainData); trainData.distance = distance; if (pq.size() < k) { pq.offer(trainData); } else { Data top = pq.peek(); if (distance < top.distance) { pq.poll(); pq.offer(trainData); } } } List<Data> result = new ArrayList<>(); while (!pq.isEmpty()) { result.add(pq.poll()); } return result; } private String getMostFrequentLabel(List<Data> nearestNeighbors) { Map<String, Integer> countMap = new HashMap<>(); for (Data data : nearestNeighbors) { String label = data.label; countMap.put(label, countMap.getOrDefault(label, 0) + 1); } String label = null; int maxCount = 0; for (Map.Entry<String, Integer> entry : countMap.entrySet()) { if (entry.getValue() > maxCount) { label = entry.getKey(); maxCount = entry.getValue(); } } return label; } private double getDistance(Data d1, Data d2) { double sum = 0.0; for (int i = 0; i < d1.features.length; i++) { sum += Math.pow(d1.features[i] - d2.features[i], 2); } return Math.sqrt(sum); } private static class Data { String label; double[] features; double distance; public Data(String label, double[] features) { this.label = label; this.features = features; } } } ``` 使用方法: ```java List<KNN.Data> trainSet = new ArrayList<>(); trainSet.add(new KNN.Data("A", new double[]{1, 2})); trainSet.add(new KNN.Data("A", new double[]{2, 3})); trainSet.add(new KNN.Data("B", new double[]{3, 4})); trainSet.add(new KNN.Data("B", new double[]{4, 5})); KNN knn = new KNN(trainSet); KNN.Data testData = new KNN.Data(null, new double[]{1.5, 2.5}); String label = knn.classify(testData, 3); System.out.println(label); // 输出 "A" ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值