Android Studio Android map〈string,string>to List〈Map〈String,String>>循环只取当前值

smdnsysy  于 2022-11-16  发布在  Android
关注(0)|答案(2)|浏览(261)

嘿,所有我试图添加到map<String, String>的列表使用下面的代码:

@NonNull
public static List<Map<String, String>> mysqlSelect(@NonNull String select, String where) {
    Map<String, String> info = new HashMap<String,String>();
    List<Map<String, String>> list = new ArrayList<>();
    int selectCnt = select.replaceAll("[^,]","").length();
    final boolean[] b = {true};

    new Thread(new Runnable() {
        @Override
        public void run(){
            try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
                String sql = "SELECT fb_firstname, fb_lastname, fb_id FROM fbuser";
                PreparedStatement statement = connection.prepareStatement(sql);
                ResultSet resultSet = statement.executeQuery();

                while (resultSet.next()) {
                    info.put("name", resultSet.getString("fb_firstname"));
                    info.put("address", resultSet.getString("fb_lastname"));
                    info.put("phone_number", resultSet.getString("fb_id"));

                    list.add(info);
                }

                b[0] = false;
            } catch (Exception e) {
                Log.e("InfoAsyncTask", "Error reading school information", e);
                b[0] = false;
            }
        }
    }).start();

    while (b[0]) { }

    return list; //result.get("name")
}

当它在while () {..部分循环时,它用所需的3个字符串值填充列表。然而,当它一次又一次地循环时,它似乎把所有以前的值填充到最后一次读取的值,而不是每次都有新的数据。
示例:
第1个循环:

name: bob barker
address: something
phone-number = 4235421212

第2个循环:

name: steve jobs
address: something again
phone-number = 8546521111

此时,第一循环数据变为
第1个循环:

name: steve jobs
address: something again
phone-number = 8546521111

第2个循环:

name: steve jobs
address: something again
phone-number = 8546521111

我错过了什么?

更新1

原来我有以下几点:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    new InfoAsyncTask().execute();
}

@SuppressLint("StaticFieldLeak")
public class InfoAsyncTask extends AsyncTask<Void, Void, Map<String, String>> {
    @Override
    protected Map<String, String> doInBackground(Void... voids) {
        Map<String, String> info = new HashMap<>();

        try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
            String sql = "SELECT fb_firstname, fb_lastname, fb_id FROM fbuser LIMIT 1";
            PreparedStatement statement = connection.prepareStatement(sql);
            ResultSet resultSet = statement.executeQuery();
            if (resultSet.next()) {
                info.put("name", resultSet.getString("fb_firstname"));
                info.put("address", resultSet.getString("fb_lastname"));
                info.put("phone_number", resultSet.getString("fb_id"));
            }
        } catch (Exception e) {
            Log.e("InfoAsyncTask", "Error reading school information", e);
        }

        return info;
    }

    @Override
    protected void onPostExecute(Map<String, String> result) {
        if (!result.isEmpty()) {
            TextView textViewName = findViewById(R.id.textViewName);
            TextView textViewAddress = findViewById(R.id.textViewAddress);
            TextView textViewPhoneNumber = findViewById(R.id.textViewPhone);

            textViewName.setText(result.get("name"));
            textViewAddress.setText(result.get("address"));
            textViewPhoneNumber.setText(result.get("phone_number"));
        }
    }
}

但是我想把它放到它自己的类文件中,然后从onCreate调用它。

svmlkihl

svmlkihl1#

您遇到了并发问题-当您开始使用线程并更新其他线程可以看到的值时,无法保证一个线程看到的是另一个线程所做的,除非您使用某种形式的 * 同步 *。
(基本上,为了让电脑运行真实的快,他们会对工作的顺序和记录工作的位置进行一些自由。这就像在办公室的白板上更新当前数据一样,只是为了更快地工作,人们有时只是在办公桌上的记事本上写下事情,然后 * 稍后 * 再更新白板)
有很多方法可以解决这个问题--您可以在section on concurrency in the Java documentation中获得概述,并且有很多工具可供您使用。我强烈建议您阅读它(或者类似 *Java并发实践 * 的内容),因为这是一个非常重要的概念,可以帮助您了解--如果您不知道自己在使用线程做什么,可能会发生很多奇怪和糟糕的事情!
但在这种情况下,如果您有一个boolean,只有一个线程在向其写入数据,其他所有线程都在从其阅读数据,您可以将boolean标记为volatile。这意味着它会在所有位置更新这样就可以解决你的“为什么它仍然是真的”问题。(尽管您可能需要将其添加为Runnable上的一个字段,并存储对它的引用,因为所有这些操作都是在函数中完成的)
您在Map中写入的实际数据是更大的问题-您需要确保这些数据是同步的。(在所有线程上),但是Java也提供了一些并发意识的集合类,比如ConcurrentHashMap,它们会为你处理这些问题。就像竞争条件一样--同样,你必须了解围绕这些东西的问题,这样你才能注意避免它们

p4tfgftt

p4tfgftt2#

我敢打赌,您已经想出了一个boolean[]来替代一个简单的boolean标志,您不能从lambda表达式中更改它,因为它应该是 effectively final。这个技巧将允许更新标志,但由于它可能被一个新线程缓存,另一个线程可能无法观察到它已经被更新。
要建立所谓的 happens-before 关系,需要使用AtomicBoolean而不是单元素boolean数组b,这样可以保证代码的行为是一致的。
此外,HashMap不适用于多线程环境。

解决更新后的问题

如果我没有理解错的话,你正在尝试重新实现使用过时的AsyncTask类开发的代码。
由于之前您有一个异步生成Map并返回整个内容的代码,因此您实现了Callable接口(与Runnable相反,它能够生成返回值)以实现相同的结果。
这就是这样的实现可能看起来的样子:

public class InfoTask implements Callable<Map<String, String>> {
    
    private String url;      // remove the field
    private String user;     // if you want to choose another way of providing these data
    private String password; // other than passing them to constructor
    
    // constructor
    
    @Override
    public Map<String, String> call() throws Exception {

        Map<String, String> info = new HashMap<>();

        try (Connection connection = DriverManager.getConnection(url, user, password)) {
            String sql = "SELECT fb_firstname, fb_lastname, fb_id FROM fbuser LIMIT 1";
            PreparedStatement statement = connection.prepareStatement(sql);
            ResultSet resultSet = statement.executeQuery();
            if (resultSet.next()) {
                info.put("name", resultSet.getString("fb_firstname"));
                info.put("address", resultSet.getString("fb_lastname"));
                info.put("phone_number", resultSet.getString("fb_id"));
            }
        } catch (Exception e) {
            Log.e("InfoAsyncTask", "Error reading school information", e);
        }

        return info;
    }
}

为了在单独的线程中运行Callable,可以使用ExcecutorService的示例,它基本上是一种线程管理机制,它维护一个线程池或单个线程,并安排所提供任务的执行(可以同时提交RunnableCallable)。
当任务提交后,执行程序返回一个Future的示例,你可以把它想象成一个容器,里面装着将来某个时候的预期结果。
若要取得ExcecutorService,您可以使用Executors类别所提供的其中一个静态作坊方法。
下面是一个使用单线程执行器的简单示例:

InfoTask task = new InfoTask(url, user, password);
        
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Map<String, String>> future = executor.submit(task);
        
// do some stuff here
        
// NOTE: get() call is BLOCKING
Map<String, String> resultt = future.get(5, TimeUnit.SECONDS); // don't use parameterless get(), always provide a timeout
        
executor.shutdown(); // always invoke shutdown when executor is no longer needed

您也可以执行检查isDone(),以找出Future实体是否包含结果:

if (future.isDone()) {
    Map<String, String> resultt = future.get(5, TimeUnit.SECONDS);
    // do something
}

相关问题